UNIVERSITÀ DEGLI STUDI DI SIENA
FACOLTÀ DI INGEGNERIA
CORSO DI LAUREA IN INGEGNERIA INFORMATICA
MODULO PROFESSIONALIZZANTE
"TECNOLOGIE PER IL KNOWLEDGE MANAGEMENT"
A.A. 2005/2006
DISPENSE DI
CALCOLATORI ELETTRONICI 1
Hanno collaborato alla stesura delle dispense:
Sara Belloni
Paolo Bennati
Riccardo Brogi
Elena Caini
Saverio Carito
Matteo Carletti
Ilaria Castelli
Ludovico Ciacci
Paolo Cini
Manuela Cippitelli
Elena Clementi
Matteo Collini
Andrea Corsi
Andrea Corsoni
Luca Daveri
Mauro De Biasi
Lucia Di Noi
Alessandra Di Tella
Pierluigi Failla
Valentina Fambrini
Samuele Forconi
Lucia Gentili
Roberto Giorgi
Annamaria Giovannoni
Francesco Gnarra
Ciro Guariglia
Mirko Leommanni
Michele Moramarco
Riccardo Nieto
Giacomo Novembri
Davide Pallassini
Carlo Alberto Pascucci
Erik Peruzzi
Gabriele Petri
Marcello Piliego
Nicola Pisu
Claudio Rocchi
Laura Romano
Francesco Russo
Fabrizio Simi
Carlo Snickars
Martina Tiribocchi
Andrea Tommasi
Francesco Vivi
Matilda Xheladini
Matteo Zampi
La dispensa e' rilasciata per uso dei soli studenti
del Corso di Calcolatori Elettronici 1
del Corso di Laurea di Ingegneria Informatica
della Facoltà di Ingegneria
Università degli Studi di Siena
Copyright notice
All figures from Computer Organization and Design: The Hardware/Software Approach, Second Edition, by David Patterson and
John Hennessy, are copyrighted material. (COPYRIGHT 1998 MORGAN KAUFMANN PUBLISHERS, INC. ALL RIGHTS
RESERVED.)
Figures may be reproduced only for classroom or personal educational use in conjunction with the book and only when the above
copyright line is included. They may not be otherwise reproduced, distributed, or incorporated into other works without the
prior written consent of the publisher.
INDICE
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
Lezione
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
Introduzione
Assembly MIPS (parte prima)
Assembly MIPS (parte seconda)
Valutazione delle prestazioni (parte prima)
Valutazione delle prestazioni (parte seconda)
Valutazione delle prestazioni (parte terza)
Standard IEEE-754 per le operazioni floating point
Assembly MIPS
Interrupt
Implementazione di semplice CPU MIPS
BUS
BUS PCI, SCSI e USB
Tecniche di pilotaggio dei dispositivi I/O
Esempi di dispositivi di I/O: il timer e la porta seriale
Hard disk
Introduzione al sottosistema di memoria
Introduzione alla memoria cache (parte prima)
Introduzione alla memoria cache (parte seconda)
Memoria virtuale
Pipeline
LEZIONE 1
Introduzione
1.1 INTRODUZIONE
Queste dispense sono state raccolte con lo scopo di fornire un riferimento sintetico alla
materia trattata nel Corso di Calcolatori Elettronici 1 presso la Facolta’ di Ingegneria
dell’Universita’ di Siena. Per gli approfondimenti (molti!) alla materia si rimanda ai Testi di
Riferimento del corso [1], [2], [3], [4], [5], [6], [7] e ai Riferimenti Bibliografici posti al
termine di ognuna delle venti lezioni. Gli argomenti di una lezione vengono tipicamente trattati
in circa due ore. Accompagnano il materiale didattico del corso, gli esercizi proposti nelle
prove di esame e i simulatori architetturali (disponibili sul sito web del corso [8]).
1.1.1 Prerequisiti del Corso
Per affrontare il corso di Calcolatori Elettronici 1 è opportuno aver acquisito alcune
conoscenze di base dai corsi precedenti, con particolare riferimento a:
• struttura base della macchina (macchina di Von Neumann, ciclo fetch-execute);
• lettura e scrittura di programmi C;
• passi per eseguire il programma (compilazione, collegamento, caricamento, esecuzione);
• progetto logico di un circuito (componenti logici, macchine a stati finiti,).
1.2 STRUTTURA DEL CORSO
I tre
•
•
•
principali obiettivi che il corso si propone di raggiungere sono:
imparare a misurare e analizzare le prestazioni di un Calcolatore;
capire l’architettura di un Calcolatore;
individuare possibili ottimizzazioni per migliorare le potenzialità di un Calcolatore.
1.2.1 Analisi delle prestazioni
Per poter valutare la bontà di un Calcolatore è necessario innanzitutto mettersi d’ accordo su
come misurarne le prestazioni relativamente allo scopo che si vuole adibire a quella macchina.
L’obiettivo della prima parte del corso è quindi quello di analizzare i fattori che influenzano le
prestazioni, capire come effettuare un’analisi quantitativa di essi e discutere di come questi
siano influenzati dall’architettura scelta. Gli argomenti affrontati in questa prima parte sono:
• definizione “software” della macchina (lezioni 2,3);
• metriche e benchmark per la valutazione delle prestazioni (lezioni 4,5,6);
• influenza dei criteri prestazionali sulla definizione “software” (lezioni 7,8,9).
1.2.2 Analisi dell’architettura
Per comprendere l’architettura dei moderni Calcolatori si analizzera’ l’organizzazione interna
dei principali elementi della macchina cosi’ come sono oggi realizzati: processore, memoria,
sottosistema di input/output (I/O). I principali argomenti trattati in questa parte sono:
• struttura del processore: parte di controllo e datapath (lezione 10);
• reti di interconnessione e interfacciamento (lezioni 11,12);
• sottosistema di Input/Output (lezioni 13,14,15);
• memoria (lezione 16).
1.2.3 Meccanismi architetturali per il miglioramento delle performance
Parti “accessorie” del Calcolatore ma ormai quasi universlamente utilizzate sono:
• memoria cache (lezioni 17,18);
• memoria virtuale (lezione 19);
• pipeline (lezione 20).
1.1
1.3 EVOLUZIONE DEI CALCOLATORI
1.3.1 La legge di Moore
Il forte sviluppo dei Calcolatori Elettronici dipende in gran parte dai progressi della
Tecnologia Elettronica e in particolare dei Circuiti Integrati la cui capacita’, in termini di
numero di transistor e quindi di funzionalita’ ha seguito per oltre 40 anni la Legge di Moore:
“Il numero di transistor su singolo chip raddoppia ogni 18 mesi”
Nel 1965, Gordon Moore (che successivamente nel 1968 fondo’ con R. Noyce la ditta Intel)
osservo’ l’evoluzione dell’industria elettronica nei 6 anni precedenti (1959-1965) e gli sembro’
ragionevole una proiezione [9] secondo cui il numero di “componenti a minimo costo” in un
circuito integrato sarebbe raddoppiato ogni 12 mesi; questa supposizione si rivelò corretta “al
90%” fino al 1975. Osservando invece, il numero di transistor nei processori Intel da meta’ del
1971 (anno di introduzione del processore 4004, con 2300 transistor) a fine del 2000 (anno di
introduzione del processore Pentium-4, con 43 milioni di transistor) si puo’ osservare un
raddoppio dei transistor solo ogni 24 mesi (in parte questo è contemplato in [9]). Altri (non
Moore) hanno fatto si’ che quella formulata sopra sia la versione “popolarmente acclamata
della Legge di Moore”.
Pent 4
Pent III
Pent II
Pent Pro
Pent.
Migliaia di Transistor
10000
486SL
486
286
100
8086
8080
4004
1
1972
1976
1980
1984
1988
1992
1996
2000
Figura 1.1: La legge di Moore.
A prescindere dalle diverse interpretazioni, resta comunque il dato di fatto di uno
stupefacente incremento, che non ha eguali in nessun altro settore industriale. Simili
incrementi si possono altresi’ registare per altri componenti dell’Architettura del Calcolatore:
• Processore: Porte logiche:
+30%/anno
Velocità clock:
+20%/anno
• Memoria:
Dimensione:
+60%/anno
Velocità:
+9%/anno
Costo per bit:
+25%/anno
• I/O (Disco): Dimensione:
+60%/anno
1.3.2 I tipi di Calcolatori Elettronici
Si puo’ dire che agli inizi del terzo millennio le tipologie di Calcolatori esistenti sono
sostanzialmente tre (v. anche figura 1.2):
• Sistemi Desktop (principalmente analizzati in questo corso);
• Sistemi Server (Network of Workstation, Supercomputer, Parallel Computers);
• Sistemi Embedded (dai telefonini ai palmari e ai sistemi di controllo industriale).
1.2
Mainframe
Massively
Parallel Processors
Vector
Supercomputer
Minicomputer
Portable
Computers
Networks of Workstations/PCs
Figura 1.2: Computer Food Chain.
Altre tipologie introdotte agli albori dell’introduzione dei “Calcolatori a programma
memorizzato” sono ormai scomparse (Mainframe, MiniComputer) o prevalentemente confluite
(Vector Supercomputer, Massively Parallel Processor) in altre tipologie.
1.4 ARCHITETTURA DI UN CALCOLATORE DESKTOP
Un Calcolatore di tipo desktop può essere visto come un elettrodomestico componibile. Al suo
interno sono presenti infatti molti componenti che consentono di mettere in piedi l’intera
macchina in maniera abbastanza semplice e modulare. Una tipica organizzazione di un
Calcolatore Desktop e’ riportata in Figura 1.3. Tale organizzazione si discosta pochissimo
dall’originaria impostazione presentata da Von Neumann [10] nel 1946.
CPU
Cache
Bus
Memoria
Adattatore
(bridge)
Disco
Dispostivi di I/O: Display
Tastiera
Controller
Scheda di rete
INTERNET
Figura 1.3: Organizzazione a blocchi.
1.3
1.4.1 Limiti dovuti alla dimensione del chip
Sabbene sia teoricamente conveniente avere chip “grossi” per ottenere un numero elevato di
transistor (e quindi di funzionalita’) sullo stesso, cio’ non viene attuato nella realta’ perche’
“il costo di un chip è proporzionale a circa il cubo della sua area. “
E’ infatti possibile calcolare il costo del chip (o ‘die’) con la seguente formula:
C DIE =
CWAFER
N DIE ⋅ YWAFER
dove: CDIE = Costo del die
CWAFER = Costo del wafer
NDIE = Numero di die in un wafer
YWAFER = ‘Yield’ o Resa del wafer (numero ‘die’ per unita’ di sup.)
Il numero di die all’interno di un wafer e la resa del wafer stesso dipendono dall’area del die,
come si vede dalle seguenti formule:
2
N DIE
π ⋅ ⎛⎜ DWAFER 2 ⎞⎟
AWAFER
⎠ − π ⋅ DWAFER − N
= ⎝
TEST ≅
ADIE
ADIE
2 ⋅ ADIE
YWAFER =
1
2
1 + (F ⋅ ADIE 2)
dove: DWAFER = Diametro del wafer
ADIE = Area del ‘die’=⋅π(DWAFER/2)2
AWAFER = Area del wafer
NTEST = Numero di ‘die’ usati per test
F
= Difetti per unita’ di superfice
1.4.2 Limiti dovuti al consumo di potenza
Il consumo di potenza di un calcolatore elettronico è un altro degli aspetti fondamentali che
limita il numero di componenti che e’ possibile integrare sul chip. Inolre, questo problema
risulta essere maggiormente pressante con l’avvento di CPU sempre più complesse.
Il consumo di potenza è influenzato dalla densità con cui sono costruiti i chip; chip più densi,
che significano quindi sistemi più complessi, richiedono un maggiore consumo di potenza per
unità di superficie. Per un Calcolatore Desktop il limite “a braccio” che viene utilizzato per la
potenza totale dissipata (Total Dissipated Power o TDP) e’ intorno ai 100W.
1.5 DEFINIZIONE DI “ARCHITETTURA DI UN CALCOLATORE”
L’Architettura di un Calcolatore e’ “...l’insieme dell’insieme degli attributi visibili al
programmatore, ovvero la struttura concettuale e il comportamento funzionale, distinti
dall’organizzazione del flusso dei dati e del controllo, dagli elementi logici e
dall’implementazione fisica” (tradotto dalla definizione originale che compare in [12]).
Per definire un’architettura si dovrà specificare come organizzare i dispositivi di
memorizzazione, definire quali siano i tipi di dato e le strutture dati, stabilire l’insieme delle
istruzioni ed il loro formato, fissare un metodo di indirizzamento della memoria e di accesso
ai dati e alle istruzioni ed, infine, essere in grado di gestire le eccezioni.
In altri termini, l’Architettura del Calcolatore e’ l’interfaccia tra cio’ che viene fornito
dall’Hardware e cio’ che viene richiesto dal Software.
1.4
Un termine vicino ma distinto e’ Microarchitettura, che viene usato per definire le risorse ed i
metodi per implementare una data Architettura [13] di un Calcolatore ovvero la
Microarchitettrura e’ l’implementazione di una Architettura e fornisce a sua volta la specifica
da utilizzare per il progetto logico (e successivamente elettrico e fisico).
1.6 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] G. Bucci, "Architettura dei Calcolatori Elettronici", McGraw-Hill, 2001, ISBN 88-386-0889-X.
[3] P. Corsini, G. Frosini, "Architettura dei calcolatori", McGraw Hill, 1997, ISBN 88-386-0735-4.
[4] A. S. Tanenbaum, "Structured computer organization", 4th ed., Prentice-Hall International, 1999,
ISBN 0130959901.
[5] V.P. Heuring, "Computer Systems Design and Architecture" 2ed, Pearson/Prentice Hall, 2004,
ISBN 0-13-191156-2.
[6] S. Furber, "ARM System-on-chip architecture", 2ed, Pearson/Addison Wesley, 2000, ISBN 0-20167519-6.
[7] W. Stallings, "Architettura e organizzazione dei calcolatori", Jackson Libri, 2000, ISBN 88-2561836-0.
[8] Sito Web del Corso.
http://www.dii.unisi.it/~giorgi/didattica/calel
[9] G. E. Moore, “Cramming more components onto integrated circuits”, Electronics Magazine (1965)
(ftp://download.intel.com/museum/Moores_Law/Articles-Press_Releases/Gordon_Moore_1965_Article.pdf).
[10] J. von Neumann, “First Draft of a Report on the EDVAC” (anche disponibile al sito:
http://www.virtualtravelog.net/entries/2003-08-TheFirstDraft.pdf).
[11] Intel, Microprocessor Quick Reference Guide,
http://www.intel.com/pressroom/kits/quickrefyr.htm
[12] G. M. Amdahl, G.A. Blaauw, F.P. Brooks, (1964), “Architecture of the IBM System/360”, IBM J.
Research and Development 8. Reprinted (2000) loc.cit. 44 21-37.
(http://www.research.ibm.com/journal/rd/441/amdahl.pdf)
[13] B. Shriver and B. Smith. The Anatomy of a High-Performance Microprocessor, A Systems
Perspective. IEEE Computer Society Press, July 1998.
1.5
LEZIONE 2
Assembly MIPS (parte prima)
.
2.1 INTRODUZIONE
I calcolatori, per loro natura, comprendono segnali composti da due soli elementi, che
rappresentano lo stato di on e off, identificati nei numeri 0 e 1 e il linguaggio formato da
questo alfabeto è detto linguaggio macchina. I programmatori anziché il linguaggio macchina
utilizzano il linguaggio assembly che associa alle sequenze binarie del linguaggio macchina una
rappresentazione simbolica più comprensibile. Tutti i comandi che vengono impartiti ai
calcolatori sono detti “istruzioni”, ossia sequenze di 0 e 1 che il calcolatore è in grado di
comprendere. Un programma assemblatore traduce la notazione simbolica, quindi in linguaggio
assembly, di un’istruzione, nella corrispondente notazione binaria, ovvero in linguaggio
macchina.
Ad esempio:
add $so, $s1, $s2
000000 10010 10011 10001 00000 100000
(linguaggio assembly MIPS)
(linguaggio macchina MIPS)
L’ istruzione nell’esempio dice al calcolatore di sommare due numeri.
Il linguaggio macchina ha una diretta implementazione in termini circuitali. Sopra al linguaggio
assembly si costruiscono dei linguaggi ad alto livello che includono delle istruzioni più vicine al
linguaggio naturale in quanto permettono la programmatore di non specificare certi tipi di
dettagli implementativi della macchina ed inoltre sono più flessibili rispetto all’assembly.
Alcuni dei più famosi linguaggi ad alto livello sono il Java, il C++, il C e il Pascal. I programmi
che utilizzano tali linguaggi si chiamano compilatori e consentono al programmatore di scrivere
espressioni del tipo:
C = A + B;
che il compilatore tradurrà in istruzione assembly
(linguaggio ad alto livello C)
add $so, $s1, $s2.
Dove la variabile A è associata al contenuto del registro $s1, la variabile B a quello del
registro $s2 e la variabile C al registro $s0.
2.2 ISTRUZIONI ASSEMBLY
Per poter gestire l’hardware di un calcolatore bisogna saper parlare il suo linguaggio; le parole
del linguaggio del calcolatore si chiamano istruzioni e il vocabolario è il set di istruzioni. Il set
di istruzioni prescelto proviene dal processore MIPS, usato fra gli altri da NEC, Nintendo,
Sylicon Graphics e Sony. L’architettura dei processori MIPS appartiene alla famiglia delle
architetture RISC (Reduced Instruction Set Computer) sviluppate dal 1980 in poi. I
principali obiettivi delle architetture RISC sono la semplificazione della progettazione
dell'hardware e del compilatore, la massimizzazione delle prestazioni e la minimizzazione dei
costi. Gli operandi delle istruzioni non possono essere variabili in memoria ma devono essere
scelti fra i registri interni al microprocessore. Fanno eccezione solo le istruzioni load e store
che fanno riferimento a variabili in memoria. Un processore possiede un numero limitato di
registri ad esempio il processore MIPS possiede 32 registri composti da 32-bit (word). In
tutti i casi in cui è possibile i registri sono associati alle variabili di un programma dal
compilatore. Inoltre se i registri non fossero sufficienti per rappresentare le variabili che
vogliamo, possiamo ricorrere alla memoria, ma questo metodo non si usa sempre dato che
2.1
l’accesso alla memoria è più lento di quello ai registri, argomento che sarà affrontato in
dettaglio nella lezione 16. Per identificare i registri si usano nomi simbolici preceduti da $, ad
esempio: $s0, $s1 oppure possono essere anche indicati direttamente mediante il loro
numero (0, …, 31) preceduto sempre da $. I nomi simbolici e i numeri per i registri vengono
assegnati attraverso questa convenzione:
Tabella 2.1: Convenzione per i registri MIPS
Numero registro
0
2-3
4-7
8-15
16-23
24-25
26-27
28
29
30
31
Uso
Valore costante 0
Valore dei risultati e valutazione di espressione
Parametri
Variabili temporanee
Variabili salvate
Altre variabili temporanee
Riservato al kernel
Global pointer
Stack pointer
Frame pointer
Indirizzo di ritorno
Nome
$zero
$v0-$v1
$a0-$a3
$to-$t7
$s0-$s7
$t8-$t9
$k0-$k1
$gp
$sp
$fp
$ra
Le istruzioni del linguaggio assembly MIPS possono essere divise nelle seguenti categorie:
• Istruzioni ARITMETICO-LOGICHE
• Istruzioni LOAD and STORE (trasferimento da/verso memoria)
• Istruzioni di salto condizionato e non condizionato per il controllo del flusso di
programma
Il linguaggio assembly fornisce anche delle pseudoistruzioni che costituiscono un modo più
“comodo” per scrivere istruzioni assembly o brevi sequenze di istruzioni assembly. Queste
sono istruzioni fornite dall’assembler ma non implementate in hardware.
Ad esempio:
move $t0, $t1
esiste solo in assembly in quanto l’assemblatore traduce usando
add $t0, $t1, $zero
Nel registro t0 viene messo il risultato della somma del contenuto di t1 e di zero, ovvero
viene spostato il contenuto di t1 in t0.
2.2.1 Istruzioni aritmetiche
In MIPS, un’istruzione aritmetico-logica possiede tre operandi che sono sempre e solo
registri: i due registri contenenti i valori da elaborare (registri sorgente) e il registro
contenente il risultato (registro destinazione). L’ordine degli operandi è fisso: prima il
registro contenente il risultato dell’operazione e poi i due operandi.
Istruzione add
L’istruzione add serve per sommare il contenuto di due registri sorgente s1 e s2 e mettere il
risultato in s0.
2.2
Esempio:
Codice C: R = A + B
Codice MIPS: add $s0, $s1, $s2
Dove la variabile R è associata al registro $s0, la variabile A al registro $s1 e la variabile
B a $s2.
Nella traduzione da linguaggio ad alto livello a linguaggio assembly, le variabili sono associate
ai registri dal compilatore Quando dobbiamo svolgere operazioni con un numero di operandi
maggiore di tre queste possono essere effettuate scomponendole in operazioni più semplici, in
quanto ciascuna delle istruzioni MIPS può eseguire una sola operazione.
Ad esempio, per eseguire la somma delle variabili B, C , D ed E nella variabile A servono tre
istruzioni:
Codice C:
A=B+C+D+E
Codice MIPS: add $t0, $s1, $s2
add $t0, $t0, $s3
add $s0, $t0, $s4
# il registro $t0 contiene B+C
# il registro $t0 contiene B+C+D
# il registro $s0 contiene B+C+D+E
Dove la variabile A è associata al registro $s0, la variabile B al registro $s1, la
variabile C a $s2, la variabile D a $s3 e la variabile E a $s4. Il registro $t0 è utilizzato
per memorizzare temporaneamente il risultato.
Istruzione sub
L’istruzione sub serve per sottrarre il contenuto di due registri sorgente s1 e s2 e mette il
risultato in s0.
Esempio:
Codice C:
R=A-B
Codice MIPS: sub $s0 ,$s1, $s2
Dove la variabile R è associata al registro $s0, la variabile A al registro $s1 e la variabile
B a $s2.
2.2.2 Istruzioni di trasferimento
Gli operandi delle istruzioni aritmetiche devono risiedere nei registri che, come abbiamo già
detto, nel nostro processore MIPS sono 32. Se abbiamo programmi i cui dati richiedono più di
32 registri, ovvero hanno moltissime variabili, alcuni di questi risiedono nella memoria.
L’allocazione in memoria di alcune variabili è detto spilling (riversamento). A questo proposito
servono specifiche istruzioni atte a trasferire i dati dalla memoria ai registri e viceversa;
queste possono solo leggere o scrivere un operando ma non possono eseguire nessuna
operazione su di esso. Le operazioni base di questo tipo sono due la load e la store
rispettivamente.
Breve richiamo sulla struttura della memoria
La memoria può essere vista come un unico grande array uni-dimensionale con gli indirizzi che
rappresentano l’indice del vettore a partire da 0. Dato che i byte, composti da 8 bit, sono
utilizzati da molti programmi, la maggior parte delle architetture indirizza il singolo byte ma
per ragioni di efficienza i microprocessori trasferiscono più byte in un colpo solo (come ad
2.3
esempio un microprocessore a 32 bit). Quindi dato che i programmi utilizzano l’indirizzamento
al byte e le istruzioni sono a 32 bit (quindi comprendono 4 byte) il loro indirizzo è sempre a
multipli di quattro, così parole adiacenti hanno indirizzi che differiscono di un fattore di
quattro. All’interno di una parola i byte possono essere ordinati facendo corrispondere
all’indirizzo al byte (es. 0x00000000) il byte più significativo di una parola a 4 byte; si parla in
questo caso di disposizione big-endian (v. figura 2.1). Se invece si fa corrispondere
all’indirizzo al byte (es. 0x00000000) il byte meno significativo si parla di disposizione littleendian (v. figura 2.1).
Vogliamo andare alla parola 0 con questa sequenza di byte numerati da 0 a 3 le due
disposizioni sono:
Disposizione big-endian
0
1
2
3
Disposizione little-endian
3
2
1
0
Fgura 2.1: Disposizione dei bit per big-endian e little-endian. Nel caso big-endian la memorizzazione inizia
dal byte più significativo per finire col meno significativo; nel caso little-endian la memorizzazione inizia dal
byte meno significativo per finire col più significativo.
Istruzione load
L’istruzione LOAD serve per trasferire un dato dalla memoria in un registro. Il nome
convenzionale di questa istruzione nel linguaggio MIPS è lw, che significa load word (carica
una parola). L’istruzione lw ha tre argomenti: il registro di destinazione in cui caricare la
parola letta dalla memoria, una costante che rappresenta l’offset (o spiazzamento) e un
registro sorgente che contiene il valore dell’indirizzo base da sommare alla costante. Quindi
l’indirizzo effettivo della parola di memoria da caricare nel registro di destinazione viene
calcolato sommando un valore base, contenuto nel registro sorgente e un offset direttamente
specificato all’interno dell’istruzione.
$s 3
(BASE)
4*k
(OFFSET)
Figura 2.2: Schema di funzionamento dell’istruzione di LOAD.
Esempio:
Codice C: g = h + A[8]
Codice MIPS: lw $t0, 32($s3)
add $s1, $s2, $t0
# il registro $t0 assume il valore A[8]
# g = h + A[8]
2.4
In cui la variabile g è associata al registro $s1, la variabile h al registro $s2, A a $s3
(registro base), 8 rappresenta il nono elemento del vettore di indirizzamento a 32 bit,
ossia l’offset 8 × 4=32.
Istruzione store
L’istruzione STORE è la complementare della load, infatti serve per trasferire il contenuto di
un registro verso la memoria. Il nome convenzionale in linguaggio MIPS è sw, che significa
store word (memorizza una parola). Il suo formato è analogo a quello della load: per primo
viene messo il nome del registro che contiene il dato da trasferire in memoria, poi l’offset e in
fine il registro base. Nell’istruzione store il registro destinazione è l’ultimo operando.
Esempio:
Codice C:
A[12] = h + A[8]
Codice MIPS: lw $t0, 32($s3)
add $t0, $s2, $t0
sw $t0, 48($s3)
# il registro t0 assume il valore A[8]
# il registro $t0 assume il valore h + A[8]
# memorizza h + A[8] in A[12]
In cui la variabile h è associata al registro $s2, A a $s3 (registro base), 8 rappresenta il
nono elemento del vettore di indirizzamento a 32 bit, ossia l’offset 8 × 4=32, ugualmente
12 che ha un offset di 12 × 4=48.
2.2.3 Istruzioni di salto condizionato e non condizionato
Le istruzioni di salto condizionato e non condizionato sono rese necessarie dalla struttura del
calcolatore. L'unità di controllo fa funzionare l’elaboratore che da quando viene acceso a
quando viene spento esegue di continuo il ciclo di prelievo / decodifica / esecuzione (fetch /
decode / execute ), la cui struttura è schematizzata nella seguente tabella.
Tabella 2.2: Il ciclo Fecth-Decode-Execute.
Passo 1.
Carica un'istruzione dalla memoria puntata da un registro speciale chiamato “program counter” (PC), e la
assegna a un registro interno chiamato IR.
Passo 2.
Cambia PC con l'indirizzo successivo in memoria (PC=PC+4).
Passo 3.
Determina il tipo di istruzione appena caricata nel registro delle istruzioni in base al suo opcode (Codice
Operativo).
Passo 4.
Si chiede se l'istruzione utilizza una parola in memoria. In tal caso determina l’effettivo indirizzo
dell’operando in memoria (base + offset).
Passo 5.
Se necessario, carica la parola residente in memoria in un registro della cpu.
Passo 6.
Esegue l'istruzione.
Passo 7.
Torna all'inizio, al 1 passo, ed esegue la prossima istruzione.
Il ciclo descritto in tabella può essere così sintetizzato:
• Operazione di FETCH
2.5
Passi 1 2
• Operazione di DECODE
Passo 3
• Operazione di EXECUTE
Passo 4, 5, 6.
Istruzioni particolari possono alterare il prelievo delle istruzioni da celle consecutive come ad
esempio le istruzioni di salto.
Le istruzioni di salto condizionato e non condizionato prendono il nome anche di istruzioni di
decisione in quanto alterano il flusso principale del programma, ovvero cambiano la “prossima”
istruzione da eseguire. Le istruzioni di salto condizionato utilizzano un campo offset di 16 bit
(con segno); possono quindi saltare a una locazione posta nell’intervallo [-215-1; 215].
Le istruzioni di salto incondizionato contengono invece un campo di 26 bit per specificare
l’indirizzo, anche se il calcolo dell’indirizzo effettivo è diverso a seconda del tipo di istruzione,
come vedremo nel capitolo 2. I salti condizionati si dividono in beq (branch if equal), bne
(branch if not equal) e slt (set on less than) mentre quelli incondizionati vengono effettuati
con l’istruzione j (jump) e jal (jump and link).
Istruzioni di salto condizionato
Le istruzioni di salto sono effettuate attraverso le operazioni di branch che cambiano il
flusso del programma creando diramazioni. L’istruzione beq trasferisce il flusso di controllo
(PC) all’istruzione etichettata con Label se e, solo se, il valore contenuto nel registro t0 è
uguale al valore del registro t1:
beq $t0, $t1, Label
Esempio:
Codice C:
if (i= =j)
f = g + h;
f =f –i;
Codice MIPS:
beq $s3, $s4, L1
add $s0, $s1, $s2
L1: sub $s0, $s0, $s3
# va a L1 se i = j
# f = g + h ( si salta se i = j )
# f = f – i ( eseguita sempre)
Dove la variabile i è associata al registro $s3, la variabile j al registro $s4, la variabile f
a $s0, la variabile g a $s1 e la variabile h a $s2.
L’istruzione bne indirizza all’istruzione etichettata con Label se e, solo se, il valore contenuto
nel registro t0 non è uguale al valore del registro t1.
bne $t0, $t1, Label
E’ opportuno chiedersi a questo punto quale istruzione utilizzare nel caso vogliamo fare
confronti di disuguaglianza ( <, ≤ , >, ≥ ) fra due variabili. A questo proposito viene utilizzata
l’istruzione slt che serve per verificare se la variabile corrispondente al registro s0 è minore
della variabile corrispondente al registro s1. Il registro t0 diventa uguale a 1 se s0 < s1 0
altrimenti.
Quindi il test finale è realizzato da un’istruzione bne che confronta i registri t0 e zero,
oppure da un’istruzione beq che confronta i registri t0 e la costante 1.
2.6
slt $t0, $s0, $s1
Esempio:
Codice C:
if A<B;
then C=1;
else C=0;
Codice MIPS: slt $t0, $s1, $s2
La variabile A è associata al registro $s1, la variabile B a $s2, e la variabile C a $t0.
Istruzioni di salto non condizionato
L’istruzione di salto non condizionato è j ed indica all’elaboratore di eseguire sempre il salto;
l’indirizzo di destinazione del salto è un indirizzo assoluto di memoria.
Esempio:
Codice C:
if (i = = j) f = g + h;
else f = g – h;
Codice MIPS: beq $s3, $s4, L1
L1:add $s0, $s1, $s2
bne $s3, $s4, L2
L2: sub $s0, $s1, $s2
j esci
# va a L1 se i = j
# f = g + h ( si salta se i = j )
# va a L2 se i ≠ j
# f = g -h
# vai a esci
La variabile i è associata al registro $s3, la variabile j al registro $s4, la variabile f al
registro $s0, e le variabili g e h rispettivamente a $s1 e $s2.
L’istruzione di salto non condizionato jal richiede un’analisi più approfondita che sarà
affrontata nella lezione 3.
2.2.4 Esempi di programmi assembly
Implementazione ciclo “while”
Codice C: while (A[i]==k)
i=i+j;
Codice MIPS:
Ciclo: add $t1, $s3, $s3
add $t1, $t1, $t1
add $t1, St1, $s6
lw $t0, 0($t1)
bne $t0, $s5, esci
add $s3, $s3, $s4
j ciclo
Esci:
# il registro temporaneo $t1 è uguale a 2 × i
# il registro temporaneo $t1 è uguale a 4 × i
# $t1 è uguale all’indirizzo di A[i]
#il registro temporaneo $t0 è uguale a A[i]
# vai ad esci se A[i]=k
# i=i+j
# vai a ciclo
Si assuma che le variabili i, j e k corrispondano ai registri $s3, $s4 e $s5 rispettivamente e
che $s6 contenga l’indirizzo di base del vettore A.
Implementazione ciclo “do while”
Codice C: do
2.7
g = g+A[i];
i=i+j;
while (i!=h)
Codice MIPS:
Ciclo: add $t1, $s3, $s3
add $t1, $t1, $t1
add $t1, St1, $s5
lw $t0, 0($t1)
add $s1, $s1, $t0
add $s3, $s3, $s4
bne $s3, $s2, ciclo
# il registro temporaneo $t1 è uguale a 2 × i
# il registro temporaneo $t1 è uguale a 4 × i
# $t1 è uguale all’indirizzo di A[i]
# il registro temporaneo $t0 è uguale a A[i]
# g=g+A[i]
# i=i+j
# se i ≠ h vai a ciclo
Si suppone che le variabili g e h siano associate a $s1 e $s2, i e j associate a $s3, $s4 e che
$s5 contenga l’indirizzo di base di A.
2.3 LINGUAGGIO MACCHINA
Tutte le istruzioni MIPS hanno la stessa dimensione (32 bit). I 32 bit hanno un significato
diverso a seconda del formato (o tipo) di istruzione. Tutte le istruzioni scritte dal
programmatore in linguaggio assembly vengono passate al calcolatore come una sequenza finita
di bit scritta in base 2. Il tipo di istruzione è riconosciuto in base al valore dei 6 bit più
significativi che compongono il codice operativo (OPCODE, OP) mentre i rimanenti 26 bit sono
suddivisi diversamente in base al formato in cui l’istruzione è scritta. Esistono tre tipi di
formati:
• Tipo R (register)
• Tipo I (immediate)
• Tipo J (jump)
2.3.1 Formato di tipo R
Con il formato di tipo R vengono descritte le funzioni aritmetico-logiche. A ciascuno dei campi
delle istruzioni MIPS viene associato un nome, per poter fare più agevolmente gli opportuni
riferimenti.
opcode
6 bit
rs
5 bit
rt
5 bit
rd
5 bit
shamt
5 bit
funct
6 bit
Ogni campo in cui è suddivisa l’istruzione ha un preciso significato:
• Op (opcode) identifica il tipo di operazione è chiamato anche codice operativo
• Rs primo operando sorgente
• Rt secondo operando sorgente
• Rd registro di destinazione che contiene il risultato dell’operazione
• Shamt questo termine contiene il valore dello scorrimento (shift amount che verrà
discussa successivamente)
• Funct funzione. Seleziona una variante specifica dell’operazione base descritta nel
campo op, è chiamato “codice funzione”.
Esempio:
2.8
Add $t0, $s1, $s2
0
17
18
8
0
32
000000
10001
10010
01000
00000
100000
Esempio istruzione formato R:
Nome campo
Dimensione
Add $s1,$s2,$s3
Op
6 bit
000000
Rs
5 bit
10010
Rt
5 bit
10011
Rd
5 bit
10001
Shamt
5 bit
00000
Funct
6 bit
100000
2.3.2 Formato di tipo I
Il formato di tipo I nasce dall’esigenza di specificare operandi immediati (costanti)
direttamente nelle istruzioni. In generale servirebbero 32 bit ma fortunatamente nella
maggioranza dei software le costanti utilizzate sono piccole, quindi con 16 bit si risolve già la
stragrande maggioranza dei casi. Infatti quando si vuole rappresentare un’istruzione di
trasferimento con il formato R possono nascere dei problemi in quanto questa necessita di
campi di dimensioni maggiori rispetto a quelli sopra specificati. Ad esempio l’istruzione load
deve specificare due registri e una costante; se la costante dovesse essere specificata in uno
dei campi da cinque bit, sarebbe limitata al valore di 25, cioè 32, ma poiché questa serve per
indirizzare all’interno di vettori di grandi dimensioni spesso assume valori maggiori. Per far
rimanere la lunghezza delle istruzioni sempre di 32 bit il compromesso è quello di creare
formati diversi per le diverse istruzioni.
Per le istruzioni di trasferimento utilizziamo il tipo I che ha la seguente struttura:
opcode
6 bits
rs
5 bits
rt
5bits
In questo caso i campi hanno il seguente significato:
• opcode identifica il tipo di istruzione
• rs indica il registro base
• rt indica il registro destinazione dell’istruzione di caricamento
• address riporta lo costante (in alcuni casi offset)
2.9
address
16 bits
Esempio:
lw $t0, 32($s3)
35
19
8
32
100011
10011
01000
0000000000100000
Esempio istruzione formato I:
Nome campo
Dimensione
Lw $t0, 32($s3)
Op
6 bit
100011
Rs
5 bit
10011
Rt
5 bit
01000
Address
16 bit
0000 0000 0010 0000
Con il formato I oltre che alle istruzioni di trasferimento vengono descritte anche tutte le
istruzioni con operandi immediati e operazioni di salto condizionato. Quest’ultime possono
avere un offset maggiore dei 16 bit, che consente il formato I, quindi una valida alternativa
consiste nello specificare un registro il cui valore deve essere sommato all’indirizzo di salto
permettendo così di raggiungere 32 bit. Il registro da usare è il program counter che
contiene l’indirizzo dell’istruzione corrente e quindi può saltare fino a una distanza di ± 215
istruzioni rispetto alle istruzioni in esecuzione. Siccome in quasi tutti i cicli e i costrutti di if,
in cui i salti condizionati vengono usati, l’etichetta a cui salto è tipicamente un numero di
istruzioni minore di 216 parole, il program counter è la scelta ideale.
2.2 3 Formato di tipo J
Il terzo tipo di formato di istruzioni (formato J) è il formato usato per le istruzioni di salto
incondizionato.
Op
6 bit
Address
26 bit
In questo caso i campi hanno il seguente significato:
• Op indica il tipo di operazione
• Address riporta una parte (26 bit su 32) dell’indirizzo assoluto di destinazione del
salto
L’assemblatore sostituisce l’etichetta con i 28 bit meno significativi traslati a destra di 2
(divisione per 4 per calcolare l’indirizzo di parola) per ottenere 26 bit; in pratica elimina i due
zero finali e si amplia lo spazio di salto tra zero e 228 byte (ossia 226 word). I 26 bit del campo
indirizzo rappresentano un indirizzo di parola. Quindi corrispondono a un indirizzo di byte
composto da 28 bit; poiché il registro PC è composto da 32 bit si verifica che l’istruzione jump
rimpiazza solo i 28 bit meno significati del PC lasciando inalterati i rimanenti quattro bit più
significativi.
2.10
Nome campo
Dimensione
j 32
Op
6 bit
000010
Address
26 bit
00 0000 0000 0000 0000 0000 1000
2.11
LEZIONE 3
Assembly MIPS (parte seconda)
3.1 LA MOLTIPLICAZIONE
Come è noto effettuando l’operazione di moltiplicazione tra due numeri binari rappresentati
su n bit, in generale sono necessari 2n bit per rappresentare il risultato [1].
Nel MIPS tutti i registri utilizzati hanno una dimensione di 32 bit; moltiplicando il contenuto
di due registri da 32 bit per rappresentare correttamente il risultato sono quindi necessari
64 bit.
Il risultato della moltiplicazione viene posto sempre in due registri dedicati (special porpouse)
da 32 bit ciascuno, denominati Hi e Lo in grado di contenere il prodotto su 64 bit.
L’istruzione MIPS per effettuare la moltiplicazione è mult (multiply).
Esempio:
mult $2, $3
#calcola il prodotto tra il contenuto dei registri $2 e $3 e il risultato si
trova nei registri Hi e Lo.
In particolare il registro Hi contiene i 32 bit più significativi del prodotto, Lo i 32 bit
meno significativi (Figura 3.1).
$2
HI
LO
mult
$3
64 bit
32 bit
Figura 3.1. L’istruzione mult.
Per eseguire la moltiplicazione tra numeri senza segno possiamo utilizzare multu (multiply
unsigned).
Il prodotto inserito nei registri Hi e Lo può essere recuperato con le istruzioni mfhi (move
from hi) e mflo (move from lo).
Esempio:
mfhi $4
mfhi $5
#trasferisce nel registro $4 il contenuto di Hi.
#trasferisce nel registro $5 il contenuto di Lo (Figura 3.2).
HI
LO
$4
$5
Figura 3.2. Le istruzioni mfhi e mflo.
3.1
3.2 LA DIVISIONE
LA DIVISIONE TRA DUE NUMERI PUÒ ESSERE EFFETTUATA IN MIPS UTILIZZANDO
L’ISTRUZIONE DIV (DIVIDE) .
Dividendo e divisore sono contenuti in registri da 32 bit e, analogamente a quanto accade per
la moltiplicazione, dopo l’esecuzione dell’istruzione div il risultato della divisione si trova in due
registri Hi e Lo che contengono rispettivamente il resto e il quoziente.
Esempio:
div $2,$3
#effettua la divisione tra il contenuto del registro $2 e il contenuto del
registro $3; il quoziente si trova nel registro Lo, il resto nel registro Hi
(Figura 3.3).
$2
%
$4
/
32 bit
HI
LO
32 bit
Figura 3.3. L’istruzione div.
Il contenuto dei registri Hi e Lo può essere recuperato in modo analogo a quanto abbiamo
visto per la moltiplicazione.
3.3 LE COSTANTI
L’utilizzo delle costanti ricorre spesso nelle operazioni usate comunemente in MIPS [1].
Utilizzando le sole istruzioni che abbiamo incontrato fino ad adesso tutte le volte che
abbiamo bisogno di una piccola costante dovremmo fare un ulteriore accesso alla memoria.
La costante zero viene utilizzata molto frequentemente, quindi ad essa è addirittura riservato
un registro indicato con $zero.
Per evitare ulteriori accessi alla memoria possiamo inserire le costanti direttamente nelle
istruzioni e velocizzare così la loro esecuzione.
Per inserire le costanti all’interno dell’istruzione è necessario utilizzare il formato-I (vedi
capitolo 2); come abbiamo già visto in questo tipo di formato sono disponibili 16 bit per
memorizzare la costante.
Istruzioni che contengono costanti al loro interno vengono dette immediate.
Esempio:
addi $t1, $t1, 5
#addizione immediata tra in contenuto di $t1 e la costante 5.
3.2
A questa istruzione corrisponde il seguente codice macchina:
op
rs
rt
immediate
8
9
9
5
6 bit
5 bit
5 bit
16 bit
001000
01001
01001 0000 0000 0000 0101
Figura 3.4. Esempio di istruzione addi.
Altri esempi di istruzioni immediate sono:
slti $t0, $s0, 3
andi $s0, $s1, 10
ori $s0, $s1, 4
#confronto tra il contenuto di $s0 e la costante 3;
#and logico tra $s1 e 10;
#or logico tra $s1 e 4.
Se le costanti assumono valori elevati e non possono essere rappresentate su 16 bit possiamo
caricare costanti di 32 bit nei registri utilizzando la combinazione di due diverse istruzioni.
Esempio:
Vogliamo caricare la costante 521.499 corrispondente al
00000000000001111111010100011011. Usiamo le istruzioni lui e ori:
codice
lui $t0, 0000 0000 0000 0111
binario
#load upper immediate carica i 16 bit più significativi
della costante nel registro $t0 e mette a zero i 16
bit meno significativi;
ori $t0, 1111 0101 0001 1011 #or immediate permette di caricare i 16 bit meno
significativi della costante nel registro lasciando
inalterati i 16 bit più significativi.
$t0 0000 0000 0000 0111 0000 0000 0000 0000 lui
0000 0000 0000 0111 1111 0101 0001 1011
$t0 0000 0000 0000 0111 1111 0101 0001 1011 ori
Figura 3.5. Le istruzioni lui e ori.
3.3
3.4 ISTRUZIONI PER LA MANIPOLAZIONE DI STRINGHE
Nella maggior parte dei calcolatori i caratteri vengono rappresentati su 8 bit (1 byte)
attraverso la codifica ASCII [1]. Poiché l’elaborazione dei caratteri è un’operazione
ricorrente, il MIPS prevede due istruzioni per trattare i byte: lb (load byte) che preleva dalla
memoria un byte e lo salva negli 8 bit meno significativi di un registro, e sb (store byte) che
prende gli 8 bit meno significativi di un registro e li mette in memoria.
Esempio:
Supponiamo di avere in memoria una word di 32 bit memorizzata a partire dall’indirizzo
che si trova in $4; ricordando che una variabile di tipo char occupa 1byte, utilizzando lb
e sb otteniamo:
0
$
1
an
a n-1
a n-2
a n-3
………
…
a1
a0
Figura 3.6. Le istruzioni lb e sb.
Solitamente i caratteri sono raggruppati in stringhe. Una stringa può essere rappresentata in
vari modi:
1) la lunghezza della stringa può essere indicata dal primo elemento;
N
Stringa
2) alla stringa possono essere associate due variabili: una che contiene la stringa stessa, e
una variabile di appoggio che ne contiene la lunghezza;
Stringa
N
3) l’ultimo elemento della stringa può essere un carattere particolare che ne indica la
fine.
Stringa
0
Il linguaggio C utilizza quest’ultimo metodo utilizzando come carattere di fine stringa un byte
di valore 0.
3.4
3.5 ISTRUZIONI LOGICHE
Tabella 3.1. Le istruzioni logiche.
Istruzione
Esempio
Significato
Commento
Formato
and
and $1, $2, $3
$1 = $2 & $3
AND logico
R
or
or $1, $2, $3
$1 = $2 | $3
OR logico
R
xor
xor $1, $2, $3
$1 = $2 ⊕ $3
XOR logico
R
nor
nor $1, $2, $3
$1 = ~($2 | $3) NOR logico
R
and immediate
andi $1, $2, 10
$1 = $2 & 10
AND logico con costante
I
or immediate
ori $1, $2, 10
$1 = $2 | 10
Or logico con constante
I
xor immediate
xori $1, $2, 10
$1 = ~$2 & ~10
XOR logico con costante
I
$1 = $2 << 10
Shift logico sx di una quantità costante
I
$1 = $2 >> 10
Shift logico dx di una quantità costante
Shift aritmetico dx di una quantità
costante
I
Shift logico sx di una quantità variabile
R
Shift logico dx di una quantità variabile
Shiftr aritmetico dx di una quantità
variabile
R
shift left logical sll $1, $2, 10
shift
right
logical
srl $1, $2, 10
shift
right
arithm.
sra $1, $2, 10
$1 = $2 >> 10
shift left logical sllv $1, $2, $3 $1 = $2 << $3
shift
right
logical
srlv $1, $2, $3 $1 = $2 >> $3
shift
right
arithm.
srav $1, $2, $3 $1 = $2 >> $3
I
R
Lo shift logico (destro/sinistro) coinvolge tutti i bit, mentre lo shift aritmetico lascia
inalterato il bit più significativo.
Esempio:
srl $1, $1, 1
0
$
1
an
#effettua lo shift logico a destra (1 posizione) del contenuto del
registro $1; il bit meno significativo si perde.
a n-1
a n-2
a n-3
………
…
a1
a0
Figura 3.7. Funzionamento dello shift logico a destra.
In generale shiftando verso destra di n posizioni, vengono persi gli n bit meno significativi.
Esempio:
sll $1,$1,1
#effettua lo shift logico a sinistra (1posizione) del contenuto del
registro $1; il bit più significativo viene perso.
3.5
$
1
an
a n-1
a n-2
a n-3
………
…
a1
0
a0
Figura 3.8. Funzionamento dello shift logico a sinistra.
Effettuando lo shift logico a sinistra di n posizioni, gli n meno significativi vengono posti a
zero, mentre gli n bit più significativi si perdono.
Esempio:
sra $1,$1,1
$
1
an
#effettua lo shift aritmetico a destra (1 posizione) del contenuto del
registro $1, lasciando inalterato il bit più significativo; anche in questo
caso il bit meno significativo si perde.
a n-1
a n-2
a n-3
………
…
a1
a0
Figura 3.9. Funzionamento dello shift aritmetico a destra.
Lo shift aritmetico a sinistra non viene utilizzato, perchè coincide con lo shift logico (sll).
3.6 ISTRUZIONI CHE TRADUCONO I PUNTATORI DEL C
Analizziamo un semplice programma C che fa uso di puntatori e il codice MIPS corrispondente.
Supponiamo che alle variabili siano associati i seguenti registri: zÆ$7, pÆ$8, cÆ$9.
typedef struct tabletag {
int i;
char *cp, c;
}table;
table z[20] ;
table *p ;
char c ;
p= &z[4]
..............
..............
c= p->c ;
..............
Il programma C definisce la struttura " table" i cui
campi
sono: un intero (32 bit), un puntatore a char (32 bit) e
un char (8 bit).
Un elemento di tipo table occupa quindi in totale 9
byte.
addi $1, $7, 36 #calcola l’indirizzo di z[4] (base + offset)
sw $1, 0($8) #salva l’indirizzo calcolato
lb $2, 8($8) #preleva il campo c (1 byte) dalla struttura puntata da p
sb $2, 0($9) #salva il campo prelevato nel registro corrispondente a c
3.6
3.7 MODI DI INDIRIZZAMENTO MIPS
I diversi modi con cui si può esprimere un indirizzo di memoria vengono chiamati modi di
indirizzamento [1][7]. Elenchiamo di seguito i modi di indirizzamento utilizzati in MIPS.
Nel seguito indicheremo con A l’indirizzo effettivo di memoria a cui ci riferiamo poiché, come
vedremo, questo può essere diverso dall’indirizzo che troviamo nell’istruzione.
Il modo in cui A viene calcolato differenzia un modo di indirizzamento dall’altro.
3.7.1 Register Addressing
Nel Register Addressing (indirizzamento tramite registro) gli operandi si trovano nei
registri; questo modo di indirizzamento è usato dalle istruzioni in formato-R.
L’indirizzo effettivo è dato da:
A = $(rs)
dove rs indica il numero del registro all’interno del set di registri.
op
rs
rt
rd
shamt
funct
Registro
Figura 3.10. Register addressing.
Istruzioni:
add, addu, sub, subu, mult, multu, div, divu, and, or, sll, slt, sltu, mfhi, mflo, jr.
3.7.2 Immediate Addressing
Nel modo di indirizzamento immediato l’operando è una costante specificata nell’istruzione,
come abbiamo già visto nel paragrafo dedicato alle costanti.
Il modo di indirizzamento immediato è utilizzato nelle istruzioni in formato-I.
In questo caso l’indirizzo effettivo sarà quindi uguale a quello scritto nel campo address
dell’istruzione in formato-I:
A = I (dove I è il contenuto del campo address)
op
rs
rt
immediate
Figura 3.11. Immediate addressing.
Istruzioni:
addi, addiu, andi, ori, slti, sltiu, lui.
3.7.3 Base Addressing
Nell’indirizzamento tramite base, l’operando si trova in una locazione di memoria individuata
dalla somma del contenuto di un registro e di una costante specificata nell’istruzione.
L’indirizzo effettivo è:
3.7
A = I + <contenuto del registro indicato in rs>
Il registro da utilizzare viene indicato nell’istruzione stessa.
Questo modo di indirizzamento è usato per esempio nelle istruzioni lw e sw, in cui l’indirizzo
degli operandi è dato dalla somma tra l’indirizzo di base e l’offset; per esempio lw $t0
32($s2) indica che l’indirizzo di base è contenuto nel registro $s2 e l’offset da sommare è 32
(contenuto nel campo address). L’offset è rappresentato in complemento a due (e varia tra –
32.768 e 32767).
op
rs
rt
Memoria
address
+
Registro
byte
halfword
word
Figura 3.12. Base addressing.
Istruzioni:
lw, lh, lb, lbu, sw, sh, sb.
3.7.4 PC-relative Addressing (base + index)
Il modo di indirizzamento relativo al program counter è stato introdotto per ovviare ad un
problema relativo all’ istruzione di salto condizionato (Branch).
L’istruzione Branch fa uso del formato-I, riservando quindi 16 bit al campo indirizzo.
In base a questa considerazione nessun programma potrebbe avere una lunghezza superiore a
216 poiché in caso contrario l’indirizzo a cui si deve “saltare” nella Branch non potrebbe essere
contenuto nel campo di 16 bit.
Poiché questa condizione è molto restrittiva possiamo usare un’alternativa che consiste
nell’utilizzo di un registro il cui valore deve essere sommato all’indirizzo specificato
nell’istruzione di salto.
A questo scopo viene utilizzato il registro PC (program counter) che contiene l’indirizzo
dell’istruzione corrente. Solitamente nei salti condizionati ci si sposta ad istruzioni vicine;
utilizzando il PC siamo in grado di spostarci fino ad una distanza di ±215 istruzioni rispetto
all’istruzione corrente.
Solitamente si fa in modo che il PC punti all’istruzione successiva a quella in esecuzione;
ricordando che le istruzioni sono tutte a 32 bit e quindi si trovano in memoria ad indirizzi
sempre multipli di 4, l’istruzione successiva a quella in esecuzione si trova all’indirizzo PC+4.
Quindi l’indirizzo effettivo di salto sarà dato da:
A = PC + 4 + I’ = PC + 4 + 4*I
dove I è il contenuto del campo address dell’istruzione (che può essere positivo o negativo) e
rappresenta la distanza dell’indirizzo a cui si deve saltare in termini di word. L’indirizzo PCrelative indica cioè il numero di parole che separano l’istruzione successiva a quella corrente
3.8
dall’istruzione a cui si salta. Nell’espressione I’ rappresenta questa stessa distanza in termini
di byte.
Poiché I è rappresentato su 16 bit, I’ sarà rappresentato su 18 bit, quindi il campo effettivo in
cui possiamo spostarci sarà 218.
op
rs
rt
Memoria
address
x4
+
PC + 4
Word
Figura 3.13. PC-relative addressing.
Istruzioni:
beq, bne.
3.7.5 Pseudodirect Addressing
Se dobbiamo effettuare salti su un campo più ampio di 218 possiamo utilizzare l’istruzione
Jump.
Consideriamo quindi l’istruzione di salto non condizionato Jump: essa utilizza il formato-J in
cui 6 bit vengono impiegati per il codice operativo e i restanti 26 per il campo indirizzo.
Il modo di indirizzamento pseudo-diretto viene utilizzato per impiegare al meglio i 26 bit
riservati all’indirizzo.
In questo caso l’indirizzo effettivo di salto è dato da:
A = (PC<31…28> , 0<27...0>) + I’ = (PC<31…28> , 0<27...0>) + 4*I
Anche in questo caso il campo address viene in pratica esteso di 2 bit, poiché I’ è
rappresentato su 28 bit. L’ampiezza massima del salto sarà quindi 228.
L’indirizzo effettivo viene ottenuto concatenando I’ con i 4 bit più significativi del program
counter.
op
00
address
Memoria
28 bit
31…2
8
32 bit
Memoria
Word
PC
4 bit
Figura 3.14. Pseudodirect addressing.
Istruzioni:
j,jal.
3.9
3.8 LINGUAGGIO ASSEMBLY E LINGUAGGIO MACCHINA
Il linguaggio Assembly fornisce una rappresentazione simbolica della codifica binaria usata dal
calcolatore, detta linguaggio macchina; l’Assembly è più semplice da leggere e da scrivere
rispetto al linguaggio macchina poiché utilizza simboli invece di bit [reference 1].
Inoltre l’Assembly permette di utilizzare pseudoistruzioni, ovvero istruzioni che non fanno
propriamente parte del linguaggio simbolico, ma che facilitano la scrittura dei programmi.
Come abbiamo visto nel corso della trattazione, vi sono differenze tra le istruzioni scritte in
Assembly e le corrispondenti stringhe di bit in linguaggio macchina.
Esempio:
Analizziamo l’istruzione add e il relativo codice macchina.
add $t2, $t0, $t1
op
rs
rt
rd
shamt
funct
000000
01000
01001
01010
00000
100000
0
8
9
10
0
32
Figura 3.15. Codice macchina relativo all’istruzione add.
In Assembly la destinazione è il primo operando, mentre in linguaggio macchina si trova
nel quarto campo; a livello di linguaggio macchina l’istruzione add viene rappresentata con
il codice operativo 0 e il codice funzione 32.
Un vantaggio che l’Assembly offre rispetto ai linguaggi di alto livello è la possibilità di
velocizzare l’esecuzione e minimizzare l’occupazione di memoria dei programmi. In quest’ottica
di ottimizzazione delle prestazioni è quindi importante sapere quali sono le istruzioni “reali”
che si nascondono dietro al linguaggio simbolico Assembly.
3.9 CONVENZIONI PER I REGISTRI MIPS
Tabella 3.2. I registri MIPS.
0
1
zero
at
costante 0
riservata all'assemblatore
2
3
v0
v1
valori dei risultati e
valutazione di espressioni
4
5
a0
a1
parametri utilizzati nella
chiamata delle procedure
6
7
a2
a3
8
t0
variabili temporanee
…
15
t7
3.10
16
s0
variabili salvate quando
…
23
s7
24
25
t8
t9
altre variabili temporanee
26
27
k0
k1
riservati al sistema operativo
28
29
30
gp
sp
global pointer
stack pointer
fp
frame pointer
31
ra
indirizzo di ritorno
si richiamano procedure
3.10 INTRODUZIONE ALLA CHIAMATA A FUNZIONE
Una procedura è una parte del programma dedicata alla risoluzione di un problema specifico,
uno strumento utilizzato per strutturare i programmi e renderli più facili da comprendere.
Nei successivi paragrafi verrà indicato il protocollo da seguire per implementare la chiamata
ed esecuzione di una funzione.
3.11 STRUTTURA DELLA CHIAMATA E DELL’ESUCUZIONE DI
UNA FUNZIONE
In questo paragrafo sono enunciate le varie fasi di una chiamata a funzione con le
convenzioni e le regole che le contraddistinguono; per ogni fase è riportato a fianco al nome
se questa avviene dal lato chiamante (programma che chiama la procedura) o dal lato chiamato
(procedura chiamata).
3.11.1 Prechiamata (lato chiamante)
Questa prima fase consiste nel preparare i parametri d’ingresso, quelli che servono alla
funzione che verrà successivamente chiamata per poter svolgere le operazioni per cui è stata
progettata.
Per questa operazione il MIPS rende disponibili 4 registri: $a0 - $a3.
In questi 4 registri vengono posti i primi quattro parametri necessari alla funzione; se vi è la
necessità di fornire più di quattro argomenti sarà richiesto l’impiego di uno stack (una coda
del tipo “last in first out”); in particolare per ogni funzione chiamata può essere necessario
allocare una porzione dello stack detto frame, di cui daremo una spiegazione più dettagliata in
seguito nel paragrafo 3.13 “gestione dello stack nell’assembler MIPS”.[1]
3.11.2 Chiamata a funzione (lato chiamante)
Il linguaggio assembly MIPS include un’istruzione apposita per le procedure, che salta ad un
indirizzo e contemporaneamente salva l’indirizzo dell’istruzione successiva nel registro $ra.
L’istruzione in questione è jal (jump and link) che si scrive semplicemente:
jal <etichetta della procedura>
La parte link del nome si riferisce al fatto che viene creato un collegamento (link), che
permette alla procedura di ritornare all’indirizzo corretto nel momento in cui termina; questo
“link” viene memorizzato nell’indirizzo $ra ed è detto indirizzo di ritorno. L’ indirizzo di
ritorno è fondamentale perché la stessa procedura può essere richiamata in più parti del
programma. [1]
Come già descritto nei capitoli precedenti l’indirizzo della prossima istruzione da prelevare è
contenuto in un particolare registro denominato PC (program counter); supponiamo che
l’istruzione puntata da PC sia la jal di Figura 3.16, il valore da salvare in $ra è PC+4 (il valore 4
deriva dal fatto che le istruzioni sono tutte a 32 bit e quindi si trovano in memoria ad indirizzi
sempre multipli di 4).
3.11
Prima dell’esecuzione della jal
Dopo l’esecuzione della jal
indirizzi
indirizzi
“∞”
PC
“∞”
jal proc
jal proc
ra
ra
PC
0
0
Figura 3.16. Meccanismo innescato dall’istruzione jal nella memoria.
3.11.3 Prologo (lato chiamato)
In questa fase si provvede all’allocazione di una parte di memoria che contiene i registri
salvati da una procedura e le variabili locali, questa parte dello stack prende il nome di
activation frame ( o activation record); il software MIPS utilizza un frame pointer per
indirizzare la prima parola del frame di una procedura.(vedere paragrafo 3.13 “gestione dello
stack nell’assembler MIPS”).
Nell’activation frame vanno salvati i registri che il chiamato intende sovrascrivere:
¾ Salvataggio degli argomenti della funzione oltre il quarto; i primi quattro possono
essere memorizzati nei registri $a0 - $a3.
¾ Salvataggio del precedente $ra se la funzione chiama altre funzioni; (vedi
paragrafo 3.14 “procedure annidate”)
¾ Salvataggio dei registri $s0 - $s7 se intendo sovrascriverli perché il chiamante si
aspetta di trovarli intatti.
3.11.4 Corpo (lato chiamato)
Questa fase coinvolge l’esecuzione delle istruzioni che realizzano le funzionalità previste dalla
procedura.
3.11.5 Epilogo (lato chiamato)
Nell’epilogo della funzione vi sono due operazioni fondamentali che devono essere svolte:
¾ Ripristino dei registri di interesse dallo stack: $s0 - $s7, $ra, $fp, $sp.
¾ Se devono essere restituiti dei valori questi devono essere posti nei registri $v0 e
$v1.
3.12
3.11.6 Ritorno al chiamante (lato chiamato)
Il linguaggio assembler MIPS include un’istruzione apposita jr (salto tramite registro) che
permette di ritornare all’indirizzo memorizzato nel registro $ra, cioè permette di tornare
nella procedura chiamante all’indirizzo dell’istruzione successiva a quella di chiamata a
funzione.
Al termine della procedura basterà quindi scrivere:
jr $ra .
Con questa istruzione si restituisce il controllo al chiamante.
Prima dell’esecuzione della jr
Dopo l’esecuzione della jr
indirizzi
indirizzi
“∞”
“∞”
jal proc
ra
jal proc
•••••
(
•••••
PC
•••••
PC
ra
)
•••••
•••••
•••••
Jr $ra
Jr $ra
0
0
Figura 3.17. Meccanismo innescato dall’istruzione jr nella memoria.
3.11.7 Post chiamata (lato chiamante)
Una volta ritornati nella funzione chiamante è possibile utilizzare i risultati forniti dalla
procedura chiamata tramite i registri $v0 e $v1.
3.12 MOTIVI PRINCIPALI DI UTILIZZO DELLO STACK
Come abbiamo visto analizzando le varie fasi di una chiamata a funzione esistono una serie di
situazioni in cui è necessario salvare dei dati in una zona della memoria per evitare che questi
vengano sovrascritti e quindi perduti. Quest’ area della memoria è chiamata stack.
Riassumendo i punti visti fin’ora lo stack deve essere utilizzato nei seguenti casi:
¾ Quando una procedura richiede più registri rispetto ai 4 riservati per il passaggio dei
parametri e ai 2 per la restituzione di valori.
¾ Quando si devono salvare dei registri che una procedura potrebbe modificare, ma che
il programma chiamante ha bisogno di mantenere inalterati.
3.13
¾ Quando si devono salvare delle variabili locali relative ad una procedura.
¾ Quando si devono gestire procedure annidate (procedure che richiamano al loro interno
altre procedure) e procedure ricorsive (procedure che invocano dei cloni di se stesse).
3.13 GESTIONE DELLO STACK NELL’ASSEMBLER MIPS
3.13.1 Descrizione generale dello stack
Lo stack è una coda del tipo “last in first out” e ha bisogno di un puntatore all’indirizzo del
dato introdotto più recentemente, per indicare dove la prossima procedura può memorizzare i
registri da riversare e dove può recuperare i vecchi valori dei registri. Il puntatore dello
stack è aggiornato ogni volta che viene inserito o estratto un valore di un registro; per questo
motivo il software MIPS alloca un altro registro appositamente per lo stack: lo stack pointer,
usato per salvare i registri che servono al programma chiamato. [1]
Lo stack può crescere verso l’alto (grow up) cioè da indirizzi di memoria più bassi a indirizzi di
memoria più alti, oppure verso il basso (grow down) cioè da indirizzi più alti a indirizzi più
bassi, come illustrato in figura 3.18.
Convenzione indirizzi
Grow down
Convenzione
Grow up
indirizzi
“∞”
“∞”
0
0
Figura 3.18. Convenzioni sul verso di crescita degli stack.
Inoltre lo stack si può suddividere in base a cosa punta lo stack pointer: può puntare al
successivo elemento dello stack vuoto (convenzione next empity), oppure può puntare
all’ultimo elemento dello stack pieno (convenzione last full).
3.13.2 Gestione dello stack nel MIPS
Per ragioni storiche nel caso dell’assembler MIPS lo stack adotta la convenzione grow down –
last full, e il registro riservato allo stack pointer è $sp.
Quindi l’inserimento di un dato nello stack avviene decrementando $sp per allocare lo spazio
necessario ed eseguendo l’operazione sw (store word) per inserire il dato; il prelevamento di
un dato dallo stack avviene incrementando $sp per eliminare il dato e riducendo quindi la
dimensione dello stack.
3.14
Esempio:
sw $t0, offset($sp)
lw $t0, offset($sp)
# salvataggio di $t0
# ripristino di $t0
Il segmento dello stack che contiene i registri salvati da una procedura e le variabili locali
prende il nome di activation frame . Il software MIPS utilizza un frame pointer ($fp) per
indirizzare la prima parola del frame di una procedura.
Tutto lo spazio in stack di cui ha bisogno una procedura (activation frame) viene
esplicitamente allocato dal programmatore in una sola volta.
Alla chiamata di una procedura lo spazio nello stack viene allocato sottraendo a $sp il numero
di byte necessari:
Esempio:
sub $sp , $sp, 12
# allocazione di 12 byte nello stack.
Al rientro da una procedura il frame viene rimosso dalla procedura incrementando $sp della
stessa quantità di cui lo si era decrementato alla chiamata:
Esempio:
addi $sp, $sp, 12
# dealloca 12 byte nello stack.
Di seguito è riportato uno schema che illustra la situazione in memoria dopo l’allocazione dello
stack (figura 3.19a) in cui si nota come cresca verso il basso. Nella figura 3.19b si può
osservare come viene utilizzato l’activation frame: il frame pointer ($fp) verrà usato per
l’accesso ai dati contenuti nel frame, lo stack pointer ($sp) è libero di variare e viene
tipicamente usato per inserire nello stack i risultati parziali nel calcolo delle espressioni(se
necessario). Nella figura 3.19c è schematizzata la situazione della memoria dopo che lo stack
è stato deallocato.
3.15
Situazione dopo
l’allocazione
dello stack
indirizzi
MEMORIA
Uso dello spazio
allocato per
l’activation frame
indirizzi
MEMORIA
“∞”
“∞”
fp
Argomento 5
Argomento 6
•••••
Registri
Salvati
Variabili
Salvate
sp
a)
Situazione dopo
la deallocazione
dello stack
indirizzi
MEMORIA
f
r
a
m
e
“∞”
sp
( fp )
Zona utilizzata
per il calcolo di
espressioni
0
b)
0
c)
0
Figura 3.19. Allocazione dello stack e del frame.
3.14 ESEMPIO 1: SOMMA ALGEBRICA
L’esempio in questione si propone di creare una procedura che svolga una somma algebrica; per
comprendere al meglio l’esempio di seguito è riportato il listato in linguaggio C.[1]
Procedura Somma_algebrica
int somma_algebrica (int g, int h, int i, int j)
{
int f;
f = (g + h) - (i + j);
return f;
}
ed ecco il listato MIPS:
#
#
#
#
g,h,i e j associati a $a0, …, $a3;
f associata a $v0
Supponiamo di utilizzare i 3 registi: $s0, $s1, $s2 nel calcolo quindi è necessario salvarne il
contenuto (in stack)
3.16
Somma_algebrica:
addi $sp,$sp,-12
sw $s0, 8($sp)
sw $s1, 4($sp)
sw $s2, 0($sp)
# alloca nello stack lo spazio per i 3 registri
# salvataggio di $s0
# salvataggio di $s1
# salvataggio di $s2
add $s0, $a0, $a1
add $s1, $a2, $a3
sub $s2, $t0, $t1
# $t0 Åg + h
# $t1 Å i + j
# f Å $t0 - $t1
add $v0, $s2, $zero
# restituisce f copiandolo nel reg. di ritorno $v0
# ripristino del vecchio contenuto dei registri estraendolo dallo stack
lw $s2, 0($sp)
# ripristino di $s0
lw $s1, 4($sp)
# ripristino di $t0
lw $s0, 8($sp)
# ripristino di $t1
addi $sp, $sp, 12
# aggiornamento dello stack per eliminare 3 registri
jr $ra
#ritorno al programma chiamante
3.15 PROCEDURE ANNIDATE
All’interno di una procedura ci possono essere delle ulteriori chiamate ad altre procedure o vi
possono essere procedure ricorsive che invocano “cloni di se stesse”; quando queste situazioni
hanno luogo è necessario fare molta attenzione all’uso dei registri. [1]
Si supponga ad esempio che il programma principale chiami la procedura “A” con un parametro
uguale a 3, mettendo il valore 3 nel registro $a0 ed usando l’istruzione jal A. Si supponga poi
che la procedura A chiami la procedura B con l’istruzione jal B passandole il valore 7, anch’esso
posto in $a0; dato che A non ha ancora terminato il proprio compito si verifica un conflitto
nell’uso del registro $a0. In modo analogo c’è un conflitto sull’indirizzo di ritorno dalla
procedura, memorizzato in $ra, perché dopo la chiamata a B esso contiene l’indirizzo di
ritorno a B; se non si adottano misure per prevenire il problema, la procedura A non sarà più in
grado di restituire il controllo al proprio chiamante.
Una soluzione consiste nel salvare nello stack tutti i registri che devono essere preservati. Il
programma chiamante memorizza nello stack i registri argomento ($a0-$a3) o i registri
temporanei ($t0-$t9) di cui ha ancora bisogno dopo la chiamata; il chiamato salva nello stack
il registro di ritorno $ra e gli altri registri ($s0-$s7) che utilizza.
Lo stack pointer $sp è aggiornato per tener conto del numero di registri memorizzati nello
stack; alla fine i registri vengono ripristinati e lo stack riaggiornato.
3.16 ESEMPIO 2: PROCEDURA RICORSIVA PER IL CALCOLO DEL
FATTORIALE
Nell’esempio che segue si mostra una procedura ricorsiva che calcola il fattoriale.[1]
Il segmento di programma può essere descritto attraverso il linguaggio c nel seguente modo:
3.17
int fact (int n)
{
if (n < 1) return (1);
else return(n * fatt (n-1));
}
Il parametro n corrisponde al registro argomento $a0. Il programma inizia con l’etichetta
della procedura, quindi salva nello stack due registri, l’indirizzo di ritorno e $a0:
fatt:
sub
sw
sw
$sp, $sp, 8
$ra, 4($sp)
$a0, 0($sp)
#aggiornamento dello stack per fare spazio a due elementi
#salvataggio del registro di ritorno
#salvataggio del parametro n
La prima volta che la procedura fatt viene chiamata, sw salva un indirizzo interno al
programma chiamante.
Le due istruzioni successive verificano se n è minore di 1, saltando a L1 se n<=1:
slt
beq
$t0, $a0, 1
$t0, $zero, L1
# test per n<1
# se n >=1 salta a L1
se n è minore di 1, fatt restituisce 1 mettendolo in un registro valore: somma 1 a 0 e lo
memorizza in $v0. Quindi ripristina dallo stack i due valori salvati e salta all’indirizzo di
ritorno:
addi
addi
jr
$v0, $zero, 1
$sp, $sp, 8
$ra
# restituisce 1
#aggiornamento dello stack per eliminare due elementi
#ritorno all’istruzione successiva a jal
prima dell’aggiornamento dello stack pointersi sarebbero dovuti ripristinare $a0 e $ra, ma
dato che non cambiano quando n è minore di 1 è possibile saltare queste istruzioni.
Se n non è minore di 1, il parametro n viene decrementato e viene nuovamente chiamata la
procedura fatt passandole tale valore:
L1:
sub
Jal
$a0, $a0, 1
fatt
# n > 1: l’argomento diventa (n-1)
# chiamata a fatt con (n-1)
L’istruzione successiva è quella a cui ritorna la procedura fatt; il vecchio indirizzo di ritorno
ed il vecchio valore del parametro sono ripristinati, oltre ad eseguire l’aggiornamento dello
stack pointer:
lw
lw
addi
$a0, 0($sp) #ritorno da jal: ripristino di n
$ra, 4($sp) #ripristino dell’indirizzo di ritorno
$sp, $sp, 8 #aggiornamento dello stack per eliminare due elementi
3.18
quindi nel registro $v0 viene memorizzato il valore del prodotto del valore corrente per il
vecchio parametro $a0:
mult
$v0, $a0, $v0
# restituzione di n*fatt(n-1)
infine la procedura fatt salta nuovamente all’indirizzo di ritorno:
jr
$ra
#ritorno al programma chiamante
3.17 DIRETTIVE DELL’ ASSEMBLATORE.
I nomi che iniziano con un punto specificano all’assemblatore come tradurre un programma ma
non generano istruzioni macchina. [1][12]
Tabella 3.3. Le direttive dell’assemblatore.
.data [<address>]
.text [<address>]
.globl <symb>
.asciiz <str>
.ascii <str>
.byte <b1>,…,<bn>
.word <w1>,…,<wn>
.float <f1>,…,<fn>
.double <d1>,…,<dn>
.half <h1>,…,<hn>
.space <n>
.align <n>
.extern <sym> [size]
.kdata [<addr>]
.ktext [<addr>]
marca l’inizio di una zona dati; se <address> viene specificato i dati
sono memorizzati a partire da tale indirizzo.
marca l’inizio del codice assembly; se <address> viene specificato i
dati sono memorizzati a partire da tale indirizzo.
dichiara un simbolo <symb> visibile dall’esterno (gli altri sono locali per
default) in modo che possa essere usato da altri file.
mette in memoria la stringa<str>, seguita da un byte ‘0’.
mette in memoria la stringa<str> e non inserisce lo ‘0’ finale.
mette in memoria gli n byte che sono specificati da <b1>,…,<bn>
mette in memoria le n word a 32 bit che sono specificate da
<w1>,..,<wn>
mette in memoria gli n numeri floating point a singola precisione (in
locazioni di memoria contigue).
mette in memoria gli n numeri floating point a doppia precisione (in
locazioni di memoria contigue).
mette in memoria le n quantità a 16 bit (in locazioni di memoria
contigue).
mette in memoria n spazi.
allinea il dato successivo ad un indirizzo multiplo di 2^n.
dichiara che il dato memorizzato all’indirizzo <sym> è un simbolo
globale ed occupa <size> byte di memoria.Questo consente
all’assemblatore di memorizzare il dato in una porzione del segmento
dati a cui si ha accesso tramite il registro $gp.
Marca l’inizio di una zona dati pertinente al kernel. Se è presente il
parametro opzionale <addr>, tali dati sono memorizzati a partire da
tale indirizzo.
Marca l’inizio di una zona contenente il codice del kernel. Se è
presente il parametro opzionale <addr>, tali dati sono memorizzati a
partire da tale indirizzo.
3.19
Esempio di utilizzo di direttive:
# Somma valori in un array
.data
array: .word 1,2,3,4,5,6,7,8,9,10
# dichiarazione array
# (array rappresenta l’indirizzo del primo elemento)
.text
.globl main
main:
li $s0,10
la $s1, array
add $s2, $zero, $zero
add $t2, $zero, $zero
loop: lw $t1, 0($s1)
add $t2, $t1, $t2
addi $s1, $s1, 4
addi $s2, $s2, 1
bne $s2, $s0, loop
# $s0 = 10 (li è una pseudoistruzione)
# load address (la è una pseudoistruzione)
# azzero $s2 contatore ciclo
# azzero $t2 per somma
# accesso all’array
# calcolo somma
# $s1: indice del prossimo elemento dell’array
# incremento $s2 contatore ciclo
# test termine ciclo
3.18 SYSCALL: CHIAMATE A SISTEMA.
Il mips mette a disposizione delle chiamate di sistema (system call) predefinite che
implementano particolari servizi (ad esempio: stampa a video). [1][12]
Ogni system call ha un codice (da caricare in $v0),degli argomenti (opzionali, da caricare in
$a0-$a3)e dei valori di ritorno (opzionali, in $v0).
Per richiedere un servizio ad una syscall occorre:
¾
¾
¾
¾
Caricare il codice della syscall nel registro $v0.
Caricare gli argomenti nei registri $a0-$a3 (quando necessario).
Eseguire syscall.
L’eventuale valore di ritorno è caricato nel registro $v0.
Nella pagina seguente è riportata una tabella che elenca le varie chiamate di sistema con i
rispettivi codici,eventuali argomenti ed eventuali valori di ritorno.
3.20
Tabella 3.4. Elenco delle chiamate a sistema.
Chiamata
Print_int
Cod
1
Argomento
$a0 = intero
Risultato
Print_float
2
$f12= virgola mobile
Print_double
3
$f12= doppia
precisione
Print_string
4
$a0 = stringa
Read_int
5
Intero in $v0
Read_float
6
Virgola mobile in $f0
Read_double
7
Doppia precisione in
$f0
Read_string
8
$a0 = buffer
$a1 = lunghezza
Sbrk
9
$a0 = quantità
Exit
10
Indirizzo in $v0
Esempio:
Operazione svolta dalla chiamata
stampa sulla console il numero intero
che le viene passato come
argomento.
stampa sulla console il numero intero
che le viene passato come
argomento.
stampa sulla console il numero
virgola mobile a doppia precisione
che le viene passato come
argomento.
stampa sulla console la stringa che le
viene passata come argomento
terminandola con il carattere Null.
legge una linea d’ingresso fino al
carattere a capo incluso;i caratteri
che seguono il numero sono ignorati
legge una linea d’ingresso fino al
carattere a capo incluso;i caratteri
che seguono il numero sono ignorati
legge una linea d’ingresso fino al
carattere a capo incluso;i caratteri
che seguono il numero sono ignorati
legge una stringa di caratteri di
lunghezza $a1 da una linea di
ingresso scrivendoli in un buffer
($a0),terminando la stringa con il
carattere Null
restituisce il puntatore (indirizzo)
ad un blocco di memoria
interrompe l’esecuzione del prog.
# Programma che stampa: la risposta è 5
.data
str: .asciiz “la risposta è”
.text
li $v0, 4
# $v0 caricato con codice di print_string
la $a0, str
# $a0 caricato con indirizzo della stringa
syscall
# stampa della stringa
li $v0, 1
# $v0 caricato con codice di print_int
li $a0, 5
# $a0 caricato con intero da stampare
syscall
li $v0, 10
# $v0 caricato con codice della exit
syscall
3.21
3.19 HIGH-LEVEL LANGUAGES
I linguaggi di programmazione possono essere suddivisi in 3 categorie:
• M-Code
• Low-Level
• High-Level
La prima categoria è quella del linguaggio macchina (M-Code sta per Machine Code). Questa
categoria comprende tutti linguaggi direttamente eseguibili dal calcolatore. I codici di questo
genere sono tipicamente costituiti da sequenze numeriche esadecimali o binarie.
La seconda categoria è costituita dai linguaggi di basso livello, di cui fa parte l'Assembly.
Questa categoria di linguaggi fornisce un livello di astrazione più alto rispetto all' M-Code, ma
richiede comunque che il programmatore conosca dettagliatamente l'architettura del
calcolatore.I linguaggi di questo tipo generano applicazioni non-portabili perchè il codice
sorgente è tipicamente molto legato al tipo di elaboratore per il quale viene progettato.
La terza categoria di linguaggi è costituita dai linguaggi di alto livello e comprende gli odierni
linguaggi di programmazione come C/C++/Java e altri. Questi tipi di linguaggi, hanno un livello
di astrazione molto elevato, per cui non richiedono la conoscenza specifica dell'architettura
dell'elaboratore e consentono di interagire con la macchina utilizzando un linguaggio "quasienglish". Naturalmente questo tipo di linguaggio non è direttamente eseguibile dal calcolatore,
per cui è necessario che vi sia un altro programma che esegua la conversione del codice di alto
livello in codice macchina, questa classe di programmi sono i compilatori.
3.20 COMPILATORI E SISTEMI DI COMPILAZIONE
Il processo di conversione di un codice sorgente in un file binario è composto da diverse fasi.
In questa immagine viene illustrata l'organizzazione di un sistema di compilazione:
Compiler
Source Code
Source Code with # substitutions
Preprocessor
Parser
Parse
Tree
Translator
AssemblyCode
MemoryImage
Loader
ExecutableFile
Linker
Object
File
Assembler
Figura 3.20. Struttura di un sistema di compilazione.
Come si può vedere la trasformazione del codice avviene in maniera graduale e comporta
l'impiego di di altri strumenti oltre il compilatore.
Analizziamo più in dettaglio il funzionamento degli strumenti che fanno parte del sistema di
compilazione [1] [2] .
3.20.1
Preprocessor
In C/C++ il preprocessore è lo strumento che gestisce le direttive #. Il preprocessore riceve
in input il codice sorgente e si preoccupa di rimuoverne i commenti e interpretare le direttive
speciali denotate dal carattere #. L'output del preprocessore è costituito dal nostro codice
sorgente opportunamente modificato, in cui ogni direttiva è stata sostituita dal risultato della
sua interpretazione [2]. Questo codice risultante viene poi fornito al compilatore per la vera e
3.22
propria generazione del codice oggetto. Sfruttando le possibilità offerte dal preprocessore, è
possibile rendere dinamico il sorgente di un software, facendo in modo che la compilazione
percorra una strada piuttosto che un'altra.
In C/C++ alcune tra le direttive del preprocessore sono:
#include, #define, #ifndef, #undef, #endif, #else, #elif, #if, #error, #line...
Facciamo qualche esempio:
La direttiva di inclusione #include comunica al preprocessore di includere nel
programma il contenuto di una libreria o di un file header.
La direttiva #define comunica al preprocessore una macro-sostituzione di un nome
simbolico o di una costante.
3.20.2
Compiler
Il compilatore è lo strumento che si occupa della conversione del codice sorgente in codice
Assembly. Per eseguire questa conversione è necessario che il codice sorgente non contenga
errori e quindi il compilatore si occupa anche dell'analisi del codice sorgente [2].
Riportiamo di seguito uno schema della struttura di un compilatore:
Parser
Source Code
Lexical
Analyzer
Translator
Assembly Code
Object Code
Generator
Semantic
Analyzer
Syntax
Analyzer
Code
Optimizer
Parse Tree
Intermediate Code
Generator
Figura 3.21. Struttura interna di un compilatore.
L'Analizzatore Lessicale individua le componenti elementari (token) come gli identificatori, le
stringhe, costanti numeriche, le keywords, gli operatori matematici e così via, e inizializza la
Tabella dei Simboli (Symbol Table).
L'Analizzatore Sintattico raggruppa i token in strutture sintattiche e insieme
all'Analizzatore Semantico controlla che le componenti delle strutture sintattiche soddisfino
le regole all'interno del contesto in cui sono inserite. Al termine dell'analisi verrà generato il
Parse Tree o Albero Sintattico. Il Parse Tree verrà interpretato dal Generatore di Codice
Intermedio, il cui codice a sua volta verrà interpretato dall'ottimizzatore che provvederà a
rimuovere eventuali inefficenze. A questo punto il codice è pronto per essere convertito in
Assembly dall'Object Code Generator. Il codice Assembly prodotto è legato alla architettura
del calcolatore.
Ad esempio proviamo ad esaminare il programma helloworld.c:
3.23
/* Created by Anjuta version 1.2.2 */
/*
This file will not be overwritten */
#include <stdio.h>
int main()
{
printf("Hello world\n");
return (0);
}
In sistemi Unix-like possiamo creare il corrispondente file Assembly con il comando:
cc -S main.c
che produce in output il file main.s:
.global
.global
.global
.global
.global
.global
_exit
_open
_close
_read
_write
_printf
LC0:
.ascii "Hello world\12\0"
.align 4
.global _main
_main:
;; Initialize Stack Pointer
add r14,r0,r0
lhi r14, ((memSize-4)>>16)&0xffff
addui r14, r14, ((memSize-4)&0xffff)
;; Save the old frame pointer
sw -4(r14),r30
;; Save the return address
sw -8(r14),r31
;; Establish new frame pointer
add r30,r0,r14
;; Adjust Stack Pointer
add r14,r14,#-16
;; Save Registers
sw 0(r14),r3
sub r14,r14,#8
lhi r3,(LC0>>16)&0xffff
addui r3,r3,(LC0&0xffff)
sw 0(r14),r3
jal _printf
nop
addi r1,r0,#0
add r14,r14,#8
j L1
nop
L1:
;; Restore the saved registers
lw r3,-16(r30)
nop
;; Restore return address
lw r31,-8(r30)
nop
;; Restore stack pointer
add r14,r0,r30
;; Restore frame pointer
lw r30,-4(r30)
3.24
nop
;; HALT
jal _exit
nop
_exit:
trap #0
jr r31
nop
_open:
trap #1
jr r31
nop
_close:
trap #2
jr r31
nop
_read:
trap #3
jr r31
nop
_write:
trap #4
jr r31
nop
_printf:
trap #5
jr r31
nop
.global ___gnuc_va_list
___gnuc_va_list: .space 8
Questo file sarà poi inviato all'assemblatore che si incaricherà di produrre il corrispondente
object file.
3.20.3
Assembler
Il codice Assembly prodotto in output dal compilatore viene inviato all'assemblatore, che
provvederà a tradurlo in codice macchina (object code).
Un assemblatore è un programma che processa un file sorgente in codice Assembly mappando
direttamente ogni istruzione in codice macchina (ovvero ogni istruzione Assembly viene
trasformata in una istruzione in codice macchina eseguibile dal calcolatore). Per effettuare la
mappatura del codice Assembly in codice macchina l'assemblatore effettua due passate [3] .
Durante la prima passata l'assemblatore esegue le seguenti operazioni:
• Controlla che le espressioni Assembly siano corrette.
• Alloca spazio per le istruzioni e per il salvataggio in memoria che il codice richiede.
• Assegna i valori alle costanti (se definite).
• Costruisce una Symbol Table (o Cross Reference Table).
La Symbol Table identifica i simboli che il compilatore non è riuscito a ‘risolvere’, cioè quelli
di cui non sa ancora il valore perché tale valore dipende dal resto dell’eseguibile finale.
Ci sono due tipi di simboli:
• Definiti nel file ma utilizzati altrove (esportati).
3.25
Ad esempio le funzioni che definiamo all'interno di un file ma che utilizziamo in altri moduli
del programma.
Per i simboli esportati la Symbol Table contiene: nome e indirizzo locale.
Tabella 3.5. Simboli esportati.
Simboli esportati
Nome
•
Indirizzo locale
Definiti altrove ma utilizzati nel file (esterni).
Ad esempio le funzioni importate come printf, scanf (e tutte quelle delle librerie che
utilizziamo all'interno del nostro programma).
Per i simboli esterni la Symbol Table contiene: nome e indirizzo delle istruzioni che vi fanno
riferimento.
Tabella 3.6. Simboli esterni.
Nome
Simboli esterni
Indirizzo delle istruzioni
che vi fanno riferimento
Come viene costruita la Symbol Table.
L'assemblatore legge il file sorgente una riga alla volta (ogni riga corrisponde ad una
istruzione), ed esamina se la label-field contiene un simbolo valido per una label.
Se il simbolo trovato viene ritenuto valido ed è la prima volta che si utilizza il simbolo come
label, l'assemblatore lo aggiungerà nella tabella dei simboli e gli assegnerà il valore corrente
del contatore di locazione. Se invece si verifica che il simbolo era già stato usato come label,
allora l'assemblatore riporterà il messaggio di errore Redefinition of symbol e sovrascriverà
il valore del simbolo con il valore corrente del contatore di locazione [3].
Durante la seconda passata vengono eseguite le seguenti operazioni:
• Gli op-code vengono tradotti in sequenze binarie usando una apposita Op-code Table.
• Gli indirizzi simbolici vengono tradotti in sequenze binarie usando la Symbol Table.
• Si predispone la Tabella di Rilocazione (Relocation Table).
La tabella di rilocazione identifica le parti del testo che si riferiscono ad indirizzi assoluti di
memoria (ad esempio dei jump assoluti o dei riferimenti assoluti all'area dei dati globali).
Questi indirizzi devono essere rilocati nell'eseguibile finale a seconda della posizione della
prima istruzione del testo (offset), l'offset verrà sommato ad ogni indirizzo rilocabile.
Struttura di un object file:
3.26
Header:
dimensione e posizione del file e
dei suoi moduli
Text Segment:
contiene il codice macchina
Data Segment:
dati statici generati con il
programma
Relocation Info:
identifica dati e istruzioni che
dipendono da indirizzi assoluti
quando il programma è caricato in
memoria
Symbol Table:
tabella dei simboli locali ed
etichette visibili all'esterno
Debugging Info:
parte opzionale per il debugging
del programma
Figura 3.22. Struttura di un object file.
In sistemi Unix-like possiamo avere informazioni sull'object file utilizzando il comando:
nm file.o
Ad esempio, per il programma helloworld.c:
nm main.o
Symbol Value
00000000
Symbol Type
Symbol Name
T
main
U
printf
"T" The symbol is in the text (code) section.
"U" The symbol is undefined.
3.20.4
Linker
Le librerie o i file esterni a cui un programma fa riferimento vengono compilati a parte come
moduli separati e solo successivamente vengono collegati al programma principale in maniera
da risolverne le dipendenze. Quindi, se il file sorgente contiene riferimenti a funzioni definite
in librerie o in file esterni, queste librerie e questi file esterni dovranno essere collegati al
file che le utilizza [4] [5] [6]. Il linker permette di collegare più moduli oggetto assemblati
separatemente in modo da costituire un unico modulo esegibile finale.
3.27
Figura 3.23. Schema del linking di un programma.
Ogni modulo oggetto generato dall'assemblatore contiene tutte le informazioni necessarie al
linker per operare il collegamento. In particolare vengono forniti i simboli pubblici (ad esempio
le procedure esportate nel modulo), ed i riferimenti esterni (ad esempio le chiamate a
procedure definite in altri moduli).
Il linker deve risolvere tre problemi:
1. Cercare nelle librerie le funzioni utilizzate dal programma.
2. Determinare le locazioni di memoria che ciascun modulo occuperà e rilocare le
istruzioni aggiornando i riferimenti assoluti.
3. Risolvere i riferimenti tra i file che compongono il programma.
Passi compiuti dal linker:
1. Costruisce una tabella contenente la lunghezza dei moduli oggetto.
2. In base alla tabella assegna un indirizzo di caricamento ad ogni modulo oggetto.
3. Aggiorna tutti gli indirizzi rilocabili.
4. Aggiorna tutti i riferimenti a procedure esterne.
Per realizzare le operazioni 3 e 4 il linker sfrutta le informazioni prodotte dall'assemblatore
e contenute nei moduli oggetto. Vengono costruite due tabelle generali che raccolgono i
simboli pubblici ed i riferimenti esterni di tutti i moduli [5].
Il linker produce un file eseguibile dello stesso formato dell'object file, ma con la differenza
che non contiene informazioni per la rilocazione degli indirizzi, visto che gli indirizzi sono già
stati tutti risolti, cioè sono già stati assegnati indirizzi definitivi [5].
3.28
Startup
Object file
Object files
(*.o)
Library
(*.a)
Symbol
Table
Linker
Relocation
Infos
Binary File
(executable)
Figura 3.24. Schema di funzionamento del linker.
3.20.5
Loader
Ogni volta che viene eseguito un programma il loader compie le seguenti operazioni [1]:
1. Legge l'header del file eseguibile per determinare la dimensione del Data Segment e del
Text Segment.
2. Alloca uno spazio di indirizzamento in memoria.
3. Copia le istruzioni e i dati del file eseguibile nel nuovo spazio di indirizzamento.
4. Copia i parametri passati al programma principale nello stack.
5. Inizializza i registri e assegna allo stack pointer l'indirizzo della prima cella libera.
6. Copia gli argomenti del programma principale nei registri e chiama il main.
3.21 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] Sito web: http://ou800doc.caldera.com/en/SDK_cprog/CCCS_CompCCompilationSys.html
[3] Sito web: http://www.nersc.gov/vendor_docs/ibm/asm/alangref02.htm
[4] Sito web: http://acm.uiuc.edu/sigmil/RevEng/index.html
[5] F. Tortorella, "Corso di Calcolatori Elettronici", Università degli studi di Cassino,
Link: webuser.unicas.it/tortorella/
[6] S. Salza, G. Santucci, "Calcolatori Elettronici", Università di Roma "La Sapienza",
Sito web: http://www.dis.uniroma1.it/~salza/teaching.htm
[7] Riccardo Solmi, “Assembly1”, Link: http://www.cs.unibo.it/~solmi/
3.29
LEZIONE 4
Valutazione delle prestazioni (parte prima)
4.1 INTRODUZIONE
4.1.1 Definizione di prestazione
Ci preoccupiamo, per prima cosa, di capire cosa s’intende quando si afferma che “un dato
calcolatore è più veloce di un altro”. Si potrebbe pensare a due significati diversi:
- ad esempio, un generico utente potrebbe dire che un calcolatore è più veloce di un
altro quando esegue un programma in minor tempo rispetto ad un altro;
- al contrario, il gestore di un centro informatico potrebbe dire che il calcolatore più
veloce è quello che, a parità d’intervallo di tempo a disposizione (ad esempio un’ora),
completa il maggior numero di lavori (jobs).
La differenza tra i due “significati” è dunque riscontrabile nel parametro usato per giudicare
la velocità di un calcolatore.
Prendiamo in considerazione un esempio per chiarire la situazione.
Velivolo
DC to Paris
Velocità
Passeggeri
Boeing 747
Concorde
6.5 hours
3 hours
610 mph
1350 mph
470
132
Throughput
(pmph)
286.000
178.200
Considerando la tabella sovrastante, quale dei due velivoli ha migliori prestazioni?
Prendendo in esame la velocità, potremmo dire che il Concorde ha migliori prestazioni del
Boeing 747, ma non sarebbe errato affermare che, per quanto riguarda la portata, il Boeing
747 ha migliori prestazioni del Concorde.
Com’è ormai chiaro, risulta indispensabile chiarire la tipologia d’utente ed i relativi parametri
da essi utilizzati per giudicare le suddette prestazioni:
- l’utente generico è soprattutto interessato a minimizzare il response time (tempo di
risposta), ossia il tempo che intercorre tra l’inizio e il completamento di un evento. Si
parla anche di tempo di esecuzione (execution time) oppure di latenza (latency);
Esempi di tempo di risposta:
- Durata dell’esecuzione del mio programma.
- Attesa per l’accesso ad un sito web.
-
al contrario, il gestore del centro informatico è interessato essenzialmente a
massimizzare il throughput del calcolatore, ossia la quantità complessiva di lavoro
svolto in un dato intervallo di tempo. Si parla anche di banda passante (bandwidth) del
calcolatore (da non confondere con l’ampiezza di banda dei mezzi trasmissivi).
Esempi di throughput:
- Numero di programmi eseguiti nell’unità di tempo.
- Numero di lavori (job, transizioni, interrogazioni a basi di dati) effettuati
nell’unità di tempo.
- Numero di programmi eseguibili da una macchina contemporaneamente.
Si può del resto intuire che, almeno in condizioni normali, un tempo di risposta più basso
equivale ad un aumento del throughput: quanto più il sistema è rapido nell’eseguire i singoli
compiti, tanti più compiti potrà portare a termine in un dato intervallo di tempo.
4.1
Per comprendere meglio i concetti appena citati, possiamo fare un semplice esempio. Esistono
diversi modi per migliorare le prestazioni di un calcolatore; ad esempio tre di questi sono i
seguenti:
1. periodo di clock più breve (cioè clock più veloce);
2. processori multipli per compiti diversi;
3. elaborazione parallela per problemi scientifici.
Il primo ed il terzo metodo sicuramente garantiscono tempi di risposta più brevi: ad esempio,
se l’esecuzione di un dato programma richiede N cicli di clock e ogni ciclo dura τ secondi, il
tempo totale di esecuzione è N·τ, che sarà tanto più piccolo quanto più è piccolo τ. Quindi, il
primo ed il terzo metodo contribuiscono anche ad aumentare il throughput complessivo. Il
secondo metodo, invece, non comporta alcun miglioramento sulla velocità con cui viene eseguito
il singolo compito, ma consente di svolgere più compiti contemporaneamente, per cui serve ad
incrementare solo il throughput.
Esempio 1:
I seguenti cambiamenti apportati ad un sistema di calcolo aumentano il throughput,
diminuiscono il tempo di risposta o entrambe le cose?
1. Sostituire il processore all’interno del calcolatore con una versione più veloce.
2. Aggiungere processori addizionali ad un sistema che usa processori multipli per
compiti separati, ad esempio per gestire il sistema di prenotazioni di una linea aerea.
Soluzione
Diminuire il tempo di risposta aumenta quasi sempre il throughput. Quindi nel caso 1
migliorano sia il tempo di risposta che il throughput. Nel caso 2 nessun compito viene
eseguito più velocemente e di conseguenza aumenta soltanto il throughput. Se però nel
secondo caso il numero di elaborazioni richieste fosse quasi elevato come il throughput,
il sistema potrebbe essere costretto a mettere in coda le richieste. In questo caso
aumentare il throughput può anche diminuire il tempo di risposta, dal momento che si
riduce il tempo di attesa nella coda. Così, in molti sistemi di calcolo reale modificare il
tempo di esecuzione influenza il throughput e viceversa.
4.2 MISURE STATISTICHE
Talvolta, le misure delle prestazioni di un calcolatore sono descritte meglio da distribuzioni
di probabilità che non da valori deterministici. Ad esempio, consideriamo il tempo di risposta
necessario per completare una data operazione di I/O sul disco rigido del calcolatore: tale
tempo di risposta dipende sia da fattori deterministici (ad esempio dalla quantità di dati da
leggere e da scrivere su disco) sia da fattori non deterministici (ad esempio da ciò che il disco
sta eventualmente già facendo quando viene richiesto l’I/O oppure dalla quantità di altri
processi in attesa di accedere allo stesso disco). Data la presenza di tali fattori non
deterministici, ha più senso parlare di tempo medio di risposta per un I/O da disco. In modo
analogo, anche l’effettivo throughput di disco (cioè il numero di dati che transitano realmente
da o verso il disco nell’unità di tempo) non risulta essere un valore costante. Nel seguito,
comunque, tratteremo il tempo di risposta ed il throughput come valori deterministici.
4.3 LE PRESTAZIONI
Per i progettisti di calcolatori, così come ad esempio per quelli di automobili, non esiste un
unico obiettivo da raggiungere:
4.2
-
-
ad un estremo, troviamo i progetti ad elevate prestazioni, che non badano a spese
pur di raggiungere il proprio obiettivo (lo stesso accade, ad esempio, per la
Ferrari);
all’estremo opposto troviamo invece i progetti a basso costo, dove le prestazioni
vengono sacrificate per raggiungere costi contenuti;
una via di mezzo è costituita dai cosiddetti progetti costo/prestazioni, nei quali il
progettista effettua un bilancio appunto tra costi e prestazioni.
4.3.1 Concetti generali
Il tempo è la misura delle prestazioni di un calcolatore: questo significa, in parole povere, che
il calcolatore che svolge la stessa quantità di lavoro impiegando il minor tempo è il più veloce.
Due esempi banali sono i seguenti:
-
il tempo di esecuzione di un programma si misura in secondi per programma;
la prestazione viene spesso misurata come il tasso di un certo numero di eventi per
secondo, in modo tale che bassi tempi significano prestazioni più elevate.
Il tempo può del resto essere definito in vari modi, a seconda di come lo si conteggia; volendo
usare la sua definizione più semplice, parliamo di tempo assoluto o tempo di risposta (elapsed
time):
esso rappresenta la latenza per il completamento di un dato lavoro, includendo
tutti i fattori necessari per svolgere tale lavoro,
come ad esempio gli accessi al disco, gli accessi alla memoria, le attività di I/O, il sovraccarico
dovuto al sistema operativo e altro. Tuttavia, quando ci riferiamo ad un ambiente di
multiprogrammazione, è noto che la CPU si dedica contemporaneamente a più programmi, che
quindi si alternano al suo utilizzo: ad esempio, mentre la CPU attende le operazioni di I/O per
un dato programma, si dedica all’esecuzione di un altro programma; di conseguenza, il tempo
trascorso non necessariamente risulta minimizzato, il che ci costringe a trovare altri termini
più efficaci. Ci viene incontro, allora, il cosiddetto tempo di CPU, il quale coglie la
“distinzione” citata poco fa, non includendo il tempo di attesa per l’I/O ed il tempo per
l’esecuzione di altri programmi. Ovviamente, dal punto di vista dell’utente, il tempo che conta
è il tempo di risposta del programma e non certo il tempo di CPU; quest’ultimo è invece una
buona misura di prestazione per il progettista.
Il tempo di CPU può essere ulteriormente suddiviso in due componenti:
- tempo utente di CPU: è il tempo di CPU dedicato al programma (TU);
- tempo CPU di sistema: è il tempo speso dal sistema operativo per effettuare i
compiti richiesti dal programma (TK).
Quest’ultima distinzione si può apprezzare sperimentalmente tramite un apposito comando
previsto dal sistema operativo Unix: si tratta del comando “time”, il quale restituisce una
sequenza di valori del tipo seguente:
90.7u 12.9s 2:39 65%
Questa sequenza dice che il tempo utente di CPU, per il programma desiderato, è
complessivamente di 90.7 secondi, mentre 12.9 secondi costituiscono il tempo CPU di sistema;
4.3
il tempo impiegato dal programma è di 2 minuti e 39 secondi (cioè 159 secondi), mentre invece
la percentuale di tale tempo rappresentativa del tempo di CPU vale:
90.7 +12.9
×100 = 65%
159
Quindi, poco meno dei 2/3 del tempo totale sono spesi per l’esecuzione del programma in
questione, mentre poco più di 1/3 del tempo è usato per attese legate sia all’I/O sia
all’esecuzione di altri programmi. Segnaliamo, comunque, che molte misure ignorano il tempo di
CPU del sistema, sostanzialmente per due motivi:
1. si è verificato che l’accuratezza con cui il sistema operativo effettua rilievi su se
stesso è generalmente molto bassa;
2. dato che il tempo di CPU di sistema cambia da macchina a macchina (poiché cambia il
codice di sistema), questa misura non è assolutamente valida per confrontare le
prestazioni tra due macchine diverse.
4.3.2 Le prestazioni della CPU
È noto che la maggior parte dei calcolatori viene realizzata usando un clock di frequenza
fissa. Tale clock fornisce dunque degli “eventi” che discretizzano il tempo, questi vengono
chiamati cicli di clock. Si può far riferimento alla durata dei cicli di clock sia mediante la loro
lunghezza, detta periodo di clock (ad esempio 10 ns), sia mediante il suo inverso, ossia la
frequenza di clock (ad esempio 100 MHz). In base a questa distinzione, è possibile esprimere
il tempo di CPU per un programma in due modi perfettamente equivalenti:
CCPU = cicli di clock della CPU
TC = periodo di clock
fC = frequenza di clock
C CPU
TCPU = CCPU·TC =
fC
Oltre al numero dei cicli di clock necessari per eseguire un programma, si può anche contare il
numero di istruzioni eseguite o istruzioni dinamiche, indicato con NCPU. Esso dipende dal set
di istruzioni e dalla qualità dei compilatori (per qualità di un compilatore s’intende la sua
capacità di ottimizzare il programma, ad es. eliminando le istruzioni non necessarie). Se il set
di istruzioni contiene istruzioni di tipi semplici avremo un NCPU alto; istruzioni di tipo più
complesso permetteranno invece di abbassare NCPU (con un set di istruzioni di tipo semplice ne
occorrono infatti di più per svolgere le stesse funzioni). Questo numero, unito al numero di
cicli di clock necessari per l’esecuzione, permette a sua volta di calcolare il numero medio di
cicli di clock per istruzione (indicato con CPI ):
CPI =
CCPU
N CPU
Il CPI andrebbe sempre misurato in quanto i costruttori lo forniscono spesso nella
documentazione per tutti i tipi di istruzioni per le quali il tempo di esecuzione varia, per
esempio, in funzione degli operandi, può essere definito il valore minimo o un intervallo di
valori. Se le istruzioni sono di tipo semplice, si avrà un CPI basso, se invece si eseguono
operazioni più complesse si avrà un CPI elevato).
4.4
Esempio 2:
Un progettista di compilatori sta cercando di scegliere tra due sequenze di codici per un
determinato calcolatore. I progettisti hardware hanno fornito le seguenti informazioni:
CPI A =1; CPI B=2; CPI C =3.
Una particolare istruzione di un linguaggio ad alto livello può essere compilata ricorrendo
a due diverse sequenze di codici, le quali richiedono le seguenti combinazioni di
istruzioni:
Compilatore
CC1
CC2
1.
2.
3.
Istr. tipo A
Istr. tipo B
2
4
1
1
Istr. tipo C
2
1
Quale sequenza di codici causa l’esecuzione del maggior numero di istruzioni?
Quale viene eseguita più velocemente?
Qual è il CPI per ciascuna delle sequenze?
Soluzione
NCPU(2)= 4+1+1= 6
1.
NCPU(1)= 2+1+2=5
Il compilatore 1 produce il codice più corto.
2. CCPU(1)=(2*1)+(1*2)+(2*3)=10 cicli
CCPU(2)=(4*1)+(1*2)+(1*3)= 9 cicli
Il compilatore 2 produce il codice più veloce!
C (1) 10
=
=2
CPI (1) = CPU
3.
N CPU (1) 5
CPI (2) =
CCPU (2) 9
= = 1.5
N CPU (2) 6
Il compilatore 2 produce il codice con il miglior CPI .
Dalla formula della CPI , vista in precedenza, possiamo ricavare due espressioni alternative
per il tempo di CPU: portando infatti a numeratore il numero di istruzioni eseguite e
riprendendo le precedenti formule per il calcolo del tempo di CPU, otteniamo che:
TCPU = N CPU × CPI× TC =
N CPU × CPI
fC
Dalla precedente serie di equazioni possiamo dunque affermare che le componenti principali
per la valutazione delle prestazioni, con le relative unità di misura, sono:
Componenti delle prestazioni
NCPU
Numero istruzioni
CPI
Cicli di clock per istruzione
TC
Durata del ciclo di clock
Unità di misura
Istruzioni eseguite
Numero medio di
cicli di clock per istruzione
Secondi per ciclo di clock
Osserviamo dunque che la prestazione della CPU dipende da tre caratteristiche:
- il periodo di clock (o la frequenza di clock);
4.5
i cicli di clock per istruzione ( CPI );
il numero di istruzioni (NCPU).
Non è possibile modificare separatamente una di queste tre caratteristiche, poiché le
tecnologie di base che le riguardano singolarmente sono interdipendenti tra loro:
- il periodo di clock dipende sia dalla tecnologia dell’hardware sia dall’organizzazione;
-
il CPI dipende a sua volta dall’organizzazione ed anche dall’architettura dell’insieme
di istruzioni;
- il NCPU dipende infine dall’architettura dell’insieme di istruzioni nonché dalla
tecnologia dei compilatori.
Talvolta, risulta anche utile calcolare il numero totale dei cicli di clock della CPU ripartendo il
numero medio di cicli di clock per ciascuna istruzione di programma:
-
N
(
CCPU = Σ CPIi × Ii
i=1
)
In questa formula, il pedice i indica l’i-esima istruzione dinamicamente eseguita dal
programma in questione, Ii indica il numero di volte che tale istruzione viene eseguita in un
programma, mentre CPIi indica il numero medio di cicli di clock per eseguire l’istruzione i.
Moltiplicando questa formula per il periodo di clock, si ottiene evidentemente ancora una volta
il tempo di CPU:
N
(
)
TCPU = Σ CPIi × Ii × TC
i=1
Se invece dividiamo per il numero di istruzioni, otteniamo il CPI :
N
CPI =
CCPU
=
N CPU
Σ (CPI × I )
i
i=1
i
N CPU
In generale, si ricordi, che la misura reale delle prestazioni è sempre il tempo, anche perché si
tratta dell’unica grandezza facilmente misurabile.
Esempio 3:
Un dato programma viene eseguito in 10 secondi dal calcolatore A, dotato di un clock a
400 MHz. Un progettista deve costruire un calcolatore B in grado di eseguire lo stesso
programma in 6 secondi ed ha concluso che è possibile aumentare in modo significativo la
frequenza di clock; questa modifica avrà però influenza su tutto il progetto della CPU,
facendo sì che il calcolatore B richieda un numero di cicli maggiore di un fattore 1.2
rispetto al calcolatore A. Dovendo dare un consiglio al progettista quale sarà la
frequenza di clock a cui puntare come obiettivo?
Soluzione
Il numero di cicli di clock necessari all’esecuzione del programma su A sono:
CCPU(A)=fC(A)·TCPU(A)=4·109
Da cui la frequenza di clock del calcolatore B:
fC = (CCPU(B) / TCPU(B)) = (1.2 · 4 ·109) / 6 = 800 MHz
4.6
Per ottenere una diminuzione del 40% del tempo di esecuzione è necessario raddoppiare
la frequenza di clock.
Esempio 4:
Siano dati due calcolatori A e B, il primo è dotato di un clock a 1GHz e di un numero
medio di cicli di istruzione pari a 2.5. Il secondo ha una frequenza di clock pari a 500
MHz e un numero medio di cicli di istruzione di 1.2. Tenendo presente che il set di
istruzioni non cambia per entrambi i calcolatori (NCPU(A)=NCPU(B)), qual è il calcolatore
più veloce?
Soluzione
Calcoliamo lo speed up:
SAB =
TCPU (B ) N CPU (B) CPI (B)
=
TCPU ( A) N CPU ( A) CPI ( A)
f C ( A) 1.2 1000
=
= 0.96
f C (B ) 2.5 500
Il rapporto tra le velocità di A e B è di 0.96, facendo il reciproco però, si può capire
meglio che B è più veloce di A del 4%, avendo un SBA (reciproco del rapporto SAB ) di 1.04.
4.4 CONFRONTO DI PRESTAZIONI
Quando si deve scegliere tra diverse alternative di progetto di un calcolatore, è necessario
confrontare le rispettive prestazioni in modo da individuare l’alternativa più adeguata alle
proprie esigenze. Indichiamo allora due macchine prese ad esempio con X ed Y. Diremo
sempre che “X è più veloce di Y” per dire che il tempo di esecuzione, per uno stesso lavoro, è
più basso in X che in Y:
Y
tempo di esecuzione su Y
X
tempo di esecuzione su X
0
50
100
150
200
250
Figura 4.1 Confronto delle Prestazioni.
Da un punto di vista quantitativo, possiamo quantificare la differenza di velocità nel modo
seguente: indicati con EX ed EY i tempi di esecuzione, rispettivamente, di X e di Y, possiamo
scrivere che
EY EX + Δ
Δ
=
= 1+
EX
EX
EX
In questa espressione, Δ rappresenta evidentemente l’incremento del tempo di risposta di Y
rispetto ad X. Se vogliamo adottare invece una misura percentuale allora ci basta scrivere che
EY
n
= 1+
EX
100
4.7
In base a quest’espressione potremmo dire che “X è n% volte più veloce di Y”. Ad esempio, se
n=50, diremo che X è il 50% più veloce di Y, il che significa:
EY
50
= 1+
= 1.5
EX
100
ossia che il tempo di esecuzione di X è 2/3 rispetto a quello di Y, con riferimento, ovviamente,
ad un lavoro specificato.
E’ facile capire che il tempo di esecuzione è il reciproco della prestazione, per cui possiamo
anche scrivere che
1
E
P
P
n
1+
= Y = Y = X
1
100 E X
PY
PX
dove P sta appunto per “prestazione”. In base a quest’espressione, “la prestazione di X è n%
migliore di quella di Y”.
Esiste anche un ulteriore modo di vedere la cosa. Generalmente, confrontando due quantità
diverse tra loro, si considera la differenza tra la quantità maggiore e la quantità minore e la si
divide per la quantità minore (si può anche moltiplicare per 100 per ottener una misura
percentuale). Lo stesso discorso possiamo fare nel nostro caso, ossia possiamo affermare che
l’aumento percentuale n di prestazione di X rispetto a Y equivale alla differenza tra la
prestazione di X e la prestazione di Y, divisa per la prestazione di Y:
n=
PX - P Y
×100
PY
Riarrangiando questa espressione, è facile trovare esattamente la formula precedente
1+
n
P
= X , il che ci dice che i vari modi di esprimersi, sia in termini qualitativi (cioè di
100 PY
definizioni) sia in termini quantitativi (cioè di formule), sono perfettamente equivalenti tra
loro.
Esempio 5:
Supponiamo che una data macchina A esegua un programma in 10 secondi, mentre lo
stesso programma viene eseguito da una macchina B in 15 secondi. E’ evidente che la
macchina A è più veloce della macchina B, dato il tempo di esecuzione più basso.
Soluzione
Per quantificare numericamente l’incremento n di velocità ci basta applicare le formule
citate poco fa:
1+
n
E
15
= Y =
= 1.5
100 E X 10
n=50
Quindi A è più veloce di B del 50%.
4.8
4.5 MIPS (MILIONE DI ISTRUZIONI AL SECONDO)
Una ferma convinzione di molti esperti del settore è quella per cui la sola misura uniforme ed
affidabile delle prestazioni di un calcolatore è il tempo di esecuzione di programmi reali,
ossia programmi di utilizzo comune presso gli utenti. Qualunque proposta alternativa, sia al
parametro temporale sia all’uso di programmi reali come elementi di misura, è ritenuta poco
consona alle esigenze della progettazione dei calcolatori o addirittura foriera di errori nella
progettazione stessa.
Di seguito vogliamo allora esaminare le proposte alternative più diffuse ed i pericoli insiti in
esse.
Una prima importante alternativa all’uso del tempo come metro di prestazione è il cosiddetto
MIPS, che sta per milioni di istruzioni al secondo. Per un dato programma, il MIPS è
semplicemente definito nel modo seguente:
MIPS =
N CPU
fC
6 =
TCPU ×10
CPI×106
Le macchine più veloci sono evidentemente quelle con il MIPS più alto. È subito importante
sottolineare che il MIPS dipende strettamente dal programma usato: infatti, esistono
programmi che sfruttano le parti più complicate di un processore ed altri che invece
sfruttano solo le funzioni più semplici, perciò il CPI è legato a tali programmi e quindi varia da
uno all’altro.
Tra le due forme alternative del MIPS proposte, la seconda è quella più apprezzata, dato che
la frequenza di clock è fissa per una data macchina e il CPI è normalmente un numero piccolo,
a differenza di quanto accade invece per il numero di istruzioni e il tempo di esecuzione.
Portando il tempo di esecuzione al primo membro ed il MIPS a secondo membro, si ottiene:
TCPU =
N CPU
MIPS×106
Questa espressione è utile poiché, essendo il MIPS nient’altro che la frequenza delle
operazioni per unità temporale, le prestazioni delle macchine più veloci (cioè con MIPS più
alto) sono evidenziabili tramite i tempi di esecuzione più bassi.
Il pregio fondamentale del MIPS è che si tratta di una figura di merito facilmente
comprensibile per l’acquirente: quest’ultimo sarà infatti tentato ad acquistare la macchina con
il MIPS più alto. A fronte di questo, però, ci sono da considerare tre problemi fondamentali:
1. il MIPS dipende dall’insieme di istruzioni della macchina, il che rende difficile
confrontare, tramite il MIPS appunto, calcolatori con diversi insiemi di istruzioni;
2. il MIPS può variare, sullo stesso calcolatore, al variare del programma considerato
(perché varia il NCPU e il CPI ): si può perciò avere un MIPS molto alto per un dato
programma ed un MIPS molto più basso per un altro programma;
3. infine, il MIPS può variare in modo inversamente proporzionale alle prestazioni, il che
rappresenta il principale difetto di questo indice di prestazione. Un classico esempio
per evidenziare questo concetto è quello di una macchina che possiede hardware
opzionale per le operazioni in virgola mobile: in condizioni normali, le operazioni in
4.9
virgola mobile hanno bisogno di più cicli di clock rispetto a quelle che lavorano con
numeri interi; di conseguenza i programmi usano, per la virgola mobile, non le apposite
routine software, ma l’hardware opzionale, il che garantisce tempi minori di esecuzione
ma anche un MIPS più basso (vengono eseguite meno istruzioni); al contrario, se le
operazioni in virgola mobile vengono effettuate via software (tramite l’uso di istruzioni
semplici), il MIPS sale ma sale anche il numero di istruzioni da eseguire e anche il
tempo di esecuzione.
In base a questi discorsi si comprende come il MIPS possa non dare un’immagine veritiera
delle prestazioni, proprio perché non considera il tempo di esecuzione. Per compensare questa
“debolezza”, si può pensare di introdurre un MIPS di riferimento, ossia un MIPS calcolato
per una particolare macchina di riferimento, rispetto al quale esprimere il MIPS originario,
ossia quello definito prima: si ottiene così il cosiddetto MIPS relativo, dato da
MIPS relativo =
Tempo riferimento
× MIPSriferimento
Tempo non rapportato
In quest’espressione, la quantità Temporiferimento rappresenta il tempo di esecuzione di un
programma sulla macchina di riferimento, mentre invece Temponon rapportato è il tempo
necessario ad eseguire lo stesso programma sulla macchina da rapportare. La quantità
MIPSriferimento è invece il MIPS accertato della macchina di riferimento. Naturalmente il MIPS
relativo considera solo il tempo di esecuzione per un programma specifico e per un ingresso
specifico a tale programma.
Ci sono per lo meno due problemi da considerare a proposito del MIPS relativo:
1. risulta in ogni modo difficile trovare una macchina di riferimento della stessa età di
quella da testare (negli anni ’80, la principale macchina di riferimento fu il VAX 11/780,
che fu detta anche macchina da 1 MIPS);
2. bisogna decidere se la macchina più vecchia debba funzionare con la versione più
recente del compilatore e del sistema operativo oppure se il software debba in ogni
caso essere fissato in modo che la macchina di riferimento non diventi più veloce con il
passare del tempo.
Facendo dunque una sintesi di quanto detto, anche il MIPS relativo appare scarso, in quanto il
tempo di esecuzione, il programma ed il suo ingresso devono comunque essere noti per poter
ricavare delle informazioni significative.
4.6 MFLOPS (MILIONI DI OPERAZIONI IN VIRGOLA MOBILE AL
SECONDO)
Una diffusa alternativa al tempo di esecuzione, per la misura delle prestazioni, è il cosiddetto
MFLOPS (si legge “megaflops”), che sta per “milioni di operazioni in virgola mobile al secondo”.
La formula con cui si calcola questo indice di prestazione non è altro che la definizione
dell’acronimo:
MFLOPS =
Numero di operazioniin virgola mobiledi un programma
Tempo di e sec uzione×106
4.10
E’ subito evidente che il MFLOPS dipende sia dalla macchina sia dal programma che si sta
usando. Naturalmente, dato che il MFLOPS fa riferimento alle sole operazioni in virgola
mobile, non è applicabile altrove. A titolo di esempio estremo, citiamo il fatto che ci sono
alcuni compilatori che, a prescindere dalla velocità della macchina su cui vengono eseguiti,
hanno un MFLOPS quasi nullo, poiché usano molto poco le operazioni in virgola mobile.
Il MFLOPS appare più rappresentativo del MIPS: infatti, essendo basato sulle singole
operazioni e non sulle istruzioni, esso può essere teoricamente usato per confrontare tra loro
due o più macchine, partendo dal presupposto che un programma, funzionando su diverse
macchine, può eseguire un diverso numero di istruzioni, ma compirà sempre lo stesso numero
di operazioni in virgola mobile.
Nonostante questo pregio, si manifesta comunque anche un problema: l’insieme delle
operazioni in virgola mobile non è lo stesso in tutte le macchine. Ad esempio, il calcolatore
CRAY-2 non possiede istruzioni di divisione, mentre invece il Motorola 68882 possiede la
divisione, la radice quadrata, il seno ed il coseno. A questo si aggiunge anche il problema per
cui il MFLOPS cambia non solo al variare della mescolanza tra le operazioni intere ed
operazioni in virgola mobile, ma anche al variare della mescolanza tra operazioni in virgola
mobile “lente” e “veloci”: ad esempio, un programma con il 100% di addizioni in virgola mobile
darà origine ad un MFLOPS più alto (e quindi migliore) rispetto ad un programma che invece ha
il 100% di divisioni in virgola mobile.
Una possibile soluzione ad entrambi questi problemi consiste nel definire un numero canonico
di istruzioni in virgola mobile a livello di programma sorgente ed usare tale numero per
calcolare il MFLOPS, dividendo in pratica per il tempo di esecuzione. Si ottiene in questo caso
il cosiddetto MFLOPS normalizzato, che differisce quindi dal MFLOPS originario (cioè quello
definito all’inizio del paragrafo). Ricordiamo che il MFLOPS, come ogni altra misura di
prestazione, è legato al programma che si sceglie di usare (per cui ha senso parlare solo di
MFLOPS per un dato programma su una data macchina) e quindi non può essere generalizzato
al fine di stabilire una singola misura di prestazione per un calcolatore.
4.7 SCELTA DI PROGRAMMI PER VALUTARE LE PRESTAZIONI
Un utente di calcolatori che usa gli stessi programmi ogni giorno potrebbe essere il candidato
perfetto per valutare un nuovo calcolatore: infatti, per valutare un nuovo sistema, egli
dovrebbe semplicemente confrontare il tempo di esecuzione del suo abituale carico di lavoro
(workload), ossia appunto dell’insieme di programmi e di comandi sul sistema operativo che usa
abitualmente. Il problema è che difficilmente si trovano utenti con una tale caratteristica di
regolarità per quanto riguarda i programmi utilizzati, per cui bisogna necessariamente far
riferimento ad altri metodi per valutare le macchine.
Esistono allora quattro “livelli” di programmi da poter usare per confrontare le prestazioni di
macchine diverse; gli elenchiamo in ordine decrescente dal punto di vista dell’accuratezza
delle predizioni che consentono di ottenere:
-
programmi reali: pur non sapendo quale frazione del tempo sarà dedicata a questi
programmi, il venditore sa comunque che alcuni utenti li useranno per risolvere i
propri problemi reali; tipici esempi di programmi reali sono i compilatori per il C, i
programmi di composizione testi oppure alcuni strumenti CAD come Spice. Una delle
caratteristiche dei programmi reali è quella di avere degli ingressi, delle uscite, e
delle opzioni che l’utente può selezionare quando tali programmi sono in funzione;
4.11
Esempi:
ƒ
ƒ
ƒ
ƒ
-
Programmi scientifici
Compilatori
Programmi per word processing (MS-WORD)
Strumenti CAD (SPICE)
Nuclei o benchmark ridotti (kernel): sono stati fatti numerosi tentativi per
circoscrivere piccoli pezzi chiave (detti appunto kernel) dei programmi reali per
valutare le prestazioni dei calcolatori (gli esempi più noti sono il Livermore Loops ed
il Linpack). Chiaramente, gli utenti generalmente non fanno girare programmi kernel
sulle proprie macchine, proprio perché questi, a differenza dei programmi reali da
cui provengono, sono pensati solo per valutare le prestazioni e non per risolvere
problemi reali. I kernel vengono utilizzati al meglio quando si vogliono isolare le
prestazioni di alcune caratteristiche specifiche di una macchina, al fine di
evidenziare le ragioni delle differenze tra le prestazioni con programmi reali;
Esempi:
ƒ
ƒ
-
Linpack
Livermore loops
Programmi giocattolo (Toy Benchmark): questi particolari programmi sono
tipicamente lunghi dalle 10 alle 100 linee di codice e producono un risultato che
l’utente conosce in partenza;
Esempi:
ƒ
ƒ
ƒ
-
Quicksort
Puzzle
Algoritmi di sort
Benchmark sintetici: questi programmi sono “filosoficamente” simili ai kernel, ma
hanno la differenza di sforzarsi di accordare la frequenza media degli operandi e
delle operazioni di un vasto insieme di programmi (i più popolari benchmark sintetici
sono il Whetstone ed il Dhrystone). Così come i kernel, i benchmark sintetici non
sono usati dagli utenti proprio perché non calcolano nulla di utile per i loro problemi
reali. Tra l’altro, i benchmark sintetici sono ancora più astratti rispetto ai kernel,
dato che, mentre i kernel hanno un codice comunque estratto dai programmi reali, il
codice sintetico viene creato artificialmente per soddisfare il profilo medio di
esecuzione di cui si diceva prima.
Esempi:
ƒ
ƒ
Whetstone
Dhrystone
Quando sia ha disposizione un programma per la valutazione delle prestazioni di un calcolatore
e non si è sicuri del modo con cui classificarlo, il primo controllo da effettuare consiste nel
vedere se esiste un qualche ingresso al programma e/o se sono previsti dei risultati in uscita.
Ad esempio, un programma senza ingresso calcola lo stesso risultato ogni volta che viene
4.12
invocato, mentre invece alcuni programmi (tipicamente le applicazioni per l’analisi numerica e la
simulazione) usano quantità trascurabili di dati d’ingresso. In generale, ogni programma reale
ha almeno un ingresso.
Dato che i produttori di calcolatori hanno maggiore o minore successo a seconda del rapporto
prezzo/prestazioni dei loro prodotti in relazione a quello degli altri concorrenti presenti sul
mercato, sono disponibili enormi risorse per migliorare le prestazioni dei programmi più
diffusi per la valutazione. Abbiamo osservato più volte che i programmi reali possono essere
teoricamente usati per valutare le prestazioni dei calcolatori. Tra l’altro, se l’industria usasse
tali programmi, quanto meno spenderebbe le proprie risorse per migliorare le prestazioni di
ciò che realmente serve agli utenti. Viene allora da chiedersi come mai non tutti usano i
programmi reali per misurare le prestazioni. Possiamo citare le seguenti motivazioni:
-
-
i kernel ed i toy benchmark appaiono molto utili nelle fasi iniziali di un progetto, in
quanto sono abbastanza piccoli da essere simulati facilmente, al limite anche a
mano. Essi risultano particolarmente invitanti quando si inventa una nuova macchina,
dato che i compilatori potrebbero non essere subito disponibili. Inoltre, i piccoli
benchmark, a differenza dei grandi programmi, possono essere standardizzati con
maggiore facilità ed infatti si trovano in letteratura molti più risultati pubblicati
per i piccoli benchmark che non per quelli grandi;
a fronte di questi pregi, almeno al giorno d’oggi non esiste più una valida motivazione
razionale che giustifichi l’uso dei benchmark e dei kernel per valutare come
lavorano i sistemi di calcolo: in passato, i linguaggi di programmazione non erano
uniformi in macchine diverse e ogni macchina aveva il suo sistema operativo, per cui
il trasporto dei programmi reali appariva particolarmente scomodo e quindi venivano
preferiti i benchmark e/o i kernel; al giorno d’oggi, invece, la diffusione di sistemi
operativi standard, la presenza di software liberamente distribuito dalle università
e da altri enti, oltre alla disponibilità di calcolatori moderni e veloci, hanno rimosso
molti dei vecchi ostacoli, per cui l’uso di programmi reali è diventato, oltre che
opportuno al fine di ottenere risultati corretti, anche molto semplice.
4.7.1 SPEC benchmarks
Un progresso notevole nella valutazione delle prestazioni si ebbe con la formazione del gruppo
SPEC (System Performance Evaluation Cooperative) nel 1988. SPEC comprende rappresentanti
di diverse industrie informatiche che si sono accordati su un insieme di programmi reali e di
valori d’ingresso.
La versione iniziale, chiamata SPEC89, conteneva in totale 10 benchmark: sei per l’analisi in
virgola mobile e i restanti quattro per elaborazioni intere, ed il valore complessivo veniva
calcolato come media geometrica dei tempi di esecuzione normalizzati rispetto al VAX11/780, che favoriva calcolatori con migliori prestazioni sui numeri in virgola mobile.
Nella seconda versione, chiamata SPEC92, tra i benchmark utilizzati venne escluso il
matrix300, il motivo è chiaro dal seguente grafico che riporta le prestazioni relative allo
SPEC89 riportate per la IBM Powerstation 550, utilizzando due diversi compilatori.
4.13
800
700
600
500
Compilatore
400
Compilatore avanzato
300
200
100
li
eq
n
m tot
t
at
rix
30
0
fp
pp
to p
m
ca
tv
g
es cc
pr
es
so
sp
ic
e
do
du
c
na
sa
7
0
Figura 4.2 Confronto delle Prestazioni.
Sempre nella seconda versione, venne introdotto un nuovo insieme di benchmark che
comprendeva programmi nuovi e forniva valori medi separati: SPECint, che comprendeva 6
programmi, SPECfp, che ne comprendeva 14, rispettivamente per programmi interi e in virgola
mobile. Inoltre fu aggiunta la misura SPECbase, in cui si vietano versioni di ottimizzazione
adattate ai benchmark, per permettere di ottenere delle cifre più vicine alle prestazioni dei
programmi reali.
Un ulteriore modifica fu apportata con lo SPEC95, dal quale vennero eliminati alcuni
benchmark, rivelatisi errati, ed aggiunti altri. Anche il calcolatore di riferimento per la
normalizzazione venne cambiato, adottando una Sun SPARCstation 10/40, poiché iniziava ad
essere difficile reperire esemplari funzionanti di VAX 11/780. Questa versione comprendeva
18 benchmark: otto con prevalenti operazioni su interi (go, m88ksim, gcc, compress, li, ijpeg,
perl, vortex) e i restanti dieci con prevalenti operazioni in virgola mobile (tomcatv, swim,
su2cor, hydro2d, mgrid, applu, turb3d, apsi, fpppp, wave5).
I benchmark più recenti di SPEC2000 mirano a valutare le prestazioni con carichi di lavoro
che si avvicinano ad elaborazioni scientifiche di alto livello, come ad esempio i server web.
4.8 COME PORRE IN RELAZIONE LE PRESTAZIONI
Il principio fondamentale da applicare nella presentazione delle misure di prestazioni
dovrebbe essere la riproducibilità: si dovrebbero cioè annotare e fornire tutte le
informazioni che potrebbero essere utili ad un altro sperimentatore per duplicare i risultati.
Al contrario, tale principio non sempre viene applicato; per rendersene conto, basterebbe
confrontare il modo con cui vengono descritte in letteratura le prestazioni dei calcolatori ed il
modo, ad esempio, con cui vengono presentate le prestazioni automobilistiche delle riviste del
settore: tali riviste, oltre a fornire un numero elevato di misure di prestazioni (almeno 20),
elencano tutti gli accessori montati sulla macchina in prova, il tipo di pneumatici usati durante
il test, la data di tale test e molto altro; al contrario, le pubblicazioni scientifiche possono
avere solo i secondi di esecuzione, etichettati semplicemente con il nome del programma e
quello della macchina: ad esempio, si trova scritto che Spice richiede 94 sec. su una macchina
DECstation 3100. In casi come questo, si lasciano all’immaginazione del lettore gli ingressi e la
versione del programma, quella del compilatore, il livello di ottimizzazione del codice
4.14
compilato, la versione del sistema operativo, le caratteristiche hardware della macchina (ad
esempio quantità di memoria centrale, numero e tipo di dischi, versione della CPU); si nasconde
cioè tutta una serie di informazioni che invece influiscono pesantemente sull’esito delle prove.
I periodici automobilistici hanno abbastanza informazioni, circa le misurazioni, da permettere
allo stesso lettore di ripetere i risultati oppure semplicemente di mettere in relazione le
opzioni selezionate per i rilievi; al contrario, sulle riviste di calcolatori questo spesso non
avviene.
4.9 LOCALITÀ DEL RIFERIMENTO
La proprietà principale di un programma è la cosiddetta località del riferimento: essa
corrisponde al fatto che i programmi tendono a riutilizzare i dati e le istruzioni che hanno
usato di recente. Ad esempio, una regola pratica ormai ampiamente accettata e la cosiddetta
regola 90/10: essa afferma che un programma spende il 90% del suo tempo di esecuzione
solo per il 10% del suo codice. Una immediata applicazione di questo principio è che,
basandosi sul passato recente del programma, è possibile predire, con ragionevole
accuratezza, quali dati ed istruzioni il programma userà nel prossimo futuro.
La località dei riferimenti può essere applicata anche all’accesso ai dati, anche se in modo
decisamente meno regolare rispetto all’accesso alle istruzioni. Vanno distinti, in particolare,
due differenti tipi di località:
-
una località temporale afferma che gli elementi quali si è fatto riferimento di
recente saranno usati ancora nel prossimo futuro;
una località spaziale afferma invece gli elementi i cui indirizzi sono vicini ad un
dato indirizzo di riferimento tendono ad essere referenziati in tempi molto
ravvicinati.
Questi concetti sono alla base dell’uso di particolari unità di memoria ad accesso rapido
(memorie cache) (vedi capitolo 7), che consentono di migliorare le prestazioni di una macchina
rendendo più velocemente disponibili quei dati che essa vorrà utilizzare con maggiore
probabilità.
4.10 IL LAVORO DI UN PROGETTISTA DI CALCOLATORI
Il progettista di architetture di calcolatori è colui che progetta macchine per eseguire
programmi.
La realizzazione di una macchina ha, in generale, due componenti:
-
organizzazione: comprende gli aspetti di progetto di più alto livello, come il sistema
di memoria, la struttura del bus ed il progetto interno della CPU;
hardware: con questo termine ci si riferisce generalmente al progetto logico
dettagliato della macchina ed alla tecnologia con cui è stato realmente realizzato.
4.10.1 Requisiti funzionali
Un progettista di architetture di calcolatori deve effettuare il suo progetto in modo da
soddisfare gli obiettivi prefissi su tre diversi aspetti:
4.15
- requisiti funzionali;
- prezzo;
- prestazioni.
Per quanto riguarda i requisiti funzionali, può trattarsi semplicemente di caratteristiche
specifiche imposte dal mercato: ad esempio, le applicazioni software determinano come la
macchina sarà usata e quindi spesso guidano la scelta di determinati requisiti funzionali; se
una gran parte del software viene concepita per una certa architettura dell’insieme di
istruzioni, sarà tale architettura ad essere considerata quasi come predefinita dal
progettista. Una semplice considerazione, in quest’ottica, è la seguente: il fatto che la
maggior parte dei moderni sistemi operativi usino memoria virtuale e meccanismi di protezione
costituisce un requisito funzionale che il progettista dovrà garantire, fornendo un supporto
minimo a tali funzionalità, senza il quale la macchina non sarebbe nemmeno avviabile:
successivamente, ogni hardware aggiuntivo a tale soglia può essere valutato da un punto di
vista costo/prestazioni.
4.10.2 Bilancio software e hardware
Una volta stabilito un insieme di requisiti funzionali, il progettista deve cercare di ottimizzare
il progetto complessivo della macchina, sulla base di opportuni parametri. I parametri più
comuni sono senz’altro il costo e le prestazioni. Altri parametri, più specifici dei vari ambienti
di lavoro, sono ad esempio l’affidabilità e la tolleranza ai guasti.
Subentra a questo punto la questione generale della scelta se realizzare una data
caratteristica tramite l’hardware o tramite il software:
-
la realizzazione via software è facile da progettare, semplice da aggiornare e con
minore costo degli errori;
la realizzazione via hardware ha invece il grosso vantaggio di garantire migliori
prestazioni, anche se non sempre (infatti, un algoritmo realizzato via software può
essere migliore di un algoritmo mediocre realizzato in hardware), mentre invece il
costo degli errori è decisamente più alto (una cosa è modificare la riga di un
programma e ben altra cosa è riparare o sostituire integralmente un intero
circuito).
Possiamo perciò affermare che un giusto bilancio tra hardware e software conduce
sicuramente ad ottenere la macchina migliore per l’applicazione di interesse.
A questo possiamo aggiungere che, talvolta, un requisito specifico può rendere necessaria
l’introduzione di un supporto hardware. Dovendo scegliere tra due progetti, un fattore di
giudizio importante è anche la complessità di progetto. Infatti, i progetti complessi
richiedono tempo per essere completati e immessi sul mercato, il che implica che essi, per
poter essere competitivi ed attrarre gli utenti, debbano avere alte prestazioni.
In generale, risulta più semplice controllare la complessità del software che non
dell’hardware, dato che il software è più facilmente leggibile e quindi modificabile, per cui un
progettista potrebbe esplicitamente trasferire talune funzionalità dell’hardware al software.
A fronte di questo, d’altra parte, la scelta dell’architettura dell’insieme di istruzioni e la
scelta dell’organizzazione influenzano la complessità non solo dei sistemi operativi e dei
compilatori, ma anche della realizzazione della macchina.
4.16
4.10.3 Ulteriori fattori che influiscono sul progetto
Affinché una data architettura abbia successo, deve essere progettata per sopravvivere ai
mutamenti della tecnologia hardware, della tecnologia software e delle caratteristiche delle
applicazioni. Di conseguenza, il progettista deve essere sempre informato sulle tendenze
prevalenti nella tecnologia dei calcolatori e sul loro impiego, nonché sulle tecnologie
realizzative. Il progettista deve anche essere informato sulle tendenze di mercato nel
software e sul modo in cui i programmi utilizzeranno la macchina. L’ultima importante
questione che un progettista deve affrontare, una volta compresa l’influenza che le tendenze
hardware e software hanno sul progetto della macchina, riguarda il modo con cui bilanciare la
macchina. Ad esempio, bisogna decidere quanta memoria è necessario prevedere per la
velocità di CPU stabilita oppure quando I/O verrà richiesto e simili. Per rispondere a questo
tipo di domande, i progettisti Case e Amdahl hanno creato due regole approssimative, che
sono state poi riunite nella seguente regola unificata:
una macchina da 1 MIPS (milioni di istruzioni al secondo) è bilanciata quando ha 1
megabyte di memoria ed 1 megabyte al secondo come prestazione di I/O.
Questa regola è solo un punto di partenza per la progettazione di sistemi bilanciati, da
perfezionare poi tramite misure delle prestazioni della macchina quando vengono eseguite le
applicazioni previste.
4.11 LA SCELTA DEL SOFTWARE BENCHMARK PER LE NOSTRE
VALUTAZIONI
SiSoft SANDRA (System Analyzer/Diagnostic and Reporting Assistant) è un analizzatore di
sistema per Windows, in grado di eseguire diagnosi e che consente anche di valutare le
prestazioni del pc, grazie ad una nutrita serie di moduli di testing. Ci sono diversi software
che sono in grado di svolgere queste analisi, ma abbiamo scelto SANDRA per effettuare i
nostri test perché ha il vantaggio di essere gratuito e di offrire ben 50 moduli. L’unica pecca
che può essere attribuita al programma è la non compatibilità con il sistema operativo LINUX,
quindi effettueremo i nostri test solo su macchine che montano sistema operativo Windows.
Il software si presenta sotto forma di una finestra, molto simile a quella familiare di “Esplora
Risorse” di Windows, in cui sono elencati gli “Active Modules”, ossia i moduli in grado di
eseguire analisi, test e report. La difficoltà non risiede quindi tanto nell’uso (come del resto
tutti i software di questo genere), ma piuttosto nell’interpretazione dei risultati, giacché
questi programmi recuperano informazioni dettagliate su Processore e Chipset, RAM e disco
fisso, scheda grafica e audio, porte e driver di periferiche o determinano le prestazioni del
sistema, tutti dati che richiedono un’analisi curata e approfondita in modo che una volta
interpretati si possa arrivare a trovare delle soluzioni per migliorare le prestazioni della
macchina, operando non solo a livello di aggiornamento ma anche nella configurazione ottimale
di tutte le risorse disponibili.
4.17
4.11.1 Caretteristiche del Software SiSoft Sandra
Versione: SANDRA Standard 2004.SP2 9.131
Produttore: SiSoft
PRO
CONTRO
1. Gratuito per uso privato/didattico (Freeware)
2. Disponibili 50 moduli per effettuare altrettanti
test ed analisi.
1. Non compatibile con il sistema operativo
LINUX, ma soltanto per Windows.
2. Difficoltà nell’interpretazione dei dati
forniti nei risultati dei test
3. Supporto di un vasto numero di periferiche,
chipset e processori , compresa la modalità a 64 bit
dei nuovi processori AMD Athlon.
3. Nell’esecuzione di particolari test i
componenti Hardware sono molto sollecitati
e “stressati”.
SiSoftware Sandra è uno strumento di analisi per sistemi Windows 32/64 con moduli di
testing e benchmarking integrati. Il suo funzionamento è del tutto simile a quello di altre
utilità di Windows ma Sandra si prefigge di andare oltre, mostrando cosa realmente stia
accadendo ed offrendo all’utente la possibilità di creare confronti sia ad alto che basso livello.
E’ possibile ottenere informazioni su CPU, chipset, adattatori video, porte, stampanti, schede
sonore, memoria, rete, risorse di Windows, AGP (Accelerated Graphics Port=Porta di
Accelerazione Grafica: slot di espansione ad alta velocità sviluppato da Intel per permettere
ad una scheda ad alte performance grafiche di essere adattata su un PC. La porta fornisce un
bus dedicato per trasferire dati grafici tra il processore del PC e la sua memoria principale; la
porta AGP trasferisce informazioni a 528Mbytes per secondo, così è molto più veloce del
generico bus di espansione PCI che trasferisce dati a 132 Mbytes per secondo), Connettività
ODBC (Open DataBase Connectivty: software che concede ad un'applicazione di accedere a
tutta la fonte di dati compatibile; lo standard è stato sviluppato da Microsoft ma viene anche
usato da molti differenti sviluppatori come un metodo standard di fornire acceso ad una vasta
gamma di database), USB2 (Universal Serial Bus: standard che definisce un interfaccia
seriale ad ala velocità che trasferisce dati fino a 12Mbps e permette che fino a 127 unità
periferiche compatibili siano collegate ad un calcolatore; questo bus oggi è disponibile su tutti
i nuovi computer e la porta USB probabilmente sostituirà i connettori separati richiesti per
una tastiera, mouse, modem 56k e stampanti), Firewire (Interfaccia seriale ad alta velocità
sviluppata dalla Apple Computers e usata per collegare dispositivi come le fotocamere o
telecamere digitali con il computer).
I benchmark sono ottimizzati per SMP (Sistema Multi Processore: alcuni di questi sistemi
sono per esempio gli Athlon MP)/SMT (Surface Mount Technology: metodo di costruzione dei
circuiti che permette la modalità di esecuzione parallela, cioè una modalità che è stata
sviluppata per poter ottenere maggiori prestazioni dai sistemi Hyper-Threading. Tale
implementazione hardware permette l'esecuzione di più processi contemporanei migliorando
l'utilizzazione delle risorse disponibili, e ottenendo dei sistemi più veloci), nonchè per
supportare fino ad un massimo di 32/64 CPU, a seconda della piattaforma.
I resoconti di sistema creati potranno essere salvati/stampati/inviati via fax/spediti per email/posta/trasferiti su Web o inseriti all’interno di database ADO/ODBC in formato testo,
HTML (HyperText Markup Language: una serie di codici speciali di testo che vengono
normalmente usati per creare pagine web, infatti è lo standard impiegato in internet e
tradotto da tutti i browser in circolazione ), XML (Extensible Markup Language: altro tipo di
linguaggio usato per la creazione di pagine web, ma più flessibile dell’HTML), SMS/DMI o RPT.
4.18
Sistemi operativi supportati in questa versione:
Win32 x86/IA32 Version (Windows 98/Me/2000/XP/2003); Win64 AMD64/EM64T Version
(Windows x64 Edition); Win64 IA64 Version (Windows 64-bit Edition); WinCE 3.00 Arm
Pocket PC / Smart Phone 2002; WinCE 4.20 Arm Pocket PC / Smart Phone / Windows Mobile
2003; WinCE 4.21 Arm Pocket PC / Smart Phone / Windows Mobile 2003SE
I Moduli disponibili in SANDRA sono suddivisi in quattro categorie:
1. INFORMATION MODULES: forniscono preziose informazioni sui vari elementi del
sistema operativo.
2. BENCHMARKING MODULES: offrono un rapporto dettagliato sulle prestazioni
(saranno i moduli da noi maggiormente usati per i nostri test).
3. LISTING MODULES: consentono di controllare i file di sistema come “Autoexec.bat”,
“Config.sys” o il “System.ini”
4. TESTING/DIAGNOSTIC MODULES: vengono in aiuto per risalire all’origine di un
problema nel funzionamento di un componente del sistema, per verificare lo stato del
CMOS (MOS a simmetria Complementare: è un dispositivo formato dalla combinazione
di un PMOS con un NMOS che viene montato sulla scheda madre del computer),
dell’IRQ (Interrupt ReQuest: è un segnale trasmesso all'unità centrale
dell’elaboratore [CPU] temporaneamente per sospendere l'elaborazione normale e
trasferire il controllo ad una interruzione [interrupt] che sta usando la
procedura[routine])e degli handler.
4.11.2 Informazioni sul calcolatore impiegato per i test
Iniziamo i nostri test dal modulo SYSTEM SUMMARY (Informazioni sul Sistema), disponibile
nella prima categoria dei moduli (INFORMATION MODULES), in quanto dobbiamo dare una
panoramica dettagliata in modo rapido sulle principali caratteristiche del sistema in uso:
processore, frequenza, ma anche versione del BIOS, chipset (chip base della scheda madre) e
RAM (Random Access Memory: memoria ad accesso casuale, infatti permette di accedere a
qualsiasi locazione in qualsiasi ordine ) disponibile. Qui di seguito sono riportate i risultati del
test sulla macchina che abbiamo usato. Nel campo “Voce” sono elencati gli elementi analizzati,
mentre nel campo “Valore” sono riportate le relative informazioni.
4.19
VOCE
VALORE
Processore
Modello
Intel(R) Pentium(R) 4 CPU 2.00GHz
Velocità:
2.01GHz
Performance Rating (PR):
PR2206 (stimato)
Tipo:
Standard
Cache L2 On-board:
512kB ECC Sincrono ATC (8-way a settore, 64 byte dim. linea)
Mainboard
Bus:
AGP PCI IMB USB
BIOS Sistema:
Award Software, Inc. ASUS P4PE ACPI BIOS Revision 1001
Sistema:
System Manufacturer System Name
Mainboard:
ASUSTeK Computer INC. P4PE
Memoria Totale:
512MB DDR-SDRAM
Chipset 1
Modello:
ASUSTeK Computer Inc 82845PE Brookdale Host-Hub Interface
Bridge (B0-step)
Velocità Front Side Bus (FSB)
4x 100MHz (400MHz vel. dati)
Memoria Totale:
512MB DDR-SDRAM
Velocità Bus di Memoria:
2x 133MHz (266MHz vel. dati)
Sistema Video
Monitor/Pannello:
Monitor Plug and Play
Adattatore:
NVIDIA RIVA TNT2 Model 64
Unità di Memorizzazione Fisiche
Unità Rimovibile:
Unità disco floppy
Disco Rigido:
Maxtor 6Y080P0
CD-ROM/DVD:
HL-DT-ST DVD-ROM GDR8161B (CD 48X Rd) (DVD 6X Rd)
CD-ROM/DVD:
PLEXTOR CD-R PX-W2410A (CD 40X Rd, 24X Wr)
Unità di Memorizzazione Logiche
Disco Rigido (C:):
29.3GB (15.1GB, 52% Liberi) (NTFS)
Disco Rigido (D:):
47.0GB (30.3GB, 64% Liberi) (NTFS)
Sistema Operativo
Sistema Windows:
File System:
Microsoft Windows XP (Build 2600) Professional (Win32
x86/IA32) 5.01.2600
NTFS
Le informazioni che abbiamo raccolto sono di carattere generale e possono essere
successivamente approfondite tramite l’apposito modulo per ogni periferica o componente del
PC, infatti abbiamo sempre nella categoria “INFORMATION MODULES” quelli relativi ai Bus
4.20
PCI(tipo di bus locale veloce sviluppato da Intel che permettere di trasferire dati ad alta
velocità tra il processore del PC e una periferica o una scheda di espansione) e AGP
(Accelerated Graphics Port), al sottosistema Video e alla Memoria del Sistema, alle varie
unità e alle porte, alla tastiera e al mouse, alla scheda video e audio, alle stampanti, a
Windows, ai processi in corso e ai tipi di caratteri (Font) e, in ultima analisi anche alle varie
librerie (Direct X e Open GL).
4.11.3 Risultati dei Bencmark SiSoft Sandra “Aritmetica CPU” e SPEC CPU2000
I moduli messi a disposizione nella categoria “BENCHMARKING MODULES” sono i seguenti:
• Aritmetica CPU
• Multi-Media CPU
• Unità Rimovibili/Flash
• File System
• CD-ROM/DVD
• Bandwidth Memoria
• Cache & Memoria
• Bandwidth Network/LAN
• Connessione ad Internet
• Ping Internet
Qui di seguito sono riportati i risultati del Benchmark “Aritmetica CPU” ottenuti sul
calcolatore che abbiamo usato, mentre nella seconda tabella sono riportati i risultati del test
SPEC CPU2000 effettuato sempre sullo stesso tipo di calcolatore da noi impiegato.
Risultati prodotti dal Benchmark “Aritmetica CPU” di SisSoft SANDRA:
CPU
Intel Pentium 4 2.0GHz 256L2
(Win32 x86/IA32)
Drystone ALU
Whestone FPU
Whestone SSE2
5289 MIPS
1466 MFLOPS
2701 MFLOPS
Significato del valore Dhrystone ALU:
Il valore del Benchmark Dhrystone è utilizzato per misurare le "performance" della CPU nelle
operazioni ALU (con numeri interi). Nel test vengono utilizzate numerosi tipi di operazioni
usate nelle applicazioni più comuni. Purtroppo, il valore non rappresenta le prestazioni reali
della CPU, ma è indice di velocità del processore in funzione di una comparazione con altre
CPU. Comunque l'indice MIPS (Milioni di istruzioni per secondo) dovrebbe essere identico per
lo stesso sistema con variazioni del 5-10%.
Significato del valore Whetstone FPU:
Il valore del Benchmark Whetstone è utilizzato per misurare le prestazioni del calcolo in
virgola mobile impiegando l'unità FPU o il coprocessore matematico del sistema. Molti
programmi sfruttano ampiamente l'unità FPU della CPU, e la misurazione di tale quantità è
d'obbligo per un valido programma di test. Il risultato del test non è comparabile con altri
software che verificano tale quantità. Nonostante ciò, il valore in MFLOPS (Milioni di
operazioni in virgola mobile per secondo) potrebbe differenziarsi del 5-10%.
Significato del valore Whestone SSE2:
Il valore del Benchmark Whetstone è utilizzato per misurare le prestazioni del calcolo in
virgola mobile impiegando le istruzioni SSE2. Con l'introduzione di queste istruzioni è ora
possibile quantificare un valore di prestazione del sistema utilizzando tali quantità. SiSoft
4.21
Sandra 2004 sfrutta le istruzioni delle SSE2 e determina un valore di prestazione
complessivo. Il valore in MFLOPS (Milioni di operazioni in virgola mobile per secondo)
potrebbe differenziarsi del 5-10% sullo stesso sistema.
Nota sul perché si verificano variazioni nei risultati: normalmente la variazione dei risultati
dei test condotti sullo stesso PC è del +/-5%. Nei sistemi con poca memoria tale valore
potrebbe salire al +/-10%; ciò è dovuto all'operazione di "Swapping" (in pratica il sistema
operativo utilizza il disco rigido per memorizzare dei dati poichè la memoria disponibile è
insufficiente), che rallenta non di poco l'esecuzione del Benchmark.
Risultati prodotti dal Benchmark CPU2000 di SPEC:
Risultato
Baseline
755
749
Processore
Intel Pentium 4
Processor (2.0A
GHz,400 MHz bus)
Processore
2000 MHz
Memoria RAM
512 MB (2 256 MB
PC1066-32 RDRAM
non-ECC modules)
Sist. Operativo
Windows XP
Professional
(Build 2600)
CINT2000 Result
Copyright © 1999-2002 Standard Performance Evaluation Corporation
Intel Corporation
Intel Pentium 2.0A GHz
Benchmark
SPECint2000 =
755
SPECint_base2000 =749
Base
Reference Base
Time Runtime Ratio Runtime Ratio
164.gzip
1400
177
790
177
793
175.vpr
1400
286
489
306
458
176.gcc
1100
125
878
125
877
181.mcf
1800
314
573
314
573
186.crafty
1000
134
747
132
755
197.parser
1800
251
718
251
718
252.eon
1300
147
882
127
1023
253.perlbmk 1800
208
867
208
866
1100
114
963
113
972
255.vortex 1900
155
1224
155
1224
256.bzip2
1500
252
595
253
594
300.twolf
3000
540
556
539
556
254.gap
SPECint_base2000
749
SPECint2000
755
4.22
4.12 Descrizione di SPEC CPU2000
Spec. CPU2000 è la suite benchmark industria-standardizzato next-generation di CPUintensive. In generale, spec. ha progettato spec. CPU2000 per fornire una misura comparativa
delle prestazioni intense di calcolo attraverso la più larga gamma di hardware. Perciò Spec ha
corredato i benchmarks di codice sorgente sviluppato dalle applicazioni reali dell'utente.
Questi benchmarks dipendono dal processore dalla memoria e dal compilatore sul sistema
esaminato.
4.12.1 Che cosa è “SPEC”
Spec. è una sigla che significa Standard Performance Evaluation Corporation. Spec. è
un'organizzazione senza scopo di lucro composta da fornitori di calcolatori, da integratori dei
sistemi, da università, da organismi di ricerca, da editori e da consulenti il cui obiettivo è
quello di stabilire, effettuare e firmare un insieme standardizzato dei benchmarks relativi ai
sistemi di elaborazione. Anche se nessun insieme delle prove può completamente
caratterizzare le prestazioni del sistema generale, spec. crede che l'associazione di utenti
tragga beneficio da una serie obiettiva di prove che possono servire come punto di
riferimento comune.
Che cosa misura spec. CPU2000
Spec. CPU2000 si focalizza sulle prestazioni intense di calcolo, ciò significa che questi
benchmarks danno risalto alle prestazioni di:
• il processore del calcolatore (CPU)
• l'architettura di memoria
• i compilatori.
È importante ricordarsi del contributo dei due componenti posteriori anche se le prestazioni
dipendono appena un pò più dal processore.
Spec. CPU2000 si compone di due subcomponenti che si concentrano su due tipi differenti di
prestazioni intense di calcolo:
• CINT2000 per la misurazione e il confronto delle prestazioni comput-intense di
numero intero
• CFP2000 per la misurazione e il confronto delle prestazioni comput-intense della
virgola mobile.
Si noti che spec. CPU2000 non sollecita altri componenti del calcolatore quali I/O (driver del
disco), rete, il sistema operativo o i grafici. Potrebbe essere possibile configurare un sistema
in modo tale che uno o più di questi componenti ha effetto sulle prestazioni di CINT2000 e di
CFP2000, ma questo non è intenzione della suites CPU 2000
A che cosa corrisponde la “C" in CINT2000 ed in CFP2000?
La “C" denota che questi sono benchmark del componente-livello in contrasto con i benchmark
interi del sistema.
Perchè si usa spec. CPU2000
Come detto precedentemente, spec. CPU2000 fornisce una misura comparativa delle
prestazioni intense di calcolo della virgola mobile e/o di numero intero. Se questo è combinato
con il tipo di quote di lavoro a cui siamo interessati, spec. CPU2000 fornisce un buon punto di
riferimento. Altri vantaggi nell’usare spec. CPU2000 sono:
4.23
•
•
•
•
•
I programmi di benchmark sono sviluppati dalle applicazioni reali dell'utilizzatore
finale in contrasto con l’essere benchmarks sintetici.
I fornitori multipli usano la suite SPEC e la sostengono.
Spec. CPU2000 è altamente portatile.
Una vasta gamma dei risultati è disponibile all’indirizzo web http://www.spec.org
I benchmarks sono richiesti per essere fatti funzionare e secondo per segnalare un
insieme di regole per accertare la comparabilità e la ripetibilità.
Quali sono le limitazioni di spec. CPU2000
Il benchmark ideale per la selezione del prodotto o del fornitore sarebbe la vostra propria
quota di lavoro sulla vostra propria applicazione. Quindi bisogna considerare che nessun
benchmark standardizzato può fornire un modello perfetto delle realtà dei vostri sistemi ed
associazione di utenti particolari.
Che cosa è incluso nel pacchetto di spec. CPU2000
Spec. fornisce quanto segue sulla suite di spec. CPU2000:
•
•
•
•
•
•
Codice sorgente per i benchmarks CINT2000
Codice sorgente per i benchmarks CFP2000
Un tool per la compilazione, il funzionamento, la convalidazione e la segnalazione sui
benchmarks
Tools Pre-compilati per una varietà di sistemi operativi.
Codice sorgente per i tools di spec. CPU2000, per i sistemi non coperti dai tools precompilati
Documentazione varia, tra cui dettagli sulle funzioni e le regole che definiscono come i
benchmarks dovrebbero essere usati per fornire i risultati di spec. CPU2000.
Che cosa l'utente della suite di spec. CPU2000 deve avere (Requisiti di Sistema)
Brevemente, avete bisogno di un sistema NT o UNIX con 256MB di memoria, almeno 1GB di
disco e un insieme di compilatori poiché spec. fornisce soltanto il codice sorgente per i
benchmarcks, quindi avrete bisogno di un insieme di compilatori per il risultato che intendete
misurare:
1. Per SPECint2000: sia compilatore C e C++
2. For SPECfp2000: sia compilatore C e Fortran-90
Quale codice sorgente viene fornito? Quali sono esattamente i componenti di questa
suites?
CINT2000 e CFP2000 sono basati sulle applicazioni comput-intense fornite come codice
sorgente. CINT2000 contiene undici applicazioni scritte in C ed in 1 in C++ (252.eon) che sono
usati come benchmarks:
4.24
Nome
164.gzip
175.vpr
176.gcc
181.mcf
186.crafty
197.parser
252.eon
253.perlbmk
254.gap
255.vortex
256.bzip2
300.twolf
Ref Time
1400
1400
1100
1800
1000
1800
1300
1800
1100
1900
1500
3000
Descrizione
Programma di utilità di compressione di dati
Disposizione e percorso del circuito di FPGA
Compilatore di C
Solver minimo di flusso della rete di costo
Programma di scacchi
Elaborazione di linguaggio naturale
Tracciato del raggio
Perl
Teoria di calcolo del gruppo
Base di dati orientata oggettivamente
Programma di utilità di compressione di dati
Dispone e dirige il simulatore
CFP2000 contiene 14 applicazioni (6 Fortran-77, 4 Fortran-90 e 4 C) che sono usati come
benchmarks:
Nome
168.wupwise
171.swim
172.mgrid
173.applu
177.mesa
178.galgel
179.art
183.equake
187.facerec
188.ammp
189.lucas
191.fma3d
200.sixtrack
Ref Time
1600
3100
1800
2100
1400
2900
2600
1300
1900
2200
2000
2100
1100
Descrizione
Chromodynamics di Quantum
Modellistica poco profonda dell'acqua
Solver multi-grid nel campo di potenziale 3D
Equazioni differenziali parziali di Parabolic/elliptic
Biblioteca dei grafici 3D
Dynamics fluido: analisi di instabilità oscillatoria
Simulazione della rete neurale: teoria adattabile di risonanza
Simulazione limitata dell'elemento: modellistica di terremoto
Dispositivo ottico del computer: riconosce le facce
Chimica di calcolo
Teoria di numero: prova di primality
Simulazione di arresto del Limitato-elemento
Modello dell'acceleratore della particella
301.apsi
2600
Solves problems regarding temperature, wind, etc.
Sia i numeri usati come componente dei benchmarks che i nomi forniscono un contrassegno
come aiuto per distinguere i programmi l'uno dall'altro. Per esempio, alcuni programmi erano
aggiornati da spec. CPU95 e devono essere distinti dalla loro versione precedente.
Quale metrica può essere misurata
La suites CINT2000 e CFP2000 possono essere usati per misurare e calcolare la seguente
metrica:
CINT2000 (per i confronti di prestazioni intense di calcolo di numero intero):
• SPECint2000: La media geometrica di dodici ha normalizzato i rapporti (uno per ogni
benchmark di numero intero) una volta compilata con ottimizzazione aggressiva per
ogni benchmark.
• SPECint_base2000: La media geometrica di dodici ha normalizzato i rapporti una volta
compilata con ottimizzazione conservatrice per ogni benchmark.
• SPECint_rate2000: La media geometrica di dodici ha normalizzato i rapporti di
rendimento una volta compilata con ottimizzazione aggressiva per ogni benchmark.
4.25
SPECint_rate_base2000: La media geometrica di dodici ha normalizzato i rapporti di
rendimento una volta compilata con ottimizzazione conservatrice per ogni benchmark.
CFP2000 (per i confronti di prestazioni intense di calcolo della virgola mobile):
• SPECfp2000: La media geometrica di quattordici ha normalizzato i rapporti (uno per
ogni benchmark della virgola mobile) una volta compilata con ottimizzazione aggressiva
per ogni benchmark.
• SPECfp_base2000: La media geometrica di quattordici ha normalizzato i rapporti una
volta compilata con ottimizzazione conservatrice per ogni benchmark.
• SPECfp_rate2000: La media geometrica di quattordici ha normalizzato i rapporti di
rendimento una volta compilata con ottimizzazione aggressiva per ogni benchmark.
• SPECfp_rate_base2000: La media geometrica di quattordici ha normalizzato i
rapporti di rendimento una volta compilata con ottimizzazione conservatrice per ogni
benchmark.
Il rapporto per ciascuno dei benchmarks è calcolato usando un tempo di riferimento
determinato spec. ed il tempo di esecuzione del benchmark.
Un più alto segno significa "le prestazioni migliori" sulla data quota di lavoro.
•
Quale è la differenza fra metrica "base" e metrica “peak”
Per fornire i confronti attraverso hardware di calcolatori differenti, spec. fornisce i
benchmarks come codice sorgente. Quindi, per fare funzionare i benchmarks, devono essere
compilati. Ci è accordo che i benchmarks dovrebbero essere compilati in senso che gli utenti
compilano
i
programmi.
Ma
come
compilano
i
programmi
gli
utenti?
Da un lato, la gente potrebbe compilare appena con le opzioni ad alto rendimento generali
suggerite dal fornitore del compilatore. Dall'altro lato, la gente potrebbe sperimentare con
molti compilatori e flags (bandierine) differenti del compilatore per realizzare le prestazioni
migliori. Così, mentre spec. non può sapere esattamente come tutti usano i compilatori, può
però fornire la metrica che rappresenta le caratteristiche generali di questi due gruppi.,
quindi sono possibili due punti di riferimento:
• La metrica “base”[bassa] (per esempio SPECint_base2000).
• La metrica “peak”[picco](per esempio SPECint2000) è facoltativa ed ha requisiti meno
rigorosi.
Quale è la differenza fra metrica "rate" e "speed"?
Ci sono vari differenti modi di misurare le prestazioni dell'elaboratore. Il “one-way” deve
misurare quanto velocemente il calcolatore completa una singola operazione; ciò è una misura
di velocità. Un altro metodo è quello che deve misurare quante mansioni un calcolatore può
compire in un determinato tempo; ciò è denominata una misura di rendimento, di capienza o di
tasso.
• La metrica di velocità di spec. (per esempio, SPECint2000) è usata per confrontare
l'abilità di un calcolatore per completare le singole mansioni.
• La metrica di tasso di spec. (per esempio, SPECint_rate2000) misura il rendimento o il
tasso di una macchina che effettua un certo numero di mansioni. Tradizionalmente, la
metrica di tasso è stata usata per dimostrare le prestazioni dei sistemi del
multiprocessore.
Per la metrica di tasso, le copie multiple dei benchmarks sono fatte funzionare
simultaneamente. Tipicamente, il numero di copie è lo stesso del numero di CPU sulla macchina,
ma questo non è un requisito. Per esempio, sarebbe perfettamente accettabile fare
4.26
funzionare 63 copie dei benchmarks su una macchina 64-CPU (quindi che lascia una CPU libera
di gestire le spese generali del sistema).
Quale metrica dovrebbe essere usata per confrontare le prestazioni in spec. CPU2000
Dipende dai vostri bisogni. Spec. fornisce i benchmarks ed i risultati come tools per il vostro
uso. Dovete determinare come utilizzate un calcolatore o quali sono i vostri requisiti di
prestazioni ed allora scegliere il benchmark adatto o la metrica di spec..
Un monoutente che fa funzionare un programma comput-intenso di numero intero, per
esempio, potrebbe soltanto essere interessato in SPECint2000 o in SPECint_base2000.
D'altra parte, una persona che gestisce una macchina usata da un certo numero di scienziati
che fanno funzionare le simulazioni della virgola mobile potrebbe di più preoccuparsi di
SPECfp_rate2000 o di SPECfp_rate_base2000.
Perchè si è sviluppato spec. CPU2000 e quali sono le differenze da spec. CPU95
La tecnologia sta migliorando sempre. Mentre la tecnologia migliora, anche i benchmark
dovrebbero migliorare. Spec. ha dovuto migliorare le seguenti caratteristiche rispetto a
CPU95:
• Tempo di esecuzione: vari dei benchmarks CPU95 stavano rifinendo in meno che un
minuto su processors/systems marginale. Dato che gli attrezzi di misura di spec., i
piccoli cambiamenti o fluttuazioni nelle misure stavano avendo effetti significativi sulla
percentuale dei miglioramenti che si sono visti. Spec. ha scelto di rendere i tempi di
esecuzione per i benchmarks CPU2000 più lunghi per considerare le prestazioni future
e per impedire che questo possa essere un errore per il corso della vita della suites.
• Formato di applicazione:molte osservazioni ricevute da SPEC hanno indicato che le
applicazioni si erano sviluppate nella complessità e nel formato e che CPU95 stava
diventando meno rappresentativo di ciò che funziona sui sistemi correnti. Per
CPU2000, spec. ha selezionato i programmi con i più grandi requisiti di risorse per
fornire una miscela ad alcuni dei più piccoli programmi.
• Tipo di applicazione:spec. ha ritenuto che cerano campi di applicazione supplementari
che dovrebbero essere inclusi in CPU2000 per aumentare la varietà e la
rappresentazione all'interno della suites. Le zone quali 3D e il riconoscimento di
immagine sono state aggiunte e la compressione di dati è stata espansa.
Ha un senso tradurre i risultati di spec. CPU95 ai risultati di spec. CPU2000 o
viceversa?
Non c’è formula per convertire i risultati CPU95 ai risultati CPU2000 e viceversa; sono
prodotti differenti. Ci sarà probabilmente una certa correlazione dei risultati fra CPU95 e
CPU2000 (cioè, le macchine con i risultati più elevati CPU95 avranno spesso risultati più
elevati CPU2000), ma non c’è formula universale per tutti i sistemi.
4.13 OSSERVAZIONE SUI RISULTATI OTTENUTI DAI TEST SPEC
CPU2000 E SISOFT SANDRA “ARITMETICA CPU”
Molti dei benchmarks di spec. sono stati derivati da programmi di applicazioni pubblicamente
disponibili e tutti sono stati sviluppati per essere portatili su piattaforme hardware attuali e
future. Le dipendenze hardware sono state minimizzate per evitare di favorire una
piattaforma hardware rispetto ad un altra. Per questo motivo, i programmi di applicazione in
4.27
questa distribuzione non dovrebbero essere usati per valutare le prestazioni probabili delle
versioni disponibili in commercio. I diversi benchmarks della suite Spec CPU2000 possono
essere simili, ma non identici ai benchmarks o ai programmi con lo stesso nome che sono
disponibili da altre fonti come il benchmark “Aritmetica CPU” di SiSoft Sandra ; quindi, è non
valido paragonare i risultati del benchmark di spec. CPU2000 a qualche cosa tranne ad altri
risultati del benchmark di spec. CPU2000. (Nota: ciò inoltre significa che non è valido
confrontare i risultati di spec. CPU2000 ai più vecchi benchmarks del CPU di spec.; questi
benchmarks sono stati cambiati e dovrebbero essere considerati differente e non
paragonabile.)
Riferimenti bibliografici
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] Sito www.spec.org;
[3] F. Tortorella, "Corso di Calcolatori Elettronici", Università degli studi di Cassino,
Link: webuser.unicas.it/tortorella/
4.28
LEZIONE 5
Valutazione delle prestazioni (parte seconda)
5.1 INTRODUZIONE
Le prestazioni di un sistema di elaborazione sono uno dei parametri fondamentali nella scelta
di una macchina. Per questo motivo, non basta misurare la velocità con cui un’applicazione viene
svolta su di essa, ma bisogna scegliere fra varie metriche di misura quelle più significative a
seconda del lavoro che vogliamo compiere.
A tale fine è utile stimare il carico di lavoro (workload ossia l’insieme dei programmi eseguiti),
svolto sulla nostra macchina e compararne il tempo di esecuzione su più calcolatori. Non
sempre questa operazione è possibile, si ricorre perciò a dei programmi campione (benchmark)
che rappresentino bene i diversi possibili usi del computer. I benchmark differiscono a
seconda delle particolari esigenze dell’utente: ad esempio, possono riguardare applicazioni
tipiche del campo ingegneristico o scientifico oppure database, e-commerce o altri domini
applicativi. I risultati forniti dal benchmark possono essere di natura diversa: si tratta di
valori assoluti (come, ad esempio, il numero di fotogrammi al secondo per un programma di
visualizzazione filmati oppure le milioni di operazioni eseguite in un secondo) oppure relativi
(dato ad un sistema di riferimento il valore pari a 1, il benchmark indica valori a quest’ultimo
riferiti: una performance di 2, ad esempio, indica prestazioni doppie rispetto alla macchina di
riferimento).
I benchmark possono essere suddivisi in due grandi categorie:
• benchmark applicativi: si tratta di applicazioni che riproducono reali situazioni di
utilizzo del computer. Il venditore, pur non sapendo quale frazione del tempo verrà
dedicato a questi programmi, sa però che alcuni utenti li useranno per risolvere
problemi reali. Spesso sono raggruppati in suite, per esempio SPEC95.
• benchmark sintetici: sono dei particolari software, che tentano di modellare
programmi reali, e permettono, con i propri test, di meglio analizzare specifiche
caratteristiche di un prodotto, mettendo "sotto stress" tutte le varie componenti
del calcolatore, e, quindi, di studiarne l'architettura e l'efficacia.
5.2 BENCHMARK SINTETICI: VANTAGGI E SVANTAGGI
Sono stati ideati come modelli di applicazioni reali e possono essere mediamente vicini al
contenuto statistico dei programmi modellati in termini di istruzioni, in quanto cercano di
simulare la frequenza media degli operandi e delle operazioni utilizzati in essi. Come indica il
loro nome, il loro pregio principale è la sinteticità che comporta una semplicità di
computazione. Nella figura 5.2 e 5.3 sono mostrati due esempi di benchmark sintetici.
Un limite dei benchmark sintetici è dato dal fatto che, non conoscendo il codice sorgente dei
programmi reali, non conosciamo neanche la effettiva sequenza con la quale le istruzioni
vengono eseguite. Ciò comporta che gli accessi alla memoria del benchmark non rispecchiano
quelli del software modellato. Nell’esempio, rappresentato in figura 5.1, è evidenziato che:
• in termini statistici il contenuto del benchmark modella correttamente il software (le
istruzioni ADD e LW sono utilizzate nelle medesime proporzioni in B e in S;
• in B ad ogni LW (load word – caricamento) la memoria ha un tempo di risposta
sufficiente per rendersi disponibile alla successiva operazione ADD;
• in S, invece, le modalità di accesso alla memoria sono diverse.
5.1
BENCHMARK B
SOFTWARE S
ADD
LW
ADD
LW
ADD
ADD
MEM
………
Tempo di
Risposta
ADD
MEM
LW
MEM
LW
Tempo di
Risposta
…….
LW
Tempo di
Risposta
Figura 5.1: Modellazione del software in un benchmark.
X=1.0
Y=1.0
Z=1.0
DO 88 I=1,N,1
CALL P3(X,Y,Z)
88 CONTINUE
SUBROUTINE P3(X,Y,Z)
COMMON T,TT1,T2
X1=X
Y1=Y
X1=T*(X1-Y1)
Y1=T*(X1+Y1)
Z=(X1+Y1)/T2
RETURN
Figura 5.2: Esempio di benchmark sintetico:il benchmark whetstone.
Fu sviluppato nel 1976 da CURNOW e WICHMANN del National Physical Laboratory in Inghilterra. Questo
benchmark cerca di misurare la velocità e l’efficienza con cui un calcolatore esegue le operazioni in virgola
mobile. Il risultato di tale prova viene dato in whetstone, definita come l’istruzione “media” su virgola
mobile (dove l’attributo media è da considerarsi in termini statistici).
5.2
LOAD/STORE
BRANCHES
FIXED ADD/SUB
COMPARE
FLT ADD/SUB
FLT MUL
31.2%
16.6%
6.1%
3.8%
6.9%
3.8%
FLT DIV
FIXED MULT
FIXED DIV
SHIFTS
LOGICAL
1.5%
0.6%
0.2%
4.4%
1.6%
Figura 5.3 Instruction-mix benchmark: gibson mix.
Per Instruction-Mix si intende la misura della frequenza relative delle istruzioni in un calcolatore all’interno
di diversi programmi. Qui sopra è riportato l’esempio del Gibson Mix, sviluppato da Jack C.Gibson nel 1959
per i sistemi 1BM 704. Nonostante tali valori risalgano a quasi 50 anni fa, possono essere considerati
ancora validi.
I benchmark, quindi, in quanto programmi campione, non rispecchiano fedelmente il
comportamento dei programmi reali e, poiché sono costituiti da un insieme particolare di
istruzioni, sono programmi molto scarsamente utilizzati dall’utente ai fini pratici. Spesso i
programmatori studiano delle cosiddette ottimizzazioni ‘ad hoc’, relative ad un solo specifico
software, e può accadere che tali tecniche di miglioramento non siano applicabili anche ad
altri programmi se pur simili. I benchmark, in quanto non programmi reali, risulterebbero
essere immuni da tali ‘trucchi’, ma solo ‘in teoria’. Infatti per migliorare le prestazioni di un
benchmark, è comunque possibile effettuare ottimizzazioni del compilatore e dell’hardware
che naturalmente non garantiscono i medesimi risultati su programmi reali. Per evitare questo
problema ed avere una visione più chiara delle potenzialità della macchina che stiamo
analizzando, ad esempio, gli autori del benchmark Dhrystone (insieme a Whetstone, uno dei
più popolari benchmark sintetici) richiedono che siano riportati i risultati ottenuti sul codice
ottimizzato e su quello non ottimizzato.
5.3 SPEC
L’insieme di benchmark più popolare è le serie SPEC (System Performance Evaluation
Cooperative) nato nel 1989 per volontà di Apollo/Hewlett-Packard, DEC, MIPS e Sun, col fine
di migliorare la misurazione delle prestazioni utilizzando processi di misure più controllate e
benchmark più realistici. Consideriamo lo SPEC95; esso consta di 18 programmi: 8 relativi
all’elaborazione su interi, scritti in linguaggio C, ed i restanti 10 dedicati all’elaborazione su
virgola mobile, scritti in FORTRAN (vedi tabella 5.1)
5.3
BENCHMARK
go
m88skim
gcc
compress
li
jpeg
perl
vortex
tomcatv
swim
su2cor
hydor2
mgrid
applu
turb3d
aspi
fpppp
wave5
SPEC95 – ELABORAZIONE SU INTERI
DESCRIZIONE
Intelligenza artificiale: gioco del Go
Simulatore del chip Motorola 88k; esecuzione di un programma
Compilatore GNU C che genera codice SPARC
Compressione e decompressione di un file in memoria
Interprete LISP
Compressione e decompressione di immagini grafiche
Interprete del linguaggio PERL
Programma di gestione di una base di dati
SPEC95 – ELABORAZIONE SU VIRGOLA MOBILE
Programma per la generazione di griglie
Modello per acqua poco profonda con una griglia 513x513
Fisica quantistica: simulazione Monte Carlo
Astrofisica: equazioni idrodinamiche di Naiver Stokes
Risolutore multi-griglia in campo di potenziale 3D
Equazioni alle differenze parziali paraboliche/ellittiche
Simulazione di turbolenza isotropica e omogenea in un cubo
Risoluzione di problemi di temperatura, velocità del vento
Chimica quantistica
Fisica dei plasmi: simulazione di particelle elettromagnetiche
Tabella 5.1 I benchmark di CPU SPEC95
Come analisi delle prestazioni viene utilizzato l’indice SPEC (SPEC RATIO)
⎛ n Tref , k
⎜
INDICE SPEC = ⎜ ∏ T
⎝ k =1 new , k
⎞
⎟
⎟
⎠
1
n
dove:
n è il numero di SPEC benchmark eseguiti,
Tref , k è il tempo di esecuzione del k-esimo SPEC benchmark misurato sulla macchina di
riferimento, una Sun SPARCstation 10/40,
Tnew, k è il tempo di esecuzione del k-esimo SPEC benchmark sulla macchina in valutazione.
Tale normalizzazione fa sì che a valori maggiori dell’indice corrispondano prestazioni migliori.
Le misure si distinguono in SPECint95 e SPECfp95 a seconda che si considerino nel calcolo
benchmark su interi o benchmark su virgola mobile.
L’ultima versione della serie SPEC è la suite SPEC CPU2000 che è composta da 12 programmi
per l’elaborazione su interi e 14 per l’elaborazione su virgola mobile (vedi tabella 5.2).
5.4
SPEC2000 – ELABORAZIONE SU INTERI
NOME
gzip
DESCRIZIONE
Compressione
SPEC2000- ELABORAZIONE IN FP
NOME
wupwise
DESCRIZIONE
Quantum chromodynamics
vpr
FPGA circuit placement and routing
swim
gcc
Compilatore GNU C
mgrid
mcf
Ottimizzazione combinatoria
applu
crafty
Gioco degli scacchi
mesa
Risolutore multi-griglia in campo di potenziale
3D
Equazioni alle differenze parziali
paraboliche/ellittiche
Three-dimensional graphics library
parser
Word processing program
galgel
Dinamica dei fluidi
eon
Computer visualization
art
Modello per acqua poco profonda
Image recognition usando reti neurali
gap
Group theory, interpreter
facerec
Simulazione della propagazione delle onde
sismiche
Image recognition of faces
vortex
Object-oriented database
ammp
Chimica computazionale
bzip2
Compressione
lucas
perlbmk
twolf
Applicazione del linguaggio Perl
equake
Place and rote simulator
fma3d
sixtrack
apsi
Test di primalità
Simulazione di crash usandoil metodo degli
elementi finiti
High-energy nuclear physics accelerator design
Meteorologia: pollutant distribution
Tabella 5.2 I benchmark di SPEC CPU2000
Per l’analisi delle prestazioni utilizziamo, anche in questo caso, l’indice SPEC RATIO in cui il
tempo di esecuzione misurato sulla macchina in considerazione è normalizzato dal tempo di
esecuzione su una Sun Ultra 5_10 con un processore di 300 MHz. Gli analoghi degli indici
SPECint95 e SPECfp95 sono rispettivamente CINT2000 e CFP2000. Per mostrare i
miglioramenti delle performance consideriamo la figura 5.4 dove vengono analizzate le misure
relative agli SPEC CINT2000 e SPEC CFP2000 dei processori Intel Pentium 4 e Intel Pentium
III utilizzando un computer Dell Precision.
Figura 5.4: Influenza dei fattori dell’equazione delle prestazioni su CINT2000 E CFP2000.
Notiamo che per entrambi i processori le performance crescono linearmente con la frequenza di clock. In
generale le prestazioni del Pentium 4 sono migliori di quelle del Pentium III però nel primo i CFP2000 hanno
prestazioni superiori del CINT2000, viceversa nel secondo. Ciò è dovuto ad una tecnologia di costruzione
superiore usata per il Pentium 4 per il floating point.
5.5
5.4 ESPRESSIVITÀ DELLE METRICHE
Al fine di confrontare più sistemi è utile avvalersi di un unico valore che ne esprima in modo
sintetico le prestazioni. Ci sono più modi per fare ciò. Una prima idea fa riferimento al tempo
totale di esecuzione di due o più programmi su due diverse macchine (chiamiamole A e B).
PA =
1
TA
PB =
1
TB
dove:
PA indica le prestazioni della macchina A,
PB indica le prestazioni della macchina B,
TA è il tempo totale di esecuzione sulla macchina A,
TB è il tempo totale di esecuzione sulla macchina B.
Se TB p TA Î
PA p P B
quindi B è migliore di A in termini di prestazioni.
Adesso confrontiamo le due prestazioni relative calcolando lo speed-up di B rispetto ad A
( S AB ).
S AB =
PB
PA
che ci dice di quanto B è più veloce rispetto ad A.
5.4.1 IDEA 1 – AMT
Un metodo alternativo consiste nel calcolare la media aritmetica dei tempi (AMT –
Arithmetic Mean Time) cosi’ definita
n
AMTCPU = ⋅ ∑TCPU , K
1
n
K =1
dove:
n è il numero dei programmi benchmark,
TCPU , K è il tempo di esecuzione del K-esimo programma.
Questo tipo di approccio è utile in quanto l’AMT è direttamente proporzionale al tempo di
esecuzione ed in oltre definendo
P=
1
AMTCPU
otteniamo una misura immediata delle prestazioni. Valori piu’ bassi di AMT comportano valori
maggiori di P, quindi prestazioni migliori.
5.6
5.4.2 IDEA 2 – WMT
Il precedente metodo (AMT) è attuabile solo quando gli n programmi hanno tempi di
esecuzione confrontabili. Se ciò non avviene si allungano artificialmente i tempi di esecuzione
(cioè ripetendo wK volte il K-esimo programma) fin quando non sono paragonabili. In questo
caso si può ricorrere alla media pesata dei tempi (WMT – Weighted Mean Time)
WMT CPU
1 n
= ⋅ ∑ (w K ⋅ TCPU , K )
n K =1
dove:
n è il numero dei programmi benchmark,
TCPU , K è il tempo di esecuzione del K-esimo programma,
wK
è il peso dato al K-esimo programma ed indica la frequenza relativa di esso entro il
workload (cioè il numero di volte che è eseguito).
5.4.3 IDEA 3 - HMT
Nel caso in cui le prestazioni siano espresse come un rapporto, il tempo totale di esecuzione è
definito tramite la media armonica dei tempi (HMT – Harmonic Mean Time).
HMT CPU
⎛1 n
1
= ⎜ ⋅∑
⎜ n K =1 T
CPU , K
⎝
⎞
⎟
⎟
⎠
−1
dove:
n è il numero dei programmi benchmark,
TCPU , K è il tempo di esecuzione del K-esimo programma.
Utilizzando opportuni sistemi di pesi è possibile definire la media armonica pesata dei tempi
WHMT CPU
⎛ 1 n wK
= ⎜⎜ ⋅ ∑
⎝ n K =1 TCPU , K
⎞
⎟
⎟
⎠
−1
Per quanto riguarda le medie armoniche, esse hanno la caratteristica di enfatizzare i
programmi con minor tempo di esecuzione.
5.4.4 IDEA 4 – GMT
Se le metriche fin qui esposte non soddisfano le nostre necessità, un ulteriore metodo è la
media geometrica dei tempi (GMT – Geometric Mean Time)
5.7
⎛
⎞
GMTCPU = ⎜⎜ ∏ TCPU , K ⎟⎟
⎝ K =1
⎠
n
1
n
Questa metrica enfatizza i programmi con grosse variazioni di prestazioni.
La media geometrica gode della proprietà di essere indipendente dalla serie di dati usata per
la normalizzazione, in quanto
GMT ( X )
⎛X⎞
= GMT ⎜ ⎟
GMT (Y )
⎝Y ⎠
LA MEDIA GEOMETRICA DEI RAPPORTI È UGUALE AL RAPPORTO DELLE MEDIE GEOMETRICHE
5.4.5 IDEA 5 - GMTR
Se vogliamo esporre le prestazioni di una macchina, è significativo normalizzare il tempo di
esecuzione rispetto ad una macchina di riferimento (come abbiamo visto per l’indice SPEC) e
quindi calcolare la media geometrica dei tempi normalizzati (GMTR – Geometric Mean Time
Ratio).
1 n
GMTRCPU
⎛ n T
= ⎜ ∏ CPU , K
⎜ K =1 T
CPU , REF , K
⎝
⎛ n
⎞
⎜⎜ ∏ TCPU , K ⎟⎟
⎝ K =1
⎠
1 n
⎞
⎟
⎟
⎠
=
1 n
⎛
⎞
⎜⎜ ∏ TCPU , REF , K ⎟⎟
⎝ K =1
⎠
n
=
GMTCPU
GMTCPU , REF
dove:
n è il numero dei programmi benchmark,
TCPU , K è il tempo di esecuzione del K-esimo programma,
TCPU , REF , K
è il tempo di esecuzione del K-esimo programma sulla macchina di riferimento
rispetto alla quale si aspetta la normalizzazione,
GMTCPU è la media geometrica dei tempi di esecuzione,
GMTCPU , REF è la media geometrica dei tempi di esecuzione sulla macchina di riferimento.
I vantaggi e gli svantaggi di queste metriche saranno trattati in modo più approfondito nella
successiva lezione 6.
5.8
5.5 SCALABILITÀ
Consideriamo l’equazione delle prestazioni:
TCPU = C CPU ⋅ TC =
C CPU
= N CPU ⋅ CPI ⋅ TC
fC
dove:
TC è il periodo di clock,
CCPU è il numero dei cicli di clock,
N CPU è il numero di istruzioni eseguite dinamicamente,
CPI è il numero medio di cicli di clock per istruzione.
Come abbiamo già visto le prestazioni di un sistema sono inversamente proporzionali al tempo
di esecuzione del carico di lavoro.
P∝
Se
1
TCPU
=
1
⋅ fC
N CPU ⋅ CPI
f C raddoppia ci aspetteremmo che anche le prestazioni raddoppino, ma in realtà ciò non
avviene. Osservando la figura 5.4 possiamo riscontrare che l’incremento delle prestazioni è
sempre minore al corrispondente aumento della frequenza di clock, ciò è dovuto al fatto che la
memoria principale non riesce a tenere il passo del processore.
Si definisce scalabilità rispetto ad un parametro la “capacità di fornire maggiori prestazioni
al variare di un parametro”.
Consideriamo, ad esempio, due grandezze y1 e y2 e supponiamo di voler confrontare la loro
scalabilità rispetto al parametro x (vedi figura 5.5). Ad una medesima variazione Δx del
parametro x, le grandezze y1 e y2 si comportano in modo differente: y1 subisce una variazione
Δy1 mentre y2 subisce una variazione Δy2. Poiché Δy1 > Δy2, la grandezza y1 è più
scalabile rispetto al parametro x della grandezza y2.
Tabella 5.3: Confronto fra Pentium III e Pentium 4.
RAPPORTO
CINT2000 / FREQUENZA DI CLOCK
CFP2000 / FREQUENZA DI CLOCK
PENTIUM III
0.47
0.34
PENTIUM 4
0.36
0.39
Adesso riprendiamo la figura 5.4. Nella tabella 5.3 è riportato il valor medio dei rapporti
INDICE SPEC / f C per gli indici CINT2000 e CFP2000, calcolato relativamente all’intervallo
delle frequenze di clock, in cui lavorano i due processori (PENTIUM III e PENTIUM 4). Da
questi dati deduciamo che, per quanto riguarda il CINT2000, il Pentium III è più scalabile
rispetto alla frequenza del Pentium 4; mentre nel caso del CFP2000, la situazione è invertita:
il Pentium 4 è più scalabile rispetto alla frequenza del Pentium III. Ricordiamo comunque che
tale analisi è condotta basandosi su valori medi dei rapporti e quindi possiamo fare solo
considerazioni approssimative.
5.9
< SCALABILITÀ
> SCALABILITÀ
∆y2
∆y1
∆x
∆x
(a)
Figura 5.5: Sistemi a diversa scalabilità.
(b)
5.6 LEGGE DI AMDAHL
É intelligente, al fine di migliorare le prestazioni di una macchina, concentrare i nostri sforzi
nell’ottimizzazione delle operazioni più utilizzate in quanto rendere più veloce un evento più
frequente è certamente più utile che ottimizzare un evento raro e spesso è anche più facile.
Tale principio prende il nome di legge di Amdahl o legge dei rendimenti decrescenti.
Legge di Amdahl: Il miglioramento di prestazioni che può essere ottenuto usando alcune
modalità di esecuzione più veloci è limitato dalla frazione di tempo nella
quale tali modalità possono venire impegnate.
Se ad esempio consideriamo un certo intervallo di tempo nel quale è eseguito un programma e
notiamo che in esso una porzione consistente è occupato dall’esecuzione di un unico tipo di
operazioni, possiamo chiederci di quanto devo velocizzare queste operazioni per far sì che il
programma sia eseguito x volte più rapidamente. Possiamo utilizzare la seguente relazione:
Tdopo =
Tmigliorabile
a
+ Tnon − migliorabile
dove:
Tdopo è il tempo di esecuzione dopo il miglioramento,
a è lo speed-up della parte di sistema migliorabile,
Tnon − migliorabi le è il tempo di esecuzione non influenzato dal miglioramento.
La legge di Amdahl può essere espressa in maniera diversa introducendo il fattore di speedup, il quale ci informa su quanto più velocemente un lavoro verrà effettuato usando la
macchina migliorata rispetto alla macchina originale (quindi fornisce un’informazione
sull’incremento delle prestazioni).
S=
Pdopo
Pprima
=
Tprima
Tdopo
=
Tprima
Tmigliorabile
a
+ Tnon − migliorabile
5.10
=
1
p
+ (1 − p )
a
dove p = Tmigliorabile Tprima è la percentuale di tempo migliorabile nella macchina originale
TEMPO DI ESECUZIONE PRIMA
Å---------------------------------------------------------------------------------------------Æ
Tmigliorabile
Tnon - migliorabile
p
1-p
Tmigliorabile /a
Tnon - migliorabile
p/a
1-p
Å------------------------------------------------------------------------Æ
TEMPO DI ESECUZIONE DOPO
Nel disegno sopra è evidenziato come la riduzione del tempo migliorabile tramite il fattore a
riduce il tempo di esecuzione complessivo.
100
90
80
Speed up (S)
70
60
50
40
30
20
10
0
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1
Frazione Migliorabile del Sistema (p)
Figura 5.5: Legame tra speed-up e la frazione migliorabile del sistema.
La figura 5.5 (sopra) ci mostra il legame esistente tra lo speed-up e la frazione migliorabile
del sistema (p). Come si può notare è importante, al fine di avere un significativo aumento
delle prestazioni, che p sia maggiore di 0.8 cioè che il tempo migliorabile sia almeno l’80% del
tempo di esecuzione totale del programma prima dei miglioramenti.
5.11
Una volta applicati i miglioramenti sulla parte più utilizzata è opportuno concentrarsi anche
sulla parte meno utilizzata del sistema, infatti, come possiamo notare dal prossimo esempio, è
facile raggiungere dei livelli di ottimizzazione oltre i quali non solo non si può andare, ma che
sono anche difficili da raggiungere.
Esempio 5.1
Consideriamo un generico programma e supponiamo che il 90% di esso possa essere eseguito in
parallelo ( cioè è migliorabile facendo lavorare più processori) Æ p = 0.9
S=
1
p
+ (1 − p )
a
A
1
2
10
100
1000
10000
S
1.0
1.8
5.3
9.2
9.9
9.99
Possiamo notare che con a → ∞ S = 10 ( valore massimo dello speed-up ).
Per a = 1000 si è quasi raggiunto il livello massimo di miglioramento (S=9.9) e possiamo
osservare che anche aumentando di 9000 unità il valore di a (in pratica, aggiungendo 9000
processori) otteniamo un aumento di S minore del 1%. Ciò dimostra che ad alte prestazioni per
avere piccoli miglioramenti di S bisogna fare grandi sforzi senza comunque ottenere risultati
proporzionali ad essi.
5.7 METRICHE INDIPENDENTI DALL’HARDWARE
Un errore comune è credere che le metriche indipendenti dall’hardware forniscano una
valutazione corretta delle prestazioni. Molti progettisti hanno studiato metodi che non fanno
riferimento alla misura del tempo di esecuzione. In passato esisteva un metodo che si basava
sull’utilizzo della dimensione del codice (NPROG) come misura della velocità, considerando il
programma più snello il più veloce.
5.12
PROGR:
______________
______________
___
___ CICLO(i)
___
______________
______________
CPU:
NPROG
______________
______________
___
___ CICLO(i,1)
___
___
___
___
….
….
___
___
___
N PROG ≤ NCPU
CICLO(i,2)
NCPU
CICLO(i,N)
______________
______________
Figura 5.6: Distinzione fra NPROG E NCPU.
NPROG indica il numero statico di istruzioni che costituisce il programma mentre NCPU è il numero di istruzioni
eseguite dinamicamente. Nella figura è mostrato l’esempio di un programma in cui è inserito un ciclo di i istruzioni
che deve essere svolto dalla CPU N volte (NPROG = i e NCPU = N * i ).
Ciò era importante qualche tempo fa (questo metodo fu pensato agli inizi degli anni 70),
quando la disponibilità di spazio in memoria era una risorsa critica ed era preferibile avere
programmi di dimensioni minori. Per le prestazioni tale considerazione non è valida. Prendiamo
l’esempio del calcolatore CDC 6600, i cui programmi sono in termini di N PROG 3 volte più
grandi di quelli del Burroughs B5500. Per quanto detto sopra la seconda macchina dovrebbe
essere assai più veloce della prima, ed invece la CDC 6600 esegue i programmi Algol 60 6 volte
più velocemente del Burroughs B5500.
Non è quindi opportuno usare il numero statico di istruzioni (NPROG) come parametro di
confronto fra architetture diverse. Ci dobbiamo limitare ad utilizzarlo per comparare codice
per lo stesso Instruction Set.
MIPS
Fra le metriche proposte per indicare le prestazioni di una macchina, vi sono i MIPS (milioni di
istruzioni per secondo).
MIPS
=
N CPU
10 6 ⋅ T CPU
La caratteristica positiva del MIPS è che risulta semplice da comprendere: a valori di MIPS
levati corrispondono macchine più veloci. Tuttavia questa metrica è affetta da 3 problemi:
5.13
•
per prima cosa dipende dall’insieme di istruzioni e non tiene conto delle loro
caratteristiche. Per questo non si possono utilizzare i MIPS per confrontare computer
con diversi Instruction Set (il conteggio delle istruzioni sarebbe senz’altro diverso);
•
un secondo aspetto è che i MIPS variano in base al programma considerato, quindi per
una data macchina non esiste un MIPS unico che sia valido per tutti i programmi;
•
infine, paradossalmente, l’indice MIPS può variare in modo inversamente proporzionale
alle prestazioni.
Esempio 5.2
Numero di Istruzioni (NCPU)
(espresse in miliardi)
A
B
C
COMPILATORE #1
5
1
1
COMPILATORE #2
10
1
1
A
1
___
CPI
B
2
C
3
Si consideri 2 diversi compilatori e 3 classi di istruzioni da eseguire A, B e C (vedi tabella
sopra). Supposto che la frequenza di clock fc sia 500 MHz, vogliamo sapere quale sequenza di
codice è eseguita più velocemente, confrontando tempo di esecuzione TCPU e MIPS.
TCPU
C CPU
1 3
=
=
⋅ ∑ N CPU , K ⋅ CPI K
fC
f C K =1
1
⋅ (5 ⋅1 + 1 ⋅ 2 + 1 ⋅ 3) ⋅10 9 = 20 sec
6
500 ⋅10
1
=
⋅ (10 ⋅1 + 1 ⋅ 2 + 1 ⋅ 3) ⋅10 9 = 30 sec
6
500 ⋅10
TCPU 1 =
(
)
TCPU 2
(
)
Quindi, secondo il tempo di esecuzione, il compilatore #1 risulta essere più veloce del
compilatore #2. Calcoliamo adesso i valori di MIPS nei due compilatori.
5.14
MIPS 1 =
MIPS
2
=
N CPU
TCPU ⋅ 10 6
N CPU
TCPU ⋅ 10 6
=
(5 + 1 + 1) ⋅ 10 9
20 ⋅ 10 6
1
=
= 350 MIPS
(10 + 1 + 1) ⋅ 10 9
30 ⋅ 10 6
2
= 400 MIPS
Come possiamo notare il compilatore #2 ha MIPS più elevato: da questo potremmo supporre
che il compilatore #2 sia migliore del compilatore #1, informazione smentita dai risultati
precedenti ( il compilatore #1 è più veloce Æ prestazioni di #1 maggiori di #2 ma MIPS di #1
inferiore di #2 Æ CONTRADDIZIONE! ).
IPC
Un’altra metrica proposta sono gli IPC ( numero medio di istruzioni per ciclo di clock).
IPC =
IPC =
1
CPI
1
N
N CPU
106
= CPU =
=
⋅ MIPS
fC
CPI CCPU TCPU ⋅ f C
Dall’ultima relazione si deduce che gli IPC sono caratterizzati dagli stessi problemi dei MIPS,
in quanto le due metriche sono direttamente proporzionali. A riprova riprendiamo l’esempio
precedente 5.2 e calcoliamo i valori di IPC per i due calcolatori.
IPC 1 =
IPC 2
N CPU 1
7 ⋅ 10 9
=
= 0 .7
10 ⋅ 10 9
C CPU 1
N CPU 2 12 ⋅ 10 9
=
=
= 0 .8
C CPU 2
15 ⋅ 10 9
IPC (#1) < IPC (#2)
Æ CALCOLATORE #2 MIGLIORE DEL CALCOLATORE #1 Æ SBAGLIATO!!!
Come dimostrano i MIPS e gli IPC, proposte alternative di misure delle prestazioni portano a
risultati parzialmente errati e in generale scorretti.
5.8 ALTRI INDICI USATI
Forniamo adesso altri indici per le prestazioni usati in diversi ambiti.
-ICOMP (Intel Comparative Microprocessor Performance Index)
Si tratta di un indice usato in ambito industriale, creato da Intel per stabilire la differenza di
velocità tra una CPU Intel ed un’altra. É utile soprattutto nell’ambiente del marketing per
5.15
promuovere il prodotto agli acquirenti. La prima versione ICOMP 1.0 fu introdotto il 16
ottobre 1985 quando uscì sul mercato il processore Intel 80386DX, mentre la seconda
ICOMP 2.0 nacque otto anni dopo, il 22 marzo 1993, in occasione del lancio dell’Intel Pentium
(P5). L’ultima versione ICOMP è la 3.0 utilizzata dal Gennaio 1998 quando comparve il Pentium
II (Deschutes).
- GSP (Global System Power)
GSP =
n
∑U
K =1
K
dove:
n è il numero di processori,
(T − Tstallo, K ) è il fattore di utilizzazione,
U K = CPU , K
TCPU , K
TCPU , K è il tempo di esecuzione di un programma sul processore K,
Tstallo , K è il tempo in cui non avvengono operazioni, ma in cui lavora solo la memoria relativa al
processore K ed il resto del sistema attende la sua risposta (vedi lezione 17-18 per ulteriori
approfondimenti).
Si tratta di una metrica composita perché tratta il lavoro di piu’ processori. Esso è utilizzato
soprattutto in campo accademico.
- CPKI (Cycles per Thousands Instructions)
Questo indice può essere considerato l’evoluzione del parametro CPI ed indica il numero di
cicli di clock per migliaia di istruzioni. Il CPKI viene prevalentemente nell’ambiente dei
progettisti.
5.9 ALTRI ERRORI COMUNI
Affinché i benchmark siano un utile strumento di confronto tra più macchine è necessario che
essi siano eseguiti in modo corretto. Un errore comune è lo scalare senza cura un benchmark,
eseguendone solo una parte o riducendo i dati in ingresso. Ad esempio se il nostro benchmark
prevede come input una matrice 1000*1000 sarà scorretto adoperarne una più piccola in
quanto nel secondo caso il sistema sarà stressato in modo minore ed in generale i risultati
prodotti nei due casi saranno diversi. Altra regola da seguire è quella di non includere nel
nostro pacchetto di valutazione troppi benchmark semplici, in quanto possono non evidenziare
alcune caratteristiche importanti. Allo stesso modo non dobbiamo fare uso di troppi pochi
benchmark semplici perché ciò potrebbe mettere eccessivamente in risalto una
caratteristica. In oltre bisogna ricordarsi che a seconda delle proprietà del sistema che si
intende studiare, bisogna scegliere il benchmark più opportuno.
5.16
LEZIONE 6
Valutazione delle Prestazioni (parte terza)
6.1 METRICHE BASATE SUL TEMPO
Come visto dai capitoli precedenti, il miglior modo di misurare le prestazioni, è fare
riferimento a metriche basate sul tempo, poiché sono maggiormente rappresentative: meno
tempo impiego per eseguire i miei programmi, più veloce è la mia macchina.
A questo proposito possiamo utilizzare una delle seguenti medie:
Dove TCPU è il tempo
AMTCPU
necessario a svolgere un
dato programma K escluso
il tempo di I/O e quello
impiegato per eseguire
altri programmi
1 n
= ⋅ ∑ TCPU , K
n K =1
Questa è la media aritmetica (Arithmetic Mean Time).
E’ facile da usare, ma viene troppo influenzata dai valori estremi (molto alti e/o molto bassi).
Inoltre, in questo modo stiamo supponendo che tutti i programmi vengano eseguiti lo stesso
numero di volte, ignorando così la composizione del workload reale .
Un modo per ovviare a questo inconveniente, può essere quello di “pesare” i vari tempi con un
coefficiente WK , che indica quante volte viene ripetuto il programma K. Così un programma
usato più volte, può pesare di più rispetto ad un altro che viene eseguito di rado.
Ciò di cui stiamo parlando è la media aritmetica pesata (Wheighted Mean Time) di cui sotto è
riportata la formula:
WMTCPU
1 n
= ⋅ ∑ W K ⋅ TCPU , K
n K =1
Un' altra media potrebbe essere quella armonica (Harmonic Mean Time)
HMTCPU = (
1 n 1
⋅∑
n K =1 TCPU ,K
)−1
la quale da maggior peso ai programmi più corti, in quanto al suo interno compare l’inverso del
Tempo di CPU.
Infine abbiamo la media geometrica (Geometric Mean Time)
GMTCPU = (
n
∏T
CPU , K
)1 n
K =1
che da risalto ai programmi con grandi variazioni di prestazioni.
6.1
Una proprietà importante è che il rapporto delle medie geometriche è uguale alla media
geometrica del rapporto. Quindi se stiamo valutando le prestazioni di due macchine, ad
esempio A e B, si può ottenere lo stesso valore relativo, sia che si normalizzi rispetto ad A,
sia che lo si faccia rispetto a B.
6.1.1 Medie normalizzate
Esiste anche una versione normalizzata delle precedenti medie, che consiste nell’allineare i
valori ottenuti, rispetto ad una macchina di riferimento. Questo effetto si ottiene dividendo
gli argomenti delle sommatorie delle medie aritmetica ed aritmetica pesata e della
T
produttoria della media geometrica, per un valore CPU , REF , K che sta ad
indicare il tempo di CPU (per come l’abbiamo definito all’ inizio del capitolo) del calcolatore di
riferimento. Nella media armonica invece, questo valore viene moltiplicato per il reciproco del
TCPU , K
:
AMTRCPU
1 n TCPU , K
= ⋅∑
n K =1 TCPU , REF , K
WMTRCPU
T
1 n
= ⋅ ∑ WK ⋅ CPU ,K
n K =1
TCPU ,REF ,K
HMTRCPU = (
GMTRCPU = (
1 n TCPU ,REF ,K
⋅∑
n K =1 TCPU ,K
n
∏T
K =1
TCPU ,K
Dove la R nelle
formule sta per
rapporto (ratio)
)−1
)1 n
Questa è la media usata
per valutare le
prestazioni nello
SPEC95
CPU , REF , K
6.2 L’INDICE DI PRESTAZIONI
Queste medie possono essere utilizzate nelle versioni normalizzate e non, per definire un
indice di prestazioni P, dato dal loro reciproco rispetto al tempo.
6.2
Secondi
P≡
1
XMTCPU
La X indica una
qualsiasi delle
medie
Oppure nel caso normalizzato
P≡
1
XMTRCPU
Come accennato prima, per lo SPEC95
P≡
1
GMTRCPU
6.3 DATI FUORVIANTI
Andiamo adesso, con l’ ausilio di alcuni esempi, ad evidenziare quali sono però i problemi nell’
uso di queste medie temporali.
6.3.1 Ambiguità con media aritmetica.
Prendiamo due calcolatori (A e B); facciamo eseguire loro due programmi (prog. 1, prog. 2).
Come si vede dai dati riportati in tabella, il programma 1 viene eseguito da A, 10 volte più
velocemente rispetto a B.
Invece per il programma 2 è il calcolatore B ad essere più veloce, in quanto esegue il suo
lavoro in 100 secondi contro i 1000 di A.
Ora, effettuando le medie, emerge un dato interessante: con AMT, B sembra essere 9.1 volte
più veloce di A; però con GMT le prestazioni risultano identiche! Quindi in questo modo non
possiamo dire quale sia la macchina più veloce.
6.3
Tabella 6.1 Esperimento con AMT e GMT.
Macchina A
Macchina B
TCPU (prog.1)
1s
10s
TCPU (prog.2)
1000s
100s
500.5s
55s
B è 9.1 volte più veloce di A
31.6
31.6
A è veloce come B
AMTCPU
(prog.1, prog.2)
GMTCPU
(prog.1, prog.2)
Che succede se usiamo la media aritmetica normalizzata?
Ripetiamo adesso lo stesso esperimento di prima, ma normalizziamo i risultati rispetto ad A.
Anche ora per il programma 1, B è 10 volte più lenta, mentre per il secondo programma è 10
volte più veloce. Dalle due medie però abbiamo ancora dati in conflitto tra loro. Infatti, se con
la media aritmetica normalizzata dovrebbe essere ancora B la macchina più potente, secondo
la media geometrica normalizzata i due calcolatori sono equivalenti. In sostanza, per come
abbiamo effettuato le prove, non possiamo dire nulla su quale sia il sistema più performante.
Tabella 6.2 Esperimento con le medie normalizzate rispetto ad A.
Macchina A
Macchina B
TCPU (prog.1)
1
10
TCPU (prog.2)
1
0.1
1
5.05
A è 5.05 volte più veloce di B
1
1
A è veloce come B
AMTRCPU
(prog.1, prog.2)
GMTRCPU
(prog.1, prog.2)
Normalizzando rispetto a B, con AMTR, sembra essere questa la macchina migliore,
nonostante non sia stato cambiato nulla rispetto all’ esperimento iniziale: i programmi ed i
calcolatori sono sempre gli stessi! GMTR continua invece a dirci che i due calcolatori hanno le
stesse prestazioni in media. Quindi si vede che è facile costruire dei risultati, combinando ad
hoc i dati e le medie.
Tabella 6.3 Esperimento con le medie normalizzate rispetto a B.
Macchina A
Macchina B
TCPU (prog.1)
0.1
1
TCPU (prog.2)
10
1
5.05
1
B è 5.05 volte più veloce di A
1
1
A è veloce come B
AMTRCPU
(prog.1, prog.2)
GMTRCPU
(prog.1, prog.2)
6.4
I problemi principali sono quindi che, usando la media aritmetica in un rapporto, i risultati sono
dipendenti dalla macchina di riferimento. Per la media geometrica questo non è vero in quanto,
come dicevamo all’inizio, la media geometrica del rapporto è uguale al rapporto delle medie
geometriche, quindi questa metrica è indipendente dai dati usati per la normalizzazione.
6.3.2 Ambiguità della media geometrica
A questo punto la media geometrica (normalizzata e non), sembrerebbe essere la miglior
metrica per valutare le prestazioni dei calcolatori. Purtroppo non è così, infatti:
1. Al contrario della media aritmetica, non è proporzionale al tempo totale d’ esecuzione,
quindi è anche meno intuitiva.
2. Non rispetta il principio fondamentale della misurazione delle prestazioni!
Non è infatti possibile predire attraverso essa il tempo d’esecuzione poiché non tiene traccia
del tempo totale d’esecuzione.
Cerchiamo di chiarire con un esempio:
I programmi ed i calcolatori sono sempre gli stessi dell’ esperimento sintetizzato nella Tab. 1.
Questa volta però, il programma 1 è stato fatto girare 100 volte. Per cui è come se avessimo
usato una WMT con pesi W1 = 100 e W2 = 1. Quindi considerando il tempo complessivo, A e B
avrebbero le stesse prestazioni, solo se il programma 1 venisse eseguito 100 volte più
frequentemente del programma 2. C’ è sempre la differenza di un ordine di grandezza tra i
tempi, relativi a ciascun programma, di A e di B, ma ora i tempi totali spesi dalle due macchine
per eseguire entrambi i programmi sono gli stessi: 1100 secondi.
Infatti adesso si vede che, anche con la media aritmetica, A e B hanno la stessa velocità.
Tabella 6.4 Esperimento con ripetizione del prog. 1.
TCPU
(prog.1 * 100)
TCPU (prog.2)
AMTCPU
(prog.1, prog.2)
GMTCPU
(prog.1, prog.2)
Macchina A
Macchina B
100s
1000s
1000s
100s
550s
550s
A e B hanno la stessa velocità
316s
316s
A e B hanno la stessa velocità
Il problema sopra enunciato riguardo alla GMT diventa quindi evidente considerando che,
nonostante pure GMT sia pesata, se W1 fosse uguale a 200, la GMT, al contrario della media
aritmetica, darebbe ancora risultati uguali, ignorando così la proporzionalità con il tempo
totale d‘esecuzione.
Svolgendo i conti:
6.5
AMTCPU
1 n
1
= ⋅ ∑ TCPU , K = ⋅ (200 + 1000 ) = 600
n K =1
2
AMTCPU
1 n
1
= ⋅ ∑ TCPU , K = ⋅ (2000 + 100 ) = 1050 macchina B
n K =1
2
GMTCPU = (
n
∏ TCPU ,K
K =1
GMTCPU = (
n
∏ TCPU ,K
K =1
macchina A
)1 n = (200 ⋅1000 )1 / 2 = 447.2 macchina A
)1 n = (2000 ⋅100 )1 / 2 = 447.2 macchina B
6.4 CONCLUSIONI SULLE METRICHE
In ogni caso, non si può definire un carico di lavoro (Workload), per fare previsioni, con GMTR
per 3 o più macchine. La soluzione consiste quindi, nel misurare prima uno Workload reale;
attribuire poi dei pesi secondo la reale frequenza d’ utilizzo dei programmi, oppure se non è
possibile, normalizzare, facendo sì che venga speso lo stesso tempo sullo stesso programma in
più macchine, rendendo espliciti i pesi e rendendo così possibile predire il tempo d’ esecuzione
di un carico analogo. Infine se abbiamo bisogno di normalizzare rispetto ad una macchina
specifica, questa deve essere l’ultima operazione, dopo aver sintetizzato le prestazioni con i
pesi adeguati.
In figura 6.1 è riassunta la procedura sopra descritta
6.6
Definisco lo workload
w1
t1
w2
w3
w4
w5
t2
t3
t4
t5
Estraggo i benchmark
w1’
w2’
w3’
w4’
t1’
t2’
t3’
t4’
w5’
t5’
Uso i benchmark
w1’’
w2’’
w3’’
w4’’
t1’’
t2’’
t3’’
t4’’
w5’’
t5’’
Estrapolo prestazioni
^
w1
^
t1
^
w2
^
w3
^
w4
^
w5
^
t2
^
t3
^
t4
^
t5
Figura 6.1 diagramma riassuntivo.
Dovrebbe essere ormai chiaro, che per avere un’ idea concreta di quali possano essere le
prestazioni di una macchina, si debba fare riferimento al tempo d’ esecuzione dei programmi.
Gli altri metodi utilizzati, sono fallaci sotto molti punti di vista, oppure sono stati usati in
maniera non corretta, ad esempio comparando senza le adeguate considerazioni i risultati
ottenuti su piattaforme differenti. Quindi anche quando vogliamo sintetizzare i risultati,
dobbiamo fare riferimento al tempo. Rivestono allora particolare interesse la AMT,che tiene
traccia i tempi d’ esecuzione e la WMT, che grazie ai pesi permette di bilanciare i benchmark
in un quadro di valutazione complessiva.
6.5 UN PO’ DI STORIA…
E interessante notare che non è stato così immediato arrivare a queste conclusioni. La ricerca
di un numero facile da calcolare (per i produttori) e da capire (per gli utenti), che
identificasse la qualità del sistema, ha portato spesso, anche a causa dell’evoluzione dei
computer, ad usare metriche diverse da quelle temporali ed a volte anche indipendenti
dall’hardware (dimensione del codice).
La prima in assoluto fu il tempo necessario ad effettuare una ADD, dato che la durata per
eseguire le istruzioni era più o meno la stessa per tutte. Quando questo incominciò a non
essere più vero, si pensò di rimediare misurando, invece che una sola istruzione, un mix di
6.7
queste, tenendo conto delle frequenze relative, che se misurate in cicli di clock dà il CPI: cicli
di clock per istruzione.
6.5.1 I MIPS e le loro varianti.
Scorrendo avanti nel tempo, troviamo i MIPS (milioni di istruzioni al secondo), il loro pregio è
che a valori più alti, corrispondono prestazioni superiori, solo che non tengono conto della
struttura delle istruzioni; non possono essere confrontati risultati di macchine differenti,
perché si sa a priori che il risultato sarà diverso. Addirittura, anche all’interno della stessa
macchina, i MIPS variano a seconda dei programmi eseguiti. Praticamente i MIPS sono utili
solo per confrontare due diverse implementazioni dello stesso programma sulla stessa
macchina.
Nonostante ciò, grazie alla loro intuitività, ebbero ampia diffusione. Ad “aggravare” la
situazione vennero poi i MIPS di picco, così detti perché venivano calcolati facendo eseguire
una combinazione di istruzioni che minimizzava il CPI. Senza considerare però che tale
combinazione poteva anche essere del tutto inutile ai fini pratici.
Per esempio se ho il set di istruzioni di Tab. 5
Tabella 6.5 set di istruzioni
Tipo
CPI
A
1
B
2
C
3
e voglio ottenere un valore di MIPS di picco uguale a 500, con un processore con frequenza di
clock ( f c ) di 500MHz, potrò utilizzare solo istruzioni di tipo A visto che
CPI =
fc
(10
6
⋅ MIPS
)=1
E’ importante sottolineare, che risulta piuttosto difficile immaginare che, un programma
costituito di sole istruzioni di tipo A abbia una qualche valenza pratica o sia addirittura
rappresentativo per la velocità dell’ intero sistema!
Un caso celebre fu il processore Intel i860 venne pubblicizzato come capace di eseguire due
istruzioni in virgola mobile ed una intera in un ciclo di clock a 50MHz. Gli furono attribuiti 100
MFLOPS (milioni di istruzioni in virgola mobile al secondo) e 150 MOPS (milioni di operazioni al
secondo). Quando fu commercializzato, nel ’91 (2 anni dopo il suo annuncio) però, lavorava alla
frequenza di 40MHz, ma il processore concorrente MIPS R3000, che aveva valori come 16
MFLOPS e 33 MOPS di picco, con i benchmark SPEC risultò essere il 15% più veloce dell’
Intel, quando invece sarebbe dovuto essere circa 5 volte più lento.
6.8
Allo scopo di rendere significativi i MIPS, anche con ISA (set di istruzioni) differenti, sono
stati poi “relativizzati” rispetto ad un calcolatore di riferimento (come avvenne anche per lo
SPEC).
Per cui adesso:
Dove TREF è in tempo d’esecuzione sulla
MIPSR =
TREF
⋅ MIPS REF
TNEW
macchina di riferimento;
TNEW è il tempo sul calcolatore che
stiamo testando;
MIPSREF sono i MIPS corrispondenti al
calcolatore di riferimento
Purtroppo anche questo sistema di valutazione è affetto da svariati problemi!
Innanzitutto i MIPS sono proporzionali al tempo d’esecuzione, soltanto per una determinata
combinazione dell’ input, per un determinato programma.
In più ci sono delle difficoltà legate anche alla macchina di riferimento. Poiché se negli anni
’80 il calcolatore di riferimento era il VAX-11/780, nel futuro sarebbe stato sempre più
difficile reperirlo e far girare i nuovi programmi su di esso. E le versioni dei software?
Aggiornarle avrebbe potuto far variare le prestazioni del VAX… quindi cambiare la macchina
di riferimento, avrebbe reso, anche nella migliore delle ipotesi, molto difficilmente
comparabili i risultati ottenuti successivamente.
6.5.2 Benchmark sintetici e Kernel - benchmark
Vista l’impossibilità di avere un riferimento fisso ed anche a causa dell’ evoluzione tecnologica
che vedeva CPU sempre più sofisticate e dipendenti da tanti altri fattori, come le pipeline e le
memorie, ogni speranza sulla validità dei vari MIPS, MOPS e MFLOPS venne meno, dato che,
anche conoscendo l’ ISA e le istruzioni da eseguire, non era più possibile risalire al tempo
d’esecuzione, nonché ai MIPS.
Così vennero introdotti i benchmark sintetici. Come il Whitestone e la sua versione successiva
Dhrystone, che consistevano in un “programma esempio”. La misura delle prestazioni era data
dal numero di iterazioni del benchmark stesso.
Ed ancora i kernel-benchmark (Linpack e Loop Livemoore) , ossia un frammento particolare di
un programma, che in base al suo tempo d’esecuzione, dovrebbe dare un’ idea della potenza
della CPU. Il problema è che è stato dimostrato che in questo modo le prestazioni vengono
sovrastimate.
6.5.3 I programmi giocattolo
Un altro passo (falso) in avanti fu fatto tentando di predire e misurare le prestazioni
attraverso programmi che riproducevano semplici giochi, il cui risultato era noto a priori, come
il setaccio di Eratostene, il gioco dei 15 o l’esecuzione di una Qucksort. La diffusione di questo
tipo di benchmmark fu dovuta alla facilità di implementazione di questi “programmini” sulle
varie macchine, inoltre le piccole dimensioni ne rendevano facile l’utilizzo in simulatori
software, ad esempio per i test delle cpu RISC che iniziarono a comparire nei primi anni ’80.
6.9
6.6 SCELTE PROGETTUALI
Come si è visto, sin dall’ inizio c’è stato sempre un grande sforzo per definire un sistema
valido per la valutazione delle prestazioni, perché questo è uno parametri fondamentali,
insieme al costo ed al consumo
d’ energia, sia nella fase di progetto che, da parte dell’ utente, nella fase d’acquisto di un
computer.
Questi tre fattori però sono in contrasto tra loro. Ad esempio abbassare il tempo
d’esecuzione (prestazioni migliori), comporta uno sforzo progettuale e di ricerca notevole, che
ha come conseguenza quella di aumentare i costi ed il consumo d’energia. Oppure se volessimo
progettare una CPU a basso consumo dovremmo probabilmente cedere qualcosa in termini di
prestazioni; in più anche qui il lavoro dei ricercatori e l’implementazione di nuove tecnologie
comporterebbe un aumento dei costi… Allora visto che il computer ideale (alte prestazioni,
basso consumo, basso costo), sarebbe veramente difficile (se non impossibile) da realizzare, si
preferisce fare delle scelte a priori, in base al campo d’applicazione previsto.
Per un progetto per alte prestazioni, come un server o una workstation, il costo ed il consumo
passano in secondo piano rispetto alla velocità. Per un sistema embedded, come un telefonino o
un PDA invece, la velocità non è più così vincolante, piuttosto si deve fare molta attenzione all’
assorbimento d’ energia ed al costo, poiché essendo oggetti di largo consumo, pensati per
essere sempre a portata di mano, dovranno innanzi tutto essere relativamente economici ed
avere una lunga autonomia. Per i computer da casa – ufficio infine, si richiede un compromesso
tra le due categorie precedenti, infatti siamo disposti a spendere di più, rispetto ad un
cellulare, ma sicuramente molto meno rispetto ad un server ed avendo la possibilità di
utilizzare la rete elettrica non siamo più vincolati alla scarsa autonomia delle batterie.
6.6.1 Il prezzo
Per ultimo (ma non meno importante) c’è da determinare il costo del calcolatore.
Come accennato prima c’e il costo di ricerca e sviluppo, ed il costo dei componenti, legato
anche alla tecnologia con la quale sono stati sviluppati. Comunque questi non sono gli unici
fattori, infatti c’è da tener conto del lavoro di assemblaggio e di tutta l’area relativa all’
aspetto commerciale (il marketing), nonché al margine di guadagno desiderato dal venditore
del sistema. Come se non bastasse c’è poi da tener conto che ogni sei mesi, un anno al massimo,
i componenti scelti diventano obsoleti, per cui il prezzo andrà ritoccato e le nostre scelte non
saranno più ottimali. Quindi la cosa importante è avere ben presente gli obiettivi che si
vogliono raggiungere, cercando di individuare il miglior compromesso.
6.7 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
6.10
LEZIONE 7
Standard IEEE-754 per le operazioni floating point
7.1 INTRODUZIONE
Lo sviluppo dei calcolatori elettronici è stato caratterizzato negli anni 80 e 90 da un
inarrestabile aumento della potenza di calcolo e dal costante miglioramento delle funzionalità
supportate. In particolare, i processori supportano tipicamente due tipi di rappresentazione
dei numeri: la rappresentazione dei numeri interi e la rappresentazione in virgola mobile
(floating point). Il motivo principale per l’introduzione dei numeri in floating point sta nella
possibilità di utilizzare un intervallo di numeri più ampio per effettuare i calcoli riducendo il
numero di cifre disponibili a parità di bit utilizzati per la codifica.
7.2 RAPPRESENTAZIONE FLOATING POINT
I numeri in floating point corrispondono ai numeri frazionari, rappresentabili su un numero
finito di cifre, nella forma:
(-1)S × A,B × 2 C
S è il segno del numero, la sua mantissa è A,B e il suo esponente è C. In questo caso la base di
rappresentazione e’ la base 2. Un’altra base che viene utilizzata e’ la base 10 (tramite codifica
BCD o Binary Coded Decimal). La base 2 e’ piu’ comoda da usare per ragioni di efficienza
implementativa. Nel seguito si fara’ riferimento a questa base di rappresentazione.
Nella aritmetica in floating point la prima decisione da prendere è quanti bit devono essere
assegnati ad ognuno di questi valori numerici. Una volta fissato il numero totale di bit
disponibili, resta da scegliere quanti bit assegnare a esponente e mantissa. Risulta chiaro,
quindi, che non tutti i numeri possono essere rappresentati, ma solo un sottoinsieme limitato.
Ad esempio, un comune schema per rappresentare un numero in floating point usando 32 bit è
mostrato in Figura 7.1.
1 bit
segno
8 bit
23 bit
esponente
mantissa
Figura 7.1. Rappresentazione floating point.
7.2.1 Numeri floating point normalizzati
La notazione scientifica adotta una forma standard che elimina gli zeri iniziali per avere
esattamente una cifra diversa da zero a sinistra della virgola decimale. Così –8,53 × 10-2 è in
notazione scientifica. Analogamente, un numero floating point normalizzato ha una mantissa la
cui cifra più a sinistra è diversa da zero; per i numeri floating point normalizzati in base due,
la sola cifra diversa da zero possibile è 1. Poiché il bit più a sinistra di una mantissa
normalizzata è sempre lo stesso, non è necessario codificarlo con un bit della
rappresentazione (così si risparmia un bit); perciò il bit iniziale diventa un bit nascosto
(hidden bit). La rappresentazione di un numero floating point normalizzato sarà:
(-1) S × 1, M × 2 E
dove S indica il segno, M è la mantissa ed E è l’esponente.
7.3 STANDARD IEEE-754
Nei primi anni ‘80 i principali costruttori di elaboratori elettronici producevano calcolatori che
utilizzavano i numeri floating point con proprie convenzioni e un proprio formato numerico. La
necessità di scambiare dati tra elaboratori diversi portò alla definizione di uno standard per il
floating point da parte di una commissione che trovasse un punto di riferimento comune.
7.1
Nel 1985 l’ IEEE Computer Society (Institute of Electrical and Electronics Engineers) definì
lo standard IEEE-754 per i numeri floating point e nel 1989 diventò uno standard
internazionale (IEC 559) adottato dai maggiori costruttori di calcolatori tra i quali Motorola,
Intel, SPARC e MIPS. Tale standard ha aumentato la qualità dell’ aritmetica dei calcolatori e
la facilità di trasferimento di programmi che fanno uso di operazioni in floating point. Lo
standard IEEE-754 definisce quattro formati numerici float: single precision, double
precision, single extended precision e double extended precision. Nel seguito ci
concentreremo a considerare solo i primi due formati nei paragrafi 7.3.1 e 7.3.4 .
7.3.1 Single precision (32 bit)
Dato un numero floating point nella forma (-1) S × 1, M × 2 E , con M intero rappresentabile su
23 bit ed E compreso tra –127 e 128, il formato standard single precision si rappresenta
seguendo lo schema mostrato in figura 7.2.
1 bit
Segno
8 bit
23 bit
esponente polarizzato
mantissa
Figura 7.2. Standard single precision.
Il segno della mantissa è indicato dal primo bit, seguito dalla rappresentazione dell’esponente.
Le ultime 23 cifre rappresentano la parte frazionaria della mantissa, cioè i bit che si trovano
alla destra della virgola binaria. Invece di utilizzare un esponente E con segno rappresentato
sepratamente, il valore che viene memorizzato nel campo dell’esponente (Ê) è un intero senza
segno Ê = E + 127, che assume un valore compreso tra 0 e 255. Questo formato viene
chiamato rappresentazione polarizzata (127 è il fattore di polarizzazione).
Esempio:
Rappresentare il numero –9,75 10 in single precision.
Trasformare la parte frazionaria in complemento a due:
- 1001,11 × 2 0
Normalizzare il numero:
- 1,00111 × 2 3
La rappresentazione di un numero in single precision è data da:
(-1) S × 1, M × 2Ê - 127
In questo caso:
S = 1 = segno negativo
Ê = 3 + 127 = 130 che in binario su 8 bit risulta 1 0 0 0 0 0 1 0
M = 0011 1000 0000 0000 0000 000
cioè:
10000010 0011 1000 0000 0000 0000 000 .
7.3.2 Casi particolari
Esistono casi speciali di operazioni aritmetiche in floating point che riguardano l’utilizzo di
zero e infinito. Nella tabella 1 sono schematizzati tali casi speciali.
Quando il risultato di un’operazione aritmetica è indeterminato, cioè nei casi
7.2
0
, ±∞ × 0 ,
0
(+ ∞ ) – (- ∞ ), ad essa viene associato il valore Not a Number (NaN). Il NaN è rappresentato
da una mantissa non nulla ed esponente di tutti uno.
Se in un numero floating point l’esponente è uguale a zero e la mantissa non nulla si
rappresenta un numero denormalizzato. La rappresentazione di un numero denormalizzato in
single precision è data da (-1) S × 0, M × 2- 126 , mentre in double precision avremo
(-1) S × 0, M × 2- 1022 , dove S indica il segno ed M la mantissa del numero denormalizzato; 2- 126
e 2- 1022 sono valori convenzionali. I numeri denormalizzati sono caratterizzati da una
crescente perdita di precisione (diminuzione delle cifre significative) man mano che ci si
avvicina allo zero. Il vantaggio e’ che in questo modo si riesce ad avere qualche valore
rappresentabile anche per i numeri compresi fra 0 e il numero minimo normalizzato (sia
nell’intervallo positivo che in quello negativo) (figura 7.3).
0
2-4 2-3
2-2
2-1
0
2-4 2-3
2-2
2-1
Figura 7.3. Numeri denormalizzati.
Un altro caso particolare è la divisione di un numero per zero, la quale viene riconosciuta dal
software come infinito ( ± ∞ ).
Tabella 7.1. Casi particolari.
Single Precision
Esponente E
Mantissa M
0
0
1-254
255
255
Double Precision
Esponente E
Mantissa M
0
non-zero
qualsiasi
0
non-zero
0
0
1-2046
2047
2047
0
non-zero
qualsiasi
0
non-zero
Rappresentazione
0
± numero denormalizzato
± numero floating point
±∞
NaN
7.3.3 Overflow e Underflow
L’insieme dei numeri in floating point rappresentabili è limitato ed è possibile che un numero
sia troppo grande o troppo piccolo per essere rappresentato. Quando il risultato di un calcolo
è troppo grande per essere rappresentato in un sistema di numeri in floating point, diciamo
che è avvenuto un overflow. Nello standard IEEE viene assegnato al risultato dell’operazione il
valore + ∞ o - ∞ a seconda del segno del risultato esatto. Se il risultato di un’ operazione
aritmetica genera un valore minore in modulo del più piccolo numero positivo rappresentabile,
si verifica un underflow. L’underflow dà come risultato dell’operazione il valore zero.
7.3.4 Double precision (64 bit)
Per consentire ai numeri in virgola mobile di assumere un campo valori più ampio, lo standard
IEEE specifica anche un formato in double precision, illustrato in figura 7.4, in cui si aumenta
l’insieme dei valori che possono essere assunti dall’esponente e dalla mantissa. La forma di
rappresentazione è la stessa utilizzata per lo standard single precision, (-1) S × 1, M × 2 E ,
con M intero rappresentabile su 52 bit ed E compreso tra –1023 e 1024.
7.3
1 bit
segno
11 bit
52 bit
esponente polarizzato
mantissa
Figura 7.4. Standard double precision.
L’ esponente viene rappresentato su 11 bit con fattore di polarizzazione 1023 (Ê = E + 1023); i
valori polarizzati dell’esponente Ê sono compresi tra 0 e 2047. La mantissa è rappresentata su
52 bit. Nella tabella 2 vengono riassunte le principali differenze tra i due formati standard.
Tabella 2. Caratteristiche a confronto.
Argomento
Bit di segno
Bit nell’esponente
Bit nella mantissa
Bit totali
Sistema di esponente
Fascia esponenziale
Numero più piccolo, normalizzato
Single precision
1
8
23
32
base 2 ad eccesso 127
da – 126 a 127
Numero più grande, normalizzato
Fascia decimale
Numero più piccolo, denormalizzato
~ 10
Double precision
1
11
52
64
base 2 ad eccesso 1023
da – 1022 a 1023
2
−126
2
−1022
2
128
2
1024
−38
~ 10
a 10
38
−45
−308
a 10
~ 10
−324
~ 10
308
Esempio:
Rappresentare il numero 11,5 10 in double precision.
Trasformare la parte frazionaria in complemento a due:
1011,1 × 2 0
Normalizzare il numero:
1,0111 × 2 3
La rappresentazione di un numero in double precision è data da:
(-1) S × 1, M × 2Ê - 1023
In questo caso:
S = 0 = segno positivo
Ê = 3 + 1023 = 1026 che in binario su 11 bit risulta 1 0 0 0 0 0 0 0 0 1 0
M = 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Quindi il numero in double precision sarà così rappresentato:
0 10000000010 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000.
7.4 OPERAZIONI BASE IN FLOATING POINT
I numeri floating point possono essere combinati tra loro tramite le operazioni base
dell’aritmetica. Analizziamo le varie fasi e i problemi derivanti dalla loro applicazione.
7.4.1 Addizione e Sottrazione
L’algoritmo di addizione e sottrazione in floating point si divide in quattro fasi fondamentali:
allineamento
addizione o sottrazione delle mantisse
7.4
normalizzazione
arrotondamento delle mantisse
L’addizione e la sottrazione sono due operazioni identiche a meno di un bit di segno; se
l’operazione da svolgere è una sottrazione, il processo inizia cambiando il bit di segno.
Prendendo in esame la somma (234 x 10 0 ) + (571 x 10 −2 ), passiamo alla fase successiva:
l’allineamento. Per eseguire l’addizione tra numeri in floating point non possiamo sommare le
mantisse direttamente perche’ devono occupare posizioni equivalenti, così si esegue
l’allineamento intervenendo sugli esponenti: si prende il numero con l’esponente più piccolo
(571 x 10 −2 ) e si fa scorrere la sua mantissa verso destra un numero di volte pari alla
differenza tra i due esponenti (5,71 x 10 0 ). Questa operazione può comportare la perdita di
cifre, per questo viene fatto scorrere il numero più piccolo, cosicche’ ogni cifra persa avrà
un’importanza relativamente bassa. Quindi (234 x 10 0 ) + (5,71 x 10 0 ). Procediamo alla somma
delle mantisse tenendo conto dei segni di entrambe: (234 + 5,71) x 10 0 = 239,71 x 10 0 .
Se necessario, normalizzare il risultato considerando che il numero di cifre che possiamo
memorizzare è limitato, quindi controllare eventuali overflow o underflow. Dall’esempio
avremo 2,3971 x 10 2 . Supponendo di non poter memorizzare più di 4 cifre per la mantissa,
dovremo procedere all’arrotondamento (2,397 x 10 2 ).
Inizio
Cambia il bit di segno (sottrazione). Confronta gli esponenti dei due numeri.
Esegui lo scalamento a destra del numero minore finchè il suo esponente non
corrisponde a quello maggiore.
Somma (sottrai) le mantisse
Normalizza la somma (sottrazione), o scalando a destra ed incrementando
l’esponente, o scalando a sinistra e decrementando l’esponente.
Overflow o
Underflow?
Si
Eccezione
No
Arrotonda la mantissa al numero opportuno di bit.
No
E’ ancora
normalizzato
?
Si
Fine
Figura 7.5. Addizione e sottrazione in floating point.
7.5
7.4.2 Accuratezza
Lo standard IEEE 754 prevede 3 ulteriori bit per migliorare l’accuratezza: 1 bit di guardia
(guard bit) e 1 bit di arrotondamento (round bit) e 1 sticky bit. Tali bit sono posizionati dopo
l’ultima cifra a destra della virgola e i loro valori vengono traslati nelle cifre significative della
mantissa e viceversa durante le operazioni. E’ possibile vederne l’utilità nel seguente esempio:
Esempio
Sottrarre i seguenti due numeri floating point in single precision con e senza bit di
guardia 1.00 x 21 – 1.11 x 20 (2 – 1.75 = 0.25):
-senza bit di guardia (ricordando che devo normalizzare il secondo operando)
1,00 x 21 - 0,11 x 21 = 0,01 x 21 = 1,00 x 2-1 (=0.5 ovvero commetto un errore del 100%)
-con bit di guardia
1,000 x 21 - 0,111 x 21 = 0,001 x 21 = 1,000 x 2-2 (=0.5 ovvero nessun errore!)
Dunque il bit (o in generale i bit) di guardia consente di confinare l’errore di macchina che si
genera durante i calcoli a cifre che non compaiono nelle cifre significative del risultato.
L’arrotondamento si può avere in 4 modalità:
• al valore pari più vicino
• verso + ∞
• verso - ∞
• troncamento (arrotondamento a zero)
Il primo tipo è quello più utilizzato, soprattutto per quanto riguarda i numeri esattamente a
metà tra le due rappresentazioni troncate più vicine (esempio: 0,50). Secondo lo standard
IEEE 754, se il bit meno significativo è dispari si deve aggiungere 1, se è pari si applica il
troncamento. Questo metodo crea sempre uno zero nel bit meno significativo. Le altre
modalità di arrotondamento si hanno quando il risultato è arrotondato verso l’alto (+ ∞ ), verso
il basso (- ∞ ) oppure direttamente a zero. A seconda del metodo scelto il bit di
arrotondamento verra’ opportunamente modificato.
Inoltre, lo standard prevede un ulteriore bit a destra del round bit chiamato sticky bit, che
ha lo scopo di perfezionare l’arrotondamento. Questo bit all’inizio dell’operazione deve essere
posto a 0, e se un 1 viene fatto scorrere su di esso, mantiene tale valore fino alla fine
dell’operazione svolta. In questo modo e’ come se l’arrotondamento fosse fatto su un numero
infinito di cifre.
7.4.3 Moltiplicazione e divisione
La moltiplicazione in floating point è un’operazione più semplice della somma e si articola in
varie fasi:
• somma degli esponenti
• eliminazione della polarizzazione
• prodotto delle mantisse
• normalizzazione
• arrotondamento delle mantisse
Inizialmente si devono sommare gli esponenti: se sono memorizzati in forma polarizzata la loro
somma raddoppia il fattore di polarizzazione, il quale dovrà essere sottratto dalla somma.
Successivamente si moltiplicano le mantisse tenendo conto dei loro segni; il prodotto avrà
lunghezza doppia rispetto agli operandi, però i bit in eccesso verranno persi durante
l’arrotondamento. Il prodotto verrà normalizzato ed arrotondato come è stato fatto per
7.6
l’addizione e la sottrazione. Infine, si forza il bit di segno a 1 se i segni degli operandi sono
diversi, a 0 se sono uguali. Si riporta in figura 7.6 lo schema riassuntivo delle fasi della
moltiplicazione in floating point.
Nella divisione in floating point l’esponente del divisore viene sottratto dall’esponente del
dividendo. Questa operazione cancella la polarizzazione, che dovrà essere ristabilita
sommando il fattore di polarizzazione alla differenza degli esponenti.
Il procedimento continua esattamente come quello della moltiplicazione: si prosegue dividendo
le mantisse, poi effettuando la normalizzazione e l’arrotondamento.
Inizio
Somma/Sottrai gli esponenti polarizzati dei due numeri, sottraendo/sommando la
polarizzazione dalla somma/differenza per ottenere il nuovo esponente polarizzato.
Moltiplica/dividi le mantisse
Normalizza il prodotto scalando a destra ed incrementando l’esponente.
Overflow o
Underflow?
Si
Eccezione
No
Arrotonda la mantissa al numero opportuno di bit.
No
E’ ancora
normalizzato?
Si
Forza il segno del prodotto al valore positivo se i segni degli operandi di partenza sono
uguali; se sono diversi rendi negativo il segno.
Fine
Figura 7.6. Moltiplicazione e divisione in floating point.
7.7
7.5 ISTRUZIONI FLOATING POINT NEL MIPS
Il MIPS supporta lo standard IEEE 754 con le seguenti istruzioni:
aritmetiche
confronto e branch
load/store.
move
Le istruzioni aritmetiche in floating point sono rappresentate nel MIPS su 32 e 64 bit.
Rappresentazione in single precision:
addizione = add.s
sottrazione = sub.s
moltiplicazione = mul.s
divisione = div.s
Rappresentazione in double precision:
addizione = add.d
sottrazione = sub.d
moltiplicazione = mul.d
divisione = div.d
In figura 7.7 sono riportati sia i registri per i numeri interi ($0, ... $31), sia quelli per i numeri
floating point. Tali registri sono tutti a 32 bit, ma nel caso dei regitri floating point e’ anche
possibile riferirsi alle coppie di registri che iniziano con un registro pari come se fosse un
registro a 64 bit ($f0-$f1, $f2-$f3,ecc.).
$0
$1
$f0
$f1
$31
$f31
($f0,$f1) = 64bit
32 bit
32 bit
Figura 7.7. Architettura operazioni in floating point.
Le funzionalita’ floating point sono raggrupate nel cosiddetto coprocessore 1, per evitare
un’eccessiva complessità della cpu ed eventualemente paralelizzare operazioni intere e
floating point. Originariamente tale coprocessore era disposto su un chip separata, ma
attualmente esso costituisce una parte del chip. I registri floating point resiedono appunto
nel coprocessore e le operazioni floating point operano solo su di essi. In tabella 3 sono
schematizzati i registri floating point e i relativi utilizzi.
7.8
Tabella 3. Registri floating point.
Registri
$f0…$f3
$f4…$f11
$f12...$f15
$f16…$f19
$f20…$f31
Utilizzo
Risultato della procedura
Provvisori
Passaggio dei parametri
Provvisori
Salvataggio dalla procedura chiamata
Per leggere o scrivere valori floating point dalla memoria si usano le istruzioni lwc1 (load) e
swc1 (store).
Per trasferire un valore fra i registri interi e quelli floating point sono presenti le istruzioni
mfc1 (move from coprocessor 1), e mtc1 (move to coprocessor 1). Tali istruzioni non
effettuano conversioni. Se l’intenzione e’ invece di convertire un valore intero in un valore
floating point (o viceversa) si fa uso delle istruzioni cvt, che opera solo su registri floating
point:
cvt.d.s converte da single a double
cvt.d.w converte da integer a double
cvt.s.d converte da double a single
cvt.s.w converte da integer a single
cvt.w.s converte da single a integer
cvt.w.d converte da double a single
Le istruzioni di confronto mettono il risultato (vero o falso) in un bit interno mentre il branch
decide il salto a seconda della condizione. Il confronto fra numeri floating point fa uso
dell’istruzione c.x.s in single precision e c.x.d in double precision. Al posto della x possiamo
trovare:
eq (uguale)
neq (non uguale)
lt (minore)
le (minore o uguale)
gt (maggiore)
ge (maggiore o uguale)
Il bit memorizzato internamente viene usato dalle seguenti due istuzioni di salto:
bc1t (branch true)
bc1f (branch false)
La tabella 4 riassume le istruzioni MIPS in floating point con esempi e commenti.
7.9
Istruzione
FP add single
FP subtract single
FP multiply single
FP divide single
FP add double
FP subtract double
FP multiply double
FP divide double
FP absolute value single
FP absolute value double
FP negative single
FP negative double
load word into c1
store word from c1
branch on FP true
branch on FP false
FP compare single
(eq,ne,lt,le,gt,ge)
FP compare double
(eq,ne,lt,le,gt,ge)
Move from c1 to c0
Move to c1 from c0
Convert double to
integer
Tabella 7.4: Istruzioni MIPS in floating
Esempio
Significato
add.s $f2, $f4,
$ f2 = $f4 + $f6
$f6
sub.s $f2, $f4,
$ f2 = $f4 - $f6
$f6
mul.s $f2, $f4,
$ f2 = $f4 × $f6
$f6
div.s
$f2, $f4,
$ f2 = $f4 / $f6
$f6
add.d $f2, $f4,
$ f2 = $f4 + $f6
$f6
sub.d $f2, $f4,
$ f2 = $f4 - $f6
$f6
mul.d $f2, $f4,
$ f2 = $f4 × $f6
$f6
div.d $f2, $f4,
$ f2 = $f4 / $f6
$f6
abs.s $f1
|$f1|
abs.d $f2
|$f2|
neg.s $f1
-$f1
neg.d $f2
-$f2
lwc1 $f1, 100
$f1 = Memory[$s2 +
($s2)
100]
swc1 $f1, 100
Memory[$f2 + 100] =
($s2)
$f1
if (cond == 1) go to PC +
bc1t 25
4 + 100
if (cond == 0) go to PC +
bc1f 25
4 + 100
if ($f2 < $f4)
c.lt.s $f2, $f4
cond == 1; else cond ==
0
if ($f2 < $f4)
c.lt.d $f2, $f4
cond == 1; else cond ==
0
mfc1 $2, $f4
$2 -> $f4
mtc1 $2, $f4
$f4 -> $2
cvt.w.d $f2, $f0
$f2 = (int)$f0
Convert double to single
cvt.s.d $f2, $f0
$f2 = (float)$f0
Convert single to double
cvt.d.s $f2, $f0
$f2 = (double)$f0
Convert single to integer cvt.w.s $f1, $f0
$f1 = (int)$f0
Convert integer to single cvt.s.w $f1, $f0
$f1 = (float)$f0
Convert integer to
double
$f2 = (double)$f0
cvt.d.w $f2, $f0
7.6 RIFERIMENTI BIBLIOGRAFICI
point.
Commento
somma single precision
sottrazione single precision
moltiplicazione single precision
divisione single precision
somma double precision
sottrazione double precision
moltiplicazione double precision
divisione double precision
valore assoluto single precision
valore assoluto double precision
valore negativo single precision
valore negativo double precision
trasferimento dati su 32 bit ad un
registro FP
trasferimento dati su 32 bit in
memoria
salto relativo al PC su condizione
vera
salto relativo al PC su condizione
falsa
confronto single precision
confronto double precision
Sposta il contenuto di $2 in $f4
Sposta il contenuto di $f4 in $2
Converte da double precision a
intero
Converte da double precision a
single
Converte da single precision a
double
Converte da single precision a
intero
Converte da intero a single
precision
Converte da intero a double
precision
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] Sito www.dis.uniroma1.it/~salza/teaching.htm
7.10
LEZIONE 8
Assembly MIPS
8.1 INTRODUZIONE
Le prestazioni di un calcolatore si basano sulla valutazione del tempo totale di esecuzione che
offre un’indicazione consistente delle prestazioni complessive. L’evoluzione dei calcolatori è
incentrata sul miglioramento di alcuni parametri in base all’equazione fondamentale delle
prestazioni (vedi cap.4):
• Incremento della frequenza di clock se non va a discapito del CPI.
• Miglioramenti della struttura del processore che abbassano il CPI.
• Miglioramenti del compilatore che abbassano il numero totale delle istruzioni eseguite
dinamicamente da un programma.
La tendenza generale nell'architettura e nell'organizzazione dei calcolatori è stata per lungo
tempo rivolta verso una sempre maggiore complessità della CPU: set di istruzioni più vasto,
modi di indirizzamento più complicati, registri maggiormente specializzati, e così via. A
partire dagli anni ottanta, tuttavia, si è affermato un approccio innovativo all'architettura dei
calcolatori, noto col nome di Reduced Instruction Set Computer (RISC), che sembra
consentire una maggiore efficienza nell'implementazione di sistemi di elaborazione.
L'esposizione che segue è rivolta all'introduzione dei concetti che stanno alla base delle
architetture RISC. Un altro tipo di filosofia di progetto è rappresentato dal Complex
Instruction Set Computer (CISC) che per primo conquistò il mercato con l’avvento del
Personal Computer all’inizio degli anni ‘80 e che lentamente sta lasciando il passo.
Questi due approcci si differenziano per la loro Instruction Set Architecture (ISA) che è il
punto cruciale di interfaccia tra hardware e software.
Il livello ISA è posizionato tra il livello di microarchitettura e il livello del sistema operativo
(figura 8.1). Storicamente l’ISA fu sviluppato prima di tutti gli altri livelli e infatti
originariamente era l’unico livello esistente. Oggi viene chiamato semplicemente “architettura
di macchina” o a volte “assembly language”.
Programma
FORTRAN 90
Programma C
ISA
ISALIVELLO
LEVEL
Software
_________
Hardware
Hardware
hardware
Figura 8.1: Architettura di macchina.
Quali sono le caratteristiche di un buon ISA? In primo luogo dovrebbe definire un set di
istruzioni che possa essere implementato efficientemente nelle attuali e future tecnologie. In
secondo luogo dovrebbe facilitare la compilazione del codice. La regolarità e la completezza
dell’ISA sono tratti importanti che non sono sempre presenti. In breve l’ISA dovrebbe
8.1
soddisfare sia le esigenze dei progettisti hardware (facilitando un’implementazione più
efficiente) che dei programmatori (facilitando la generazione di un buon codice).
8.1.2 Classi principali di ISA
Le classi principali dell’ISA sono descritte in tab 8.1
Tabella 8.1: classi principali dell’ISA.
Sintassi
di una
istruzione tipo
Semantica
dell’istruzione
1
add A
acc Å acc + mem[A]
1+x
addx A
acc Å acc + mem[A + x]
0
add
tos Å tos + next
A registri di
uso generale
2
add A B
EA(A)Å EA(A) + EA(B)
3
add A B C
Ad
architettura
load/store
0
add Ra Rb Rc
Ra Å Rb + Rc
1
load Ra Rb
Ra Å mem[Rb]
1
store Ra Rb
mem[Rb] Å Ra
Ad
A
accumulato
stack
re
Indirizzi
contenuti
nell’istruzi
one
EA(A)Å EA(B) + EA(C)
CISC: Con questo termine si indica un tipo di CPU in cui la fase di decodifica di un'istruzione
avviene in passi successivi(µ-codice). Uno svantaggio di questa filosofia e' che la singola
istruzione può richiedere alcuni cicli di clock per essere interpretata dal processore,
diversamente da quello che accade nei processori con il set di istruzioni di tipo RISC. Un
vantaggio dei processori di tipo CISC e' quello di codificare un programma utilizzando un
numero munore di istruzioni e quindi di richiedere una quantita' minore di memoria-istruzioni.
Esempi di CPU CISC sono la famiglia Motorola 68000 installata sui modelli Macintosh
precedenti all'uscita del Power Mac (PowerPC) e la famiglia 80x86 della Intel.
RISC: E' un tipo di CPU che permette di eseguire delle operazioni piu' velocemente di un
processore CISC perchè si esegue almeno un'istruzione per ogni ciclo di clock. Il set di
istruzioni e' stato ridotto a quelle semplici ed utilizzate piu' frequentemente dai programmi,
eliminando istruzioni piu' complesse e poco usate(l’importanza di questo punto verrà analizzata
in dettaglio nel paragrafo 8.4.1 e tab.8.6). Queste ultime possono comunque essere ricreate
partendo da istruzioni semplici. Statisticamente il numero di istruzioni usate effettivamente
da una CPU è minore del numero di istruzioni definite nel set di una CPU CISC.
Storicamente RISC e' stata, ed e', una filosofia, un modo di concepire una CPU. L'idea e'
quella di fornire un processore con poche istruzioni e pochi modi di indirizzamento, ma
interamente implementato in hardware, in contrapposizione con le CPU CISC, dotate di
istruzioni piu' ad alto livello ed implementate in micro-codice. Oggi, si considerano spesso
RISC anche processori con istruzioni derivate da vecchie implementazioni CISC (68040/060
8.2
derivanti dai 680x0 fino allo 030, od il Pentium derivante dai 80x86), poiche' si bada piu'
all'implementazione completamente hardware delle istruzioni che all'instruction set.
Processori come il PowerPC, infatti, hanno portato a questa evoluzione del termine,
introducendo in un set di istruzioni tipico di altre CPU RISC (di nome e di fatto, quale il MIPS
o l'Alpha) istruzioni piu' complesse ma ugualmente implementate in hardware, che ne
incrementano notevolmente le performance rispetto ai concorrenti. Un'architettura RISC (nel
senso moderno) permette un'implementazione più efficienti, in quanto piu' complessa solo da
un punto di vista tecnologico, ma piu' facile da gestire. Da un punto di vista della sintassi
assembler, si distingue una CPU di filosofia CISC da una RISC, indipendentemente
dall'implementazione, se sono presenti istruzioni che permettono molti modi di indirizzamento
in sorgente ed in destinazione. Il PowerPC, ad esempio, si presenta con istruzioni ad operandi
fissi, ma gode di numerose varianti della stessa istruzione per operazioni piu' complesse.
Un confronto tra CISC e RISC è mostrato in tabella 8.2.
Tabella 8.2: confronto CISC e RISC.
Caratteristiche CISC
Caratterisitiche RISC
Approccio
fondamentale
La complessità si sposta dal codice
all'hardware
La complessità si sposta dall'hardware al
software, ovvero al compilatore che deve
essere molto efficiente
Conseguenze
della scelta
per il
programmatore
Il codice è molto compatto e occorre poca
memoria per contenerlo; è l'hardware che si
incarica di decodificare istruzioni anche
molto compatte e molto complesse
La dimensione del codice aumenta in favore
della semplificazione dell'hardware.
Conseguenze
della scelta a
livello hardware
-Pochi registri.
-Presenza di una ROM di decodifica.
-ISA molto articolato con centinaia di
istruzioni.
-Modalità di indirizzamento memoriamemoria
-Molti registri
-Non esiste la modalità di indirizzamento
memoria-memoria, ma alla memoria si accede
solo con il load e lo store
-ISA con qualche decina di istruzioni soltanto
-Direct execution
-Uso della pipeline per diminuire il ritardo del
critical path.
8.2 RICHIAMO SULL’ISA R 3000
Il MIPS R3000 presenta tre formati di istruzioni (formato R, formato I e formato J) aventi
lunghezza fissa pari a 32 bit come riportato in Figura 8.2.
6 bit
op
[31-26]
5 bit
rs
[25-21]
5 bit
rd
[20-16]
Formato I
op
[31-26]
rs
[25-21]
rd
[20-16]
Formato J
op
[31-26]
Formato R
5 bit
rd
[15-11]
5 bit
shamt
[10-6]
6 bit
funct
[5-0]
Indirizzo/immediato [15-0]
Indirizzo destinazione [25-0]
Figura 8.2: Formati delle istruzioni.
Questo processore supporta le seguenti categorie di istruzioni:
• Lettura/scrittura (load/store)
• Calcolo (aritmetiche,logiche)
• Salti e diramazioni (Jump, branch rispettivamente)
8.3
Commenti
Istruzioni aritmeticologiche su registri
Trasferimenti,
salti condizionati,
istruzioni aritmeticologiche con immediati
Istruzioni di salto
•
•
•
Floating point (istruzioni del coprocessore)
Gestione della memoria(TLB)
Istruzioni speciali (syscall,break)
8.3 VISIONE GENERALE DEI METODI DI INDIRIZZAMENTO
DELLA MEMORIA
Uno degli aspetti più importanti di un’architettura è dato dalle modalità di indirizzamento in
memoria, ovvero come vengono raggiunti gli oggetti dell’elaborazione (costanti, variabili) e
come viene passato il controllo tra parti diverse di programma.
Praticamente tutte le macchine oggi prodotte assegnano gli indirizzi ai byte (8 bit di dati),
anche se è possibile trasferire oltre al singolo byte, dati di 16 bit (semiparola), di 32 bit
(parola) e 64 bit (doppia parola). Se un’istruzione fa riferimento ad un oggetto che non è un
byte, si pone il problema di come vengono presi gli altri byte che compongono l’oggetto stesso,
oltre a quello che ne dà l’indirizzo. È quindi più conveniente mantenere l’accesso al byte
trasferendo meno dati in alcuni casi, o trasferire sempre e comunque word di dati mantenendo
comunque l’accesso al byte?
8.3.1 Metodo di indirizzamento (endiannes)
Considerando per esempio i riferimenti ad una parola, il byte che dà l’indirizzo alla word è
considerato nella posizione meno significativa o in quella più significativa? I due metodi di
ordinamento sono denominati Little Endian e Big Endian (fig.8.3), dove nel primo ad indirizzi
più bassi corrispondono i byte meno significativi (Intel 80x86,vax), mentre nel secondo ad
indirizzi più bassi corrispondono i byte più significativi (MIPS,68000).
Esempio: se scrivo la stringa 76543210 all’indirizzo 0 di memoria
indirizzi al byte
3
7
…
…
2
6
…
…
1
5
…
…
0
4
…
…
0
4
…
indirizzi alti
Little Endian
indirizzi al byte
0
4
…
…
1
5
…
…
2
6
…
…
3
7
…
…
0
4
…
indirizzi alti
Big Endian
Figura 8.3: metodi di ordinamento in memoria.
8.3.2 Allineamento
Supponiamo di trasferire di 4 byte, allora una data parola potrebbe iniziare ad indirizzo
0,1,2,. Nel caso in cui l’indirizzo sia multiplo della parola si dirà che tale parola è allineata in
memoria, negli altri casi si dirà che la parola è non allineata. Supponendo che le letture di
word avvengano solo ad indirizzi multipli di 4: allora nel caso di una parola non allineata, la sua
8.4
lettura/scrittura richiede almeno 2 cicli: prima per leggere/scrivere all’indirizzo i e
successivamente all’indirizzo i+4. Questa procedura rappresenta un rallentamento e richiede
la presenza della logica per rimettere in ordine i byte. Per questa ragione l’allineamento è
richiesto in molte architetture, in special modo nelle macchine RISC. L’unico motivo per usare
indirizzi non allineati è dato da ragioni di economia dello spazio utilizzato in memoria.
Aligned
Not
Aligned
Figura 8.4: parole allineate e non allineate.
8.3.2.1 Modi di indirizzamento
La modalità di indirizzamento si riferisce alla modalità in cui viene calcolato l’indirizzo
effettivo di un operando che sta in memoria. Alcune tipologie sono mostrate in tabella 8.3:
Tabella 8.3: Tipologie di indirizzamento.
Modo di indirizzamento
Esempio
Significato
Register
Add R4,R3
R4Å R4+R3
Immediate
Add R4,#3
R4Å R4+3
Displacement
Add R4,100(R1)
R4Å R4+Mem[100+R1]
Register indirect
Add R4,(R1)
R4Å R4+Mem[R1]
Indexed/Base
Add R3,(R1+R2)
R3Å R3+Mem[R1+R2]
Direct or absolute
Add R1,(1001)
R1Å R1+Mem[1001]
Memory indirect
Add R1,@(R3)
R1Å R1+Mem[Mem[R3]]
Auto-increment
Add R1,(R2)+
R1Å R1+Mem[R2]; R2Å R2+d
Auto-decrement
Add R1,–(R2)
R2Å R2-d; R1Å R1+Mem[R2]
Scaled
Add R1,100(R2)[R3]
R1Å R1+Mem[100+R2+R3*d]
Register: l’operando è il contenuto di un registro della CPU; il nome di questo registro è
specificato nell’istruzione.
Immediate: l’operando è definito esplicitamente nell’istruzione. Questa modalità è usata per
specificare indirizzi e dati costanti.
8.5
Register indirect: l’indirizzo effettivo dell’operando è il contenuto di una locazione di
memoria il cui indirizzo appare nell’istruzione. Nell’esempio questa indirezione è indicata
ponendo tra parentesi tonde il nome del registro o l’indirizzo della locazione di memoria
fornito con l’istruzione.
Offset/Displacement:: l’indirizzo effettivo dell’operando è calcolato sommando un valore
costante al displacement (o offset). Questo indirizzamento viene rappresentato
simbolicamente con N(R) dove N è una costante(displacement o offset) e R è il nome del
registro coinvolto. L’indirizzo effettivo dell’operando è quindi A= N+[R].
Indexed/base: in questo caso, al posto della costante indice N, viene usato il contenuto di un
secondo registro. L’indirizzo effettivo è la somma del contenuto dei registri R1 e R2.
Direct or absolute: l’operando è in una locazione di memoria il cui indirizzo è fornito
esplicitamente nell’istruzione.
Memory indirect: l’indirizzo effettivo dell’operando è il contenuto di una locazione di memoria
contenuta a sua volta in una locazione di memoria il cui indirizzo appare nell’istruzione.
Auto-increment: l’indirizzo effettivo dell’operando è il contenuto di un registro specificato
nell’istruzione. Dopo l’accesso all’operando il contenuto del registro viene incrementato per
puntare all’elemento successivo in una lista.
Auto-decrement: il contenuto di un registro specificato nell’istruzione viene decrementato. Il
nuovo contenuto viene poi usato come indirizzo effettivo dell’operando. Questa modalità
permette di accedere a operandi contigui in ordine di indirizzo decrescente e combinata con
l’indirizzamento Auto-increment è utile ad es. nella realizzazione di una struttura dati a pila.
Scaled: nell’indirizzare il generico elemento, si fa fare al secondo registro la funzione di
indice nella struttura,scalando in base alla dimensione “d” dell’elemento stesso
Questo indirizzamento è molto conveniente quando si ha a che fare con strutture dati
(vettori,matrici ecc.) proprio per la funzione di indice assunta dal secondo registro.
Esempio: N(R1)[R2] significa che l’indirizzo A= N+[R1]+d*[R2]
Per misurare l’uso effettivo dei vari modi di indirizzamento supponiamo di usare una macchina
avente tutti i modi di indirizzamento a disposizione sulla quale vengono fatti girare 3
programmi. Da questa analisi si può notare come l’indirizzamento Displacement e Immediate
siano i più usati (tab.8.4). Dall’analisi sono esclusi i modi che usano solo registri.
Tab.8.4: Uso effettivo dei modi di indirizzamento.
• Displacement
• Immediate
42% avg, (32% to 66%)
33% avg, (17% to 43%)
• Reg. Deferred(indirect)
• Scaled
• Memory indirect
• Misc
13% avg, (3% to 24%)
75% displacement &
immediate
88% displacement,
immediate & register
indirect
7% avg, (0% to 16%)
3% avg, (1% to 6%)
2% avg, (0% to 3%)
8.3.3 Dimensione del displacement
Attraverso analisi sperimentali condotte su 5 programmi SPECint92 e 5 programmi SPECfp92,
e considerando i bit come quantità con segno è stato rilevato che gli operandi che fanno uso di
displacement che necessitano di essere rappresentati su un numero di bit > di 15 è pari all’1%.
8.6
Da esperimenti di questo tipo si trae l’indicazione che la dimensione ragionevole per il
Displacement va da 12 a 16 bit.
Inoltre il numero degli operandi immediati rappresentabili in 8 bit è compreso tra il 50 e il
60%, mentre quelli rappresentabili con 16 bit sono compresi tra il 75 e l’80%.
___media SPECint92 ---mediaSPECfp92
Int. Avg.
FP Avg.
14
12
10
8
6
4
2
0
0,4
0,2
0
Figura 8.5: Bit di displacement.
8.3.4 Implicazioni sul ISA del MIPS
In base a considerazioni simili a quelle esaminate in paragrafi precedenti, nel MIPS sono
quindi stati scelti i seguenti modi di indirizzamento:
8.7
1. Immediate addressing
rs
Op
rt
addi, addiu,
andi, ori, slti,
sltiu, lui
Immediate
2. Register addressing (direct addressing)
op
rs
rt
Rd
…
add, sub,
mult, div,
addu,
subu,
multu,
divu, and,
or, sll, slr
slt, sltu,
mfhi, mflo
funct
Registers
Register
3. Base addressing
Op
rs
rt
Memory
address
byte
+
halfword
Word
lw, lh, lb,
lbu, sw,
sh, sb
Register
4. PC-relative addressing: PC=PC+(addressx4)+4
Op
rs
rt
Memory
address
Word
+
bne,
bnq
PC
5. Pseudodirect addressing: PC=PC[31..28] │address x 4
op
address (26 bit)
00
J, jal
OR
Word
PC
Figura 8.6: Modi di indirizzamento MIPS.
8.8
8.4 LUNGHEZZA DEI FORMATI DELL’ISTRUZIONE
Un’altra fase molto importante nella progettazione è la scelta della lunghezza del formato
delle istruzioni che si basa su tre diverse categorie: variabile, fissa e ibrida (come mostrato
in fig.8.5).
Figura 8.5: esempi di istruzioni con varie lunghezze di formato.
Questa scelta deve rappresentare un buon compromesso tra la dimensione del codice e le
prestazioni. Nel primo caso può essere più conveniente utilizzare istruzioni a formato
variabile mentre nel secondo indubbiamente sono migliori quelle a lunghezza fissa.
Recentemente i processori (ARM, MIPS) hanno aggiunto modalità di esecuzione opzionali che
consentono la decodifica di istruzioni lunghe metà parola (16 bit).
•
•
•
Tali istruzioni sono un subset del set di istruzioni principali.
Tali standard sono denominati Thumb e MIPS16 rispettivamente.
È possibile selezionare tale opzione a livello di funzione C.
8.4.1 Scelta nel MIPS
Il compromesso adottato dai progettisti del MIPS consiste nel mantenere invariata la
lunghezza per tutte le istruzioni predisponendo formati diversi per tipi di istruzioni diverse.
In particolare vengono utilizzati tre formati: R, I, J.
6 bit
5 bit
5 bit
5 bit
5 bit
6 bit
Commenti
Formato R
op
[31-26]
rs
[25-21]
Rd
[20-16]
Rd
[15-11]
shamt
[10-6]
funct
[5-0]
Formato I
op
[31-26]
rs
[25-21]
Rd
[20-16]
Istruzioni aritmeticologiche su registri
Trasferimenti,
salti condizionati,
istruzioni aritmeticologiche con immediati
Formato J
op
[31-26]
Indirizzo/immediato [15-0]
Indirizzo destinazione [25-0]
Figura 8.5.1: Formati delle istruzioni.
8.9
Istruzioni di salto
Se ci fossero molti operandi in memoria per ogni istruzione e molti modi di indirizzamento
sarebbe necessario avere una specifica di indirizzo per ognuno degli operandi.
Invece in una architettura load/store con al massimo un indirizzo per istruzione e pochi modi
di indirizzamento (max 1 o 2) posso codificare il modo di indirizzamento direttamente insieme
al campo “opcode” dell’istruzione.
A questo punto vediamo i criteri con cui può essere effettuata la scelta delle operazioni da
supportare (vedi tab.8.V). Dal 1960 ad oggi ci sono stati pochi cambiamenti sulle operazioni
tipiche del calcolatore.
Tabella 8.5: Principali operazioni supportate
Data Movement
Load (from memory)
Store (to memory)
memory-to-memory move
register-to-register move
input (from I/O device)
output (to I/O device)
push, pop (to/from stack)
Arithmetic
integer (binary + decimal) or FP
Add, Subtract, Multiply, Divide
Shift
shift left/right, rotate left/right
Logical
not, and, or, set, clear
Control (Jump/Branch)
unconditional, conditional
Subroutine Linkage
Call, return
Interrupt
trap, return-from-interrupt
Synchronization
test & set (atomic r-m-w)
Multimedia (MMX)
parallel subword ops (16bit addx4)
*1
*1. atomic r-m-w (read-modify-write) è l’operazione fondamentale per consentire la
sincronizzazione delle operazioni a livello di sistema operativo.
Ad esempio da un’analisi effettuata sul processore 80x86 è risultato che le istruzioni più
semplici sono anche le più usate (tab. 8.6) e che quindi devono essere supportate meglio a
8.10
livello di architettura. In tale tabella possiamo osservare inoltre come nel caso del processore
80x86 il 96% delle istruzioni eseguite è rappresentato dalle sole dieci indicate.
Tabella 8.6: Frequenza di utilizzo delle principali istruzioni.
Rank
Istruzione
Percentuale totale
1
load
22%
2
conditional branch
20%
3
compare
16%
4
store
12%
5
add
8%
6
and
6%
7
sub
5%
8
move register-register
4%
9
call
1%
10
return
1%
Total
96%
8.5 TIPI “PRIMITIVI” DI DATO E DIMENSIONE DEGLI OPERANDI
NEI PROGRAMMI
I tipi di dato possono essere divisi in due categorie: numerici e non numerici. Il tipo di dato
numerico più comune è l’intero che può essere rappresentato tramite 4, 8, 16, 32 o 64 bit. I
computer più moderni memorizzano gli interi tramite notazione in complemento a due. Alcuni
computer supportano interi sia senza segno (unsigned in linguaggio C) che con segno (int in
linguaggio C). Per numeri che non possono essere espressi come un intero (per esempio 3,5) si
usa la rappresentazione in virgola mobile che può essere di tre tipi: single precision, double
precision e extended precision (vedi lez.7). Alcuni linguaggi di programmazione permettono
l’uso di numeri decimali come tipi di dato. Le macchine compatibili con questi linguaggi
supportano spesso numeri decimali in hardware, codificando generalmente una cifra decimale
in 4 bit (formato BCD, Binary Coded Decimal).
Il più comune codice di caratteri non numerici è invece l’ASCII.
Per valutare la dimensione da assegnare agli operandi utilizzati nei programmi, possiamo fare
riferimento alla statistica riportata nella figura 8.6 (sempre in riferimento all’insieme dei
programmmi studiati nel paragrafo 8.3.3).
8.11
0.00%
Doubleword
Word
69.24%
74.33%
30.75%
18.79%
Halfword
0.00%
media SPECint92
media SPECfp92
6.88%
Byte
0.01%
0
0.2
0.4
0.6
0.8
Figura 8.6: Frequenza di utilizzo per ogni dimensione del dato.
Nei programmi che utilizzano numeri interi la dimensione di dato più frequente utilizzata è 32
bit, mentre nei programmi che utilizzano numeri floating point la dimensione di dato più
frequentemente utilizzata è 64 bit (con riferimento a SPEC92).
8.6 METODI PER FARE CONFRONTI
Spesso è necessario confrontare due valori e scegliere un percorso di esecuzione tra molti
possibili in base al risultato. Ciò viene fatto di solito mediante un’istruzione di confronto
seguita da una o più istruzioni di salto condizionato. Si possono individuare vari metodi per
testare le condizioni:
•
Metodi basati sui “Condition Codes”: i Condition Codes sono bit di stato (FLAG WORD)
modificati ad ogni ciclo dell’ALU e riflettono lo stato del risultato dell’operazione più
recente. La lettura di questi bit indica se e dove effettuare il salto. Sono
prevalentemente usati per confrontare con zero un certo risultato.
Es.
add r1,r2,r3
bz
label
•
Metodi basati sul “Condition Register” (approccio MIPS): utilizza un registro in cui
viene memorizzato il risultato di un confronto. Questo registro viene successivamente
confrontato con il registro 0 e viene valutato se e dove effettuare il salto.
Es. cmp r1,r2,r3
bne r1,r0,label
•
Istruzione diretta di tipo “Compare and Branch”: in questo caso non vengono utilizzati
né particolari codici né registri di appoggio ma esiste una istruzione diretta che
permette di effettuare sia il confronto che il salto.
Es.
bgt r1,r2,label
8.12
8.6.1 Vantaggi e svantaggi del “Condition Code”
Il vantaggio dei metodi basati sul “Condition Code” è quello di ridurre il numero di istruzioni
utilizzate.
Es. X:
....
....
addi r1,r2,1
bnz X
X:
invece di
….
….
addi r1,r2,1
cmp r1,r0
bne X
Gli svantaggi sono legati alla mancata modifica dei condition codes da parte di alcune
istruzioni e questo può creare ambiguità. Inoltre si genera implicitamente una dipendenza dati
fra l’istruzione che genera il codice e quella che lo usa.
Per esempio in una pipeline (Figura 8.7) si possono generare problemi se cerco di utilizzare
queste due istruzioni in sequenza (la pipeline verrà approfondita nel capitolo 20).
ifetch
read
write
compute
Usa il vecchio CC !
ifetch
Calcolo il nuovo CC
read
write
compute
Figura 8.7: problema di utilizzo di due istruzioni in sequenza in una pipeline.
8.6.2 Distanza di salto nei “Conditional Branch”
Usando la stessa metodologia dei paragrafi precedenti è stata valutata la distribuzione del
numero di bit necessario per rappresentare il displacement nei branch come mostrato in
Figura 8.8.
___media SPECint92 ---mediaSPECfp92
40%
30%
20%
10%
14
12
10
8
6
4
2
0
0%
Figura 8.8: dimensione in bit del displacement nei Branch.
Da questa statistica (sempre in riferimento ai benchmark usati nel paragrafo 8.3.3) si nota
che il 25% dei displacement dei branch conseguenti a confronti che operano su interi, ovvero
8.13
quelli rappresentabili con 3 bit, l’indirizzo target si trova più o meno distante 4 istruzioni dal
salto (in un intervallo che va da +4 a –4 istruzioni). In maniera analoga il 35% dei displacement
utilizzati nei branch per i numeri conseguenti a confronti che operano in floating point sono
rappresentabili con 2 bit. Utilizzando 8 bit per rappresentare il displacement si ha una
copertura quasi totale della casistica dei displacement dei branch.
8.7 TIPO DI INDIRIZZAMENTO NEL “CONDITIONAL BRANCH”
Il tipo di indirizzamento più usato nei Conditional Branch è il “PC-relative” in cui l’indirizzo è la
somma del contenuto del program counter e della costante nell’istruzione (vedi lezione 3),
visto che gli indirizzi target dei branch non si discostano molto dal valore attuale del PC.
LT/GE
7%
GT/LE
7%
EQ/NE
40%
media SPECint92
23%
37%
0%
50%
86%
media SPECfp92
100%
FIG. 8.9: Frequenza di alcuni tipi di confronto nel branch
Figura. 8.9: Frequenza di alcuni tipi di confronto nel branch.
Quando si opera sugli interi il tipo di confronto utilizzato più spesso in appoggio ad
un’istruzione di branch è l’EQUAL/NOT EQUAL come si nota dal grafico in figura 8.9.
Conviene quindi supportare preferibilmente l’ istruzione EQ/NE nei branch.
8.7.1 MIPS:completamento delle istruzioni di “Compare” e “Branch”
Per realizzare le istruzioni di confronto e salto si possono utilizzare vari metodi come
riportato in tab. 8.7.
Tab.8.7: Metodologie di confronto e salto.
1. Compare and Branch
BEQ rs, rt, offset
BNE rs, rt, offset
if R[rs] == R[rt] then PC-relative branch
!=
MIPS Hennessy
and patterson
2. Compare to zero and Branch (native, non pseudoistruzioni !)
BLEZ rs, offset
if R[rs] <= 0 then PC-relative branch
BGTZ rs, offset
>
BLT
<
MIPS LSI *
BGEZ
>=
Logic LR3000
BLTZAL rs, offset
if R[rs] < 0 then branch and link (into R31)
BGEZAL
>=
*(Da “LR3000 and LR3000 A MIPS RISC Microprocessor user’s manual”, 1990)
8.14
Come osservato nei capitoli precedenti può essere sufficiente utilizzare le due istruzioni beq,
bne e slt per effettuare confronti e salti. In implementazioni reali del processore MIPS (es.
LR 3000 della LSI LOGIC) sono supportate anche le istruzioni dirette di compare & branch.
8.7.2 MIPS: riepilogo delle principali istruzioni di jump, branch e compare
Le scelte effettuate per il MIPS sono riassunte in tabella 8.8:
Tab.8.8: Principali istruzioni di branch,jump,compare.
Istruzione
Esempio
beq $1,$2,100
if ($1 == $2)
go to PC+4+100
Significato
Commenti
Equal test; PC relative branch
branch on not eq.
bne $1,$2,100
if ($1!= $2)
go to PC+4+100
Not equal test; PC relative
set on less than
slt $1,$2,$3
if ($2 < $3)
$1=1; else $1=0
Compare less than; 2’s comp.
set less than imm.
slti $1,$2,100
if ($2 < 100)
$1=1; else $1=0
Compare < constant; 2’s comp.
set less than uns.
sltu $1,$2,$3
if ($2 < $3)
$1=1; else $1=0
Compare less than;
natural numbers
set l. t. imm. uns.
sltiu $1,$2,100
if ($2 < 100)
$1=1; else $1=0
Compare < constant;
natural numbers
jump
j 10000
go to 10000
Jump to target address
jump register
jr $31
go to $31
jump and link
jal 10000
$31 = PC + 4;
go to 10000
branch on equal
For switch, procedure return
For procedure call
ESERCIZIO: diversità tra confronti Signed e Unsigned
•Supponiamo che i registri r1, r2, r3 contengano i seguenti valori
r1= 0…00 0000 0000 0000 0001
r2= 0…00 0000 0000 0000 0010
r3= 1…11 1111 1111 1111 1111
•Dopo aver eseguito le seguenti istruzioni, trovare quanto valgono i contenuti dei registri r4,
r5, r6, r7
slt r4,r2,r1 # if (r2 < r1) r4=1; else r4=0
slt r5,r3,r1 # if (r3 < r1) r5=1; else r5=0
sltu r6,r2,r1 # if (r2 < r1) r6=1; else r6=0
sltu r7,r3,r1 # if (r3 < r1) r7=1; else r7=0
il contenuto dei registri risulta quindi:
r4 =0; r5 =1; r6 =0; r7 =0;
8.8 DETTAGLI DELL’ARCHITETTURA DEL SET DI ISTRUZIONI
MIPS
Tra le caratteristiche che contraddistinguono i registri della struttura MIPS si nota che il
registro zero contiene sempre il valore zero anche nel caso in cui si cerchi di sovrascriverlo.
8.15
L’ultimo registro (r31) invece è utilizzato dalle funzioni branch/jump and link per memorizzare
il valore dell’indirizzo di ritorno (PC+4).
Bisogna inoltre tener conto che tutte le istruzioni cambiano tutti i 32 bit del registro
destinazione (incluso la lui, lb, lh) e leggono tutti i 32 bit del registro sorgente (add, sub, and,
or,…).
Per ciò che concerne le istruzioni logiche i numeri che ne sono operandi sono estesi a 32 bit
con zero-padding, ovvero vengono inseriti tutti zeri nei bit più significativi non utilizzati,
mentre per gli operandi di istruzioni aritmetiche sono estesi a 32 bit attraverso l’estensione
del segno.
Analogamente le istruzioni lbu (load byte unsigned) e lhu (load half word unsigned) prevedono
l’estensione attraverso zero-padding mentre le istruzioni lb (load byte) e lh (load half word)
prevedono l’estensione del segno in complemento a 2.
I problemi dovuti all’overflow possono riguardare le istruzioni add, sub, addi, mentre ne sono
estranee addu, subu, addiu, and, or, xor, nor, shifts, mult, multu, div, divu.
8.8.1 Delayed Branches
li
sub
bz
addi
subi
LL: slt
r3, #7
r4, r4, 1
r4, LL
r5, r3, 1
r6, r6, 2
r1, r3, r5
Come si comporta realmente un processore MIPS in presenza di un branch? Nella realtà
l’istruzione che si trova dopo un branch viene sempre eseguita anche se il sato non viene
effettuato. Questo comportamento è causato dalla presenza della pipeline (vedi lez. 20).
Per tener conto di questo problema l’assemblatore del MIPS deve provvedere a modificare il
codice nel caso in cui l’operazione successiva al branch sia stata erroneamente eseguita.
8.8.2 Istruzioni varie dell’architetture MIPS I
•syscall
trasferisce il controllo a codice appartenente al sistema
operativo (modalita’ kernel) nella quale si ha completo
accesso alla macchina
•break
in corrispondenza di questa istruzione si verifica una “trap”
ovvero un trasferimento tipo “system call” verso il codice
dell’ exception handler
•istr. coprocessore
Supporto per i dati floating point
•istruzioni TLB
Supporto per la memoria virtuale
•rfe (return from exeception)
ripristina il precedente stato della interrupt mask e dei bit
di modalita’ kernel user all’interno dello status register al
termine della routine di servizio di un interact
•load word left/right
Supporto per il caricamento di word non-allineate
•store word left/right
Supporto per la scrittura di word non-allineate
ESERCIZIO
8.16
Obbiettivo: vediamo attraverso un esercizio pratico come cambiano le prestazioni apportando
delle modifiche al calcolatore:
Risultati delle misurazioni su una data macchina base:
Op
Freq
Cicli
ALU
50%
1
Load
20%
5
Store
10%
3
Branch
20%
2
/CPI
0.5
1.0
0.3
0.4
____
2.2
%Tempo
23%
45%
14%
18%
Ricordando che CPI (cicli di clock della CPU)= numero di istruzioni nel programma x
numero medio di cicli di clock per istruzione
Di quanto migliorerebbero le prestazioni:
• inserendo una cache che riduca il tempo medio per caricare un dato a 2 cicli ?
Con questa modifica CPI relativo al load si riduce a 0,4 mentre quello totale a 1,6.
• utilizzando una branch-prediction in grado di ridurre di 1 ciclo il tempo del branch ?
Con questa modifica CPI relativo al branch si riduce a 0,2 mentre quello totale a 2
• se fosse possibile eseguire 2 istruzioni aritmetico-logiche in un solo ciclo ?
Con questa modifica CPI relativo all’ ALU si riduce a 0,25 mentre quello totale a 1,95
8.9 CONFRONTI CON ALTRE ARCHITETTURE
Come già detto in precedenza,un alternativa al modello MIPS e quindi alla filosofia RISC è
offerta dalla filosofia CISC che fornisce operazioni più potenti, riduce il numero di istruzioni
eseguite ma causa un ciclo di clock più lungo e CPI più elevato.
Quest’ultimo approccio è quello seguito dai processori VAX che rendono l’assembly più facile e
minimizzano le dimensioni del codice grazie ad una più ampia gamma di istruzioni a
disposizione. Questo fattore ha però come risultato un incremento della lunghezza
dell’istruzioni stesse che spazia tra 1 e 54 byte.
8.9.1 PowerPC indirizzamento tramite indice (indexed addressing) indirizzamento
di tipo update
Il PowerPC,costruito da IBM e Motorola e montato sul Macintosh della Apple, ha forti
analogie con il MIPS: entrambi hanno i registri di 32 bit, le istruzioni lunghe 32 bit ed il
trasferimento dati possibile soltanto tramite operazioni di tipo load e store. La principale
differenza consiste invece in due modi di indirizzamento addizionali (indicizzato e di tipo
update) e in alcune operazioni in più.
8.17
8.9.1.1 Indirizzamento tramite indice(indexed addressing)
Questo indirizzamento permette di sommare valori di due registri.
Per esempio il codice MIPS:
add $to, $ao, $s3
lw
$t1, 0($t0)
nel PowerPC è sostituito da una sola istruzione:
lw
$t1, $a0+$s3
a. Indexed addressing
op
rs
rt
rd
...
Memor y
Register
+
Word
Register
Figura 8.10: Indexed Addressing.
8.9.1.2 Indirizzamento di tipo update
L‘idea di base di questo modo di indirizzamento è quella di avere una nuova versione delle
istruzioni per il trasferimento dei dati che automaticamente incrementi il registro di base in
modo da puntare alla parola successiva ogni volta che viene fatto il trasferimento di un dato.
Dato che il MIPS usa l’indirizzamento a byte e una parola è composta da 4 byte, questo nuovo
modo di indirizzamento è equivalente alla coppia di istruzioni
lw
$t0, 4($s3)
addi $s3, $s3, 4
il PowerPC include per questo un ‘istruzione del tipo
lwu
$t0, 4($s3)
che quindi aggiorna il valore del registro all’interno dell’istruzione di load.
Le istruzioni che il PowerPC implementa oltre a quelle del MIPS sono la load e la store multiplo
che possono trasferire fino a 32 parole in una singola istruzione e che utilizzate in modo
congiunto consentono di riallocare velocemente i dati da una porzione di memoria ad un’altra.
Il PowerPC ha inoltre un particolare registro distinto dagli altri 32 che ha le funzioni di
contatore e che è utilizzato per migliorare le prestazioni nell’esecuzione dei cicli for.
b. Update addressing
op
rs
rt
Register
Address
Memory
+
Figura 8.11: Update addressing.
8.18
Word
8.9.2 Intel 80X86
L’architettura dell’ 80x86 è frutto del lavoro indipendente di molti gruppi di persone ed è in
evoluzione dal suo ingresso nel 1978, con l’aggiunta progressiva di nuove funzioni al set di
istruzioni originale. Le tappe fondamentali della sua evoluzione sono:
1978: L’Intel 8086 e’ annunciato (16 bit architecture).
1980: Viene introdotto l’8087 floating point coprocessor.
1982: Il 286 aumenta lo spazio di indirizzamento a 24 bit, complesso modo di mappatura degli
indirizzi (segmnentazione).
1985: Il 386 porta tutto a 32 bits, paginazione, modo virtuale 8086.
1989: Il 486 integra cpu, coprocessore e cache su un unico chip
vengono aggiunte istruzioni per la multiprogrammazione (test-and-set).
1992: Pentium, 2 pipeline (prima cpu superscalare Intel).
1995: Pentium MMX (introduzione di istruzioni vettoriali e multimediali).
1997: Pentium-Pro, esecuzione fuori ordine (parallelismo a livello di istruzioni).
1998: Pentium-II, altre istruzioni multimediali (SSE). Cache piu’ grandi.
1999: Pentium-III, SSE2.
2001: Pentium-4, pipeline a 20 stadi, trace-cache, branch prediction migliorata, register
renaming, 7 ALU, operazioni SSE2 a 128 bit.
Alcuni commenti famosi su questa architettura:
• “La storia dell’x86 mostra l’impatto della ‘gabbia dorata ’ della compatibilità”
• “Le caratteristiche sono state aggiunte come nel riempire una valigia già chiusa”
• “Un’architettura difficile da spiegare e impossibile da apprezzare”
8.9.3 architettura del processore 386
L’evoluzione si nota chiaramente osservando i registri del 386: il 386 infatti estende tutti i
registri (a parte i registri di segmento) da 16 a 32 bit mettendo una E davanti al nome di
ciascuno per indicare la versione a 32 bit.
32
bit
EAX
GPR0
ECX
GPR1
EDX
GPR2
EBX
GPR3
ESP
GPR4
EBP
GPR5
ESI
GPR6
EDI
GPR7
Operandi (registri)
CS
SS
DS
ES
FS
GS
EIP
EFLAGS
Figura 8.12: Registri utilizzati nel 386.
8.19
8.9.3.1 Combinazioni degli operandi nel 386
Le istruzioni aritmetiche, logiche e di trasferimento dati hanno sempre un operando che
funge sia da sorgente che da destinazione (ad esempio ADD EAX,# 6765 che significa
EAX = EAX + 6765). Inoltre uno degli operandi può essere in memoria a differenza del MIPS
e del PowerPC (ad esempio MOVW EBX,[EDI+ 45] che significa EBX = M[EDI + 45].
Tabella 8.9: Utilizzo dei registri come operandi.
Primo operando
sorgente/destin
azione
Secondo
operando
solo sorgente
Registro
Registro
Registro
Immediato
Registro
Memoria
Memoria
Registro
Memoria
Immediato
8.9.3.2 Modi di indirizzamento del 386
I modi di indirizzamento relativi ai dati in memoria sono sette. Il displacement può essere di 8
o di 32 bit.
Ci sono inoltre delle restrizioni sui registri utilizzabili nei diversi modi come mostrato in
tabella 8.10
Tabella 8.10: Restrizioni sui registri.
Modo
Descrizione
Restrizione sui registri
Codice MIPS eqivalente
Register indirect
L’indirizzo e’ un registro
No ESP o EBP
lw $s0,0($s1)
Based mode con
displacement a 8
o 32 bit
L’indirizzo e’ il contenuto del
registro
di
base
piu’
il
displacement
No ESP o EBP
lw $s0,100($s1)
# displ<= 16 bit
Base plus scaled
index
L’indirizzo e’
base+(2scale x index)
dove ‘scale’ puo’ valere
0, 1 ,2 3
Base: qualsiasi GPR
Index: no ESP
sll $t0,$s1,2
add $t0,$t0,$s1
lw $s0,0($t0)
Base plus scaled
index
con
displacement a 8
o 32 bit
L’indirizzo e’
base+(2scale x index) +
displacement
dove ‘scale’ puo’ valere
0, 1 ,2 3
Base: qualsiasi GPR
Index: no ESP
sll $t0,$s1,2
add $t0,$t0,$s1
lw $s0,100($t0)
# displ<= 16 bit
Quasi tutte le operazioni possono lavorare su dati a 8 bit o di dimensioni maggiori (16 o 32
bit) a seconda del modo utilizzato. Alcuni programmi devono chiaramente operare su dati di 8,
16 e 32 bit e per questo il 386 fornisce un modo efficiente di specificare la loro dimensione
senza estendere il codice in modo significativo. La dimensione è specificata da uno dei bit del
registro di segmento del codice e per sovrascrivere l’opzione occorre precedere l’istruzione
da un prefisso di 8 bit che specifica all’elaboratore che il dato utilizzato ha dimensione
diversa da quella predefinita (16 o 32 bit). Un altro metodo di specificare la dimensione del
dato è quello di modificare un bit del registro CS.
8.20
8.9.3.3 Prefissi delle istruzioni nel 386
I prefissi possono essere utilizzati anche per modificare i registri di segmento predefiniti,
per impedire l’ accesso al bus nella realizzazione di un semaforo per trasferimenti
atomici(486) o per ripetere l’istruzione successiva fino a quando il registro ECX scendeva a
zero. Un altro prefisso serve infine per poter modificare la dimensione degli indirizzi.
8.9.3.4 esempi di istruzioni nel 386
Le operazioni sui numeri interi del 386 possono essere suddivise in quattro categorie:
• Istruzioni per lo spostamento dei dati (move, push, pop) (punti 4, 5, 6 in tab. 8.11).
• Istruzioni aritmetiche, logiche e di confronto (punti 7, 8 in tab. 8.11).
• Istruzioni di controllo di flusso (salti, chiamate a procedura e ritorno) (punti 1, 2, 3 in
tab. 8.11).
• Istruzioni per la gestione delle stringhe (9).
N.B: si supportano anche operazioni che nella realtà sono usate raramente.
Tabella 8.11: Istruzioni supportate dal 386.
Function
Instruction
JE name
If equal (CC) EIP = name};
EIP – 128 ≤ name < EIP + 128
2
JMP name
{EIP = NAME};
3
CALL name
SP = SP – 4; M[SP] = EIP + 5; EIP = name;
4
MOVW EBX,[EDI + 45]
EBX = M [EDI + 45]
PUSH ESI
SP = SP – 4; M[SP] = ESI
POP EDI
EDI = M[SP]; SP = SP + 4
ADD EAX,#6765
EAX = EAX + 6765
TEST EDX,#42
Set condition codea (flags) with EDX & 42
MOVSL
M[EDI] = M[ESI];
EDI = EDI + 4; ESI = ESI + 4
1
5
6
7
8
9
8.21
8.9.3.5 Riepilogo operazioni dell’architettura x86
Tabella 8.11: Riepilogo operazioni dell’architettura x86.
ISTRUZIONE
Significato
Controllo
Salti condizionati e incondizionati
JNZ, JZ
Salto condizionato a EIP+offset_8_bit;
JMP
Salto incondizionato; il displacement puo’ essere a 8 o a 16 bit
CALL
Chiamata a procedura con displ .. a 16 bit; ind. ritorno nello stack
RET
Preleva dallo stack l’ind di ritorno ed esegue il salto
LOOP
Ciclo: decrementa ECS e salta a EIP+offset_8_bit se ECX != 0
Trasferimento dati
Spostamento di dati tra registri e registri e memoria
MOV
Sposta un dato da registro a registro o da registro a memoria
PUSH, POP
Mette un dato nello stack ; preleva un dato dallo stack
LES
Carica nel registro ES
Aritmetiche logiche
Operazioni aritmentiche e logiche sui dati nei registri o in memoria
ADD, SUB
Somma/sottrae il sorgente dalla destinazione; formato
CMP
Confronta il sorgente con la destinazione; formato
SHL, SHR, RCR
Shift a sinistra/destra; rotazione verso destra con uso del
CBW
Converte il byte meno significativo di EAX in una word a 16 bit
TEST
Modifica i FLAG facendo un finto AND fra sorgente e destinazione
INC, DEC
Incrementa/decrementa; formato
OR, XOR
OR logico; OR esclusivo; formato
Stringhe
Trasferim . fra stringhe; lunghezza data dal prefisso di ripetizione
MOVS
Copia un byte dalla stringa puntata da ESI a quella puntata da E DI; i due puntatori
vengono incrementati; puo’ essere ripetuta
LODS
Carica un byte/word/ double_word in EAX
JNE=JNZ , JE=JZ
reg/mem
reg /mem
carry
reg/mem
reg/mem
Nel 386 i salti condizionati si basano sui codici di controllo o flag, modificati in seguito ad
un’operazione esplicitamente con l’istruzione test. Inoltre gli indirizzi di salto PC-relative
devono essere specificati come numero di byte poiché a differenza del MIPS, nel 386 non è
vero che tutte le istruzioni sono lunghe 32 bit.
Le istruzioni per la gestione delle stringhe nella maggior parte dei programmi non vengono
utlizzate essendo in generale più lente delle procedure software equivalenti (vedi paragrafo
8.9.5).
8.9.4 80x86: formati di istruzione
La codifica delle istruzioni è molto complessa, con molti formati diversi per le istruzioni, che
variano da un byte a 17 byte (è considerata la peggiore caratteristica di questa architettura).
La figura mostra il formato di molte delle istruzioni usate. Il byte del codice operativo
contiene solitamente un bit che indica se l’operando è da 8 o 32 bit. Il codice operativo di
alcune istruzioni include il modo di indirizzamento ed il registro; questo si verifica in molte
istruzioni. Altre invece utilizzano un “postbyte” o extra byte per il codice operativo, che
contiene le informazioni relative al modo di indirizzamento; questo byte aggiuntivo è presente
in molte delle istruzioni che accedono alla memoria. Il modo di indirizzamento base con indice
usa un secondo byte aggiuntivo.
8.22
a. JE EIP + displacement
4
4
JE
Condition
8
Displacement
b. CALL
8
CALL
32
Offset
c. MOV EBX, [EDI + 45]
1 1
8
6
r-m
d w
MOV
postbyte
8
Displacement
d. PUSHESI
5
3
Reg
PUSH
e. ADD EAX, #6765
4
3 1
ADD Reg w
f. TEST EDX, #42
7
1
TEST
w
32
Immediate
32
8
Postbyte
Immediate
Figura 8.13: Formati di istruzione.
8.9.5 conclusioni sull’80x86
L’ Intel ha costruito un processore a 16 bit due anni prima di quelli dei suoi
concorrenti(Motorola 68000), ma in generale realizzare l’ 8086 è più difficile di un
calcolatore di tipo MIPS. Quello che all’ 80x86 manca in stile è però compensato dalla
diffusione,rendendolo apprezzabile se visto dalla giusta prospettiva. Ciò che lo salva è che i
componenti dell’architettura usati più frequentemente non sono troppo difficili da
implementare, permettendo di migliorare rapidamente le prestazioni dei programmi. Per fare
ciò i compilatori devono però evitare le parti dell’ architettura difficili da implementare.
ESEMPIO: Attenzione: spesso le istruzioni più potenti non offrono le migliori prestazioni.
Nella tabella seguente sono mostrati tre metodi per effettuare il trasferimento di dati da 32
bit da una porzione di memoria ad un’altra. Nella prima colonna è riportata la velocità di
trasferimento dati per un processore pentium 133, DRAM EDO da 60 ns e cache da 256 KB
che utilizza la funzione move ripetuta (REPS MOVE). La seconda e la terza mostrano invece
come la velocità di trasferimento sia maggiore utilizzando le istruzioni standard (load e store)
presenti su tutti i calcolatori.
8.23
REPS MOVE
LOAD/STORE in
registri
+unrolling
LOAD/STORE in
registri FP (piu’ lunghi)
40 MB/s
60 MB/s
80 MB/s
Figura 8.14: Confronto tra metodi di trasferimento.
8.9.6 errori e trabocchetti (esempio)
Contrariamente a quanto si possa pensare programmare in Assembly non sempre permette di
ottenere prestazioni migliori come si deduce dal confronto effettuato tra le prestazioni della
versione in linguaggio C ed assembler delle procedure ordina e scambia
Linguaggio
TCPU
Assembly
37.9s
C
25.3s
Figura 8.15: Confronto tra linguaggi Assembly e C.
8.24
LEZIONE 9
Eccezioni ed interrupt
9.1 INTRODUZIONE
Le eccezioni sono eventi che cambiano il normale flusso di esecuzione delle istruzioni. Sono
utilizzate per gestire eventi inattesi e richieste di servizi da parte dei dispositivi di I/O,
trasferendo il controllo dell’esecuzione del programma ad una routine di gestione (v. Figura
9.1).
user program
Exception
return from
exception
normal control flow:
sequential, jumps, branches, calls, returns
Figura 9.1. Meccanismo di gestione di un’eccezione.
9.2 TERMINOLOGIA
Tipicamente si distingue fra eccezioni (traps o syscall) ed interruzioni (interrupts). Le
eccezioni sono causate da eventi interni al processore (ad es. un overflow aritmetico) mentre
le interruzioni sono causate da eventi esterni, e vengono utilizzate dai dispositivi di I/O per
comunicare al processore informazioni sulle operazioni di input/output.
Tra i due termini comunque non c’è molta distinzione, ad esempio molti autori e molte
architetture utilizzano il termine “interrupt” per identificare entrambi gli eventi. Ad esempio
nell’architettura Intel 80x86 si parla di interrupt per tutti gli eventi.
Nel caso dell’architettura MIPS (v. anche tab. 9.1), invece, si parla di:
• eccezione: quando si parla di un cambiamento inatteso del flusso di controllo senza
voler specificare se sia dovuto a cause interne o esterne;
• interrupt: eccezioni causate da eventi esterni .
Tabella 9.1. Classificazione usata nel MIPS.
TIPO DI EVENTO
Richiesta di un dispositivo di I/O
Chiamata al sistema operativo
programma utente
Overflow aritmetico
Uso di un’istruzione indefinita
Malfunzionamento dell’hardware
Malfunzionamento dell’hardware
da
parte
di
9.1
un
PROVENIENZ
A
Esterna
Interna
TERMINOLOGIA MIPS
Interruzione
Eccezione
Interna
Interna
Interna
Esterna
Eccezione
Eccezione
Eccezione
Interruzione
9.3 PRINCIPALI DIFFERENZE TRA INTERRUPT ED ECCEZIONI
La differenza principale tra le eccezioni interne e le eccezioni esterne (interrupt) è che le
prime sono sincronizzate con il programma, le seconde invece non lo sono.
Questo significa che, se il programma viene rieseguito milioni di volte con lo stesso input, le
eccezioni si verificano negli stessi punti e ogni volta; invece gli interrupt dipendono, per
esempio, da quando una persona al terminale preme il tasto “invio”.
Il motivo della riproducibilità delle eccezioni a differenza degli interrupt è dovuta al fatto
che solo le prime sono direttamente causate dal programma.
Come accennato, altra differenza fondamentale è quella dovuta alle cause che li provocano: gli
interrupt sono causati da cause esterne al processore, mentre invece le eccezioni sono
riconducibili ad eventi direttamente gestiti dal processore.
9.4 ROUTINE
HANDLER)
DI
GESTIONE
DELL'ECCEZIONE
(EXCEPTION
Nel progettare l’unità di controllo si deve fare molta attenzione a tenere in considerazione il
problema delle eccezioni. Infatti, tentare di aggiungere la gestione delle eccezioni potrebbe
ridurre notevolmente le prestazioni e rendere più difficile l’implementazione dell’unità stessa.
Per quanto riguarda le procedure per la gestione dell'eccezione esistono tre approcci diversi
in base all'architettura:
• Vettore di Interruzione (approccio tradizionale)
• Tabella della Routine di Gestione (approccio RISC)
• Salto ad indirizzo fisso (approccio MIPS)
9.5 VETTORE DI INTERRUZIONE
Il vettore di interruzione non è altro che l’indirizzo della routine di gestione dell’eccezione.
Per ogni tipo di eccezione esiste una routine specifica e per ciascuna routine può essere
specificato un indirizzo diverso. Tali indirizzi si memorizzano in una tabella chiamata tabella
dei vettori di interruzione (Figura 9.2). Nel momento in cui si verifica un’eccezione si
preleverà da tale tabella il corrispondente vettore. In corrispondenza di questa operazione il
processore non eseguirà più il codice del mio programma, ma quello specificato nella routine di
gestione.
Sia 'base' l'indirizzo base di questa tabella, nella fase di gestione dell’eccezione l'indirizzo di
PC (Program Counter) diventerà: MEM[base + causa*4].
Questo metodo viene utilizzato da processori quali Motorola 68000 e Intel 80x86, e già
molto tempo fa veniva utilizzato da sistemi come IBM 370 e Vax.
9.2
base
ind. routine 0
ind. routine 1
ind. routine 2
causa*4
…
Vettore di Interruzione
Routine di
Gestione dell’Eccezione
ind. routine causa
…
ind. routine m
Tabella dei
Vettori di Interruzione
Figura 9.2. Tabella dei vettori di interruzione.
9.6 TABELLA DELLA ROUTINE DI GESTIONE
In questo tipo di approccio, le routine di gestione associate ad ogni causa vengono
memorizzate direttamente in una tabella chiamata appunto tabella della routine di gestione
(Figura 9.3). Quando si verifica un’eccezione si preleva direttamente dalla tabella la routine
associata all’eccezione. Chiaramente, in questo caso la routine deve essere particolarmente
corta perché deve essere contenuta in una locazione di memoria limitata.
Sia 'base' l'indirizzo base di questa tabella, nella fase di gestione dell’eccezione l'indirizzo di
PC diventerà: MEM[base + causa*16].
Questo metodo è utilizzato da processori quali SunSparc, HP PA, Motorola M88K.
base
Routine 0
Routine 1
Routine 2
…
Routine di Gestione
causa*16
Routine causa
…
Routine m
Tabella delle
Routine di Gestione
Figura 9.3. Tabella della Routine di Gestione.
9.7 SALTO AD INDIRIZZO FISSO
Il salto ad indirizzo fisso è un metodo di gestione delle eccezioni che prevede, qualora si
verifichi un’eccezione, che il processore interrompa il programma in esecuzione per poi
lanciare la routine di gestione che si trova ad un indirizzo fisso, che nel MIPS vale sempre
0x8000’0080 (Figura 9.4). In questo approccio non viene usato lo stack per il salto; la routine
di gestione è unica e deve analizzare i vari casi di eccezioni ogni volta che si presentano.
Nella fase di gestione dell’eccezione l’indirizzo di PC sarà: 0x8000’0080.
9.3
Per distinguere la causa dell’eccezione si usa un registro di appoggio chiamato “cause” (causa),
la cui trattazione è rimandata al par. 9.8.3.
causa
0x8000’0080
…
Routine di Gestione
Figura 9.4. Salto ad indirizzo fisso.
9.8 GESTIONE DI ECCEZIONI ED INTERRUZIONI
La prima operazione che deve eseguire una routine di gestione delle eccezioni è salvare lo
stato in cui si trova il processore al verificarsi dell’eccezione. Il processore MIPS ha a
disposizione quattro registri speciali (a 32 bit) per il salvataggio dello stato (tab. 9.2).
Questi registri si trovano in una parte della CPU chiamata coprocessore 0.
Tabella 9.2. Registri del coprocessore 0.
NOME
REGISTRO
NUMERO
REGISTRO
BadVAddr
8
Stato
Causa
EPC
12
13
14
USO
Registro contenente l’indirizzo di memoria a cui è stato fatto
riferimento
Maschera delle interruzioni e bit di abilitazione
Tipo dell’interruzione e bit dell’interruzione pendente
Registro contenente l’indirizzo dell’istruzione che ha causato
l’interruzione
9.8.1 Registro di Stato: modalità user/kernel
Il sistema operativo è un programma speciale che ha il compito di gestire l’interfacciamento
hardware/software e le risorse del sistema, utilizzando una modalità privilegiata chiamata
“modalità kernel”; esiste un’altra modalità chiamata “ modalità utente” (o “user”). La modalità
kernel permette di accedere direttamente a tutte le risorse del calcolatore, mentre la
modalità utente, utilizzata da tutti gli altri software, non ha questo privilegio.
Al verificarsi di un’eccezione il processore:
1) salva l’indirizzo dell’istruzione responsabile dell’eccezione nell’EPC (Exception
Program Counter)
2) salva nel registro Causa il tipo di eccezione
3) eventualmente se c’è un indirizzo di memoria al quale è stato fatto riferimento
viene salvato nel registro BadVAddr
4) modifica opportuni bit del registro di stato (vedi par. 9.8.2)
9.4
5) trasferisce il controllo alla routine di gestione (nel kernel, non nello spazio di
indirizzamento dell’utente).
Questo codice esamina la causa dell’eccezione ed esegue un salto alla porzione appropriata del
sistema operativo che, in risposta all’eccezione, termina il processo che l’ha provocata o
svolge una determinata azione.
Il sistema operativo provvede poi a fornire un determinato servizio al programma utente, e a
seguire l’azione predeterminata in risposta alla causa dell’eccezione o a terminare l’esecuzione
del programma segnalando un errore. Dopo aver compiuto le azioni necessarie in risposta
all’eccezione, il sistema operativo può terminare il programma utente o riprenderne
l’esecuzione, utilizzando EPC per determinare lo stato da cui ripartire.
9.8.2 Registro di Stato
La figura 9.5 mostra i campi principali del registro Stato:
8
5
15
K
Maschera
delle interruzioni
4
E
Vecchi
3
K
2
E
Precedenti
1
K
0
E
Attuali
Figura 9.5. Campi del registro Stato.
Con riferimento al processore LR 33000 della LSI Logic, una implementazione commerciale
del processore MIPS, il campo maschera delle interruzioni contiene un bit per ciascuna delle
sei possibili sorgenti di interruzione hardware e delle due possibili sorgenti di interruzione
software; un bit a 1 abilita l’interruzione corrispondente, mentre un bit a 0 la disabilita. I 6
bit meno significativi del registro Stato implementano uno stack di profondità tre per i bit
kernel/utente (K) e abilitazione delle interruzioni (E).
Il bit kernel/utente vale 0 se il programma aveva richiesto servizi al kernel (nucleo del
sistema operativo), ed era quindi in esecuzione codice del kernel, quando si è verificata
l’interruzione, e 1 se era in modalità utente; se il bit di abilitazione delle interruzioni è a 1 le
interruzioni sono abilitate, se è a 0 sono disabilitate. Quando si verifica un’interruzione questi
6 bit sono scalati a sinistra di 2 posizioni, per cui i bit attuali diventano i bit precedenti, ed i
bit precedenti diventano bit vecchi (i bit vecchi vengono scartati). Entrambi i bit attuali sono
posti a 0, per cui il gestore delle interruzioni viene eseguito inizialmente nel kernel e con le
interruzioni disabilitate.
9.8.3 Registro Causa
La figura 9.6 mostra i campi principali del registro Causa:
15
10
Interruzioni
pendenti
5
2
Codifica
delle eccezioni
Figura 9.6. Campi del registro Causa.
Nei processori MIPS reali questo registro contiene dei campi aggiuntivi che riportano, ad
esempio, se l’istruzione che ha causato l’interruzione era eseguita in un branch delay slot
(viene eseguita l’istruzione che segue immediatamente il salto anche se questo non viene
fatto), quale coprocessore ha causato l’eccezione oppure se c’è un’interruzione software
pendente. Questa parte sarà approfondita nel capitolo 20 quando verrà trattata la pipeline.
9.5
I sei bit delle interruzioni pendenti corrispondono alle sei sorgenti di interruzione; un bit
assume il valore 1 quando si verifica un’interruzione non ancora servita del corrispondente
livello. Il campo codifica delle eccezioni descrive la causa dell’eccezione secondo la codifica
mostrata nella tabella 9.3.
Tabella 9.3. Codifica delle cause delle eccezioni.
NUMERO
NOME
0
4
INT
ADDRL
5
6
7
8
9
10
12
ADDRS
IBUS
DBUS
SYSCALL
BKPT
RI
OVF
DESCRIZIONE
Interruzione esterna
Eccezione dovuta ad errore di indirizzamento (caricamento o prelievo di
un’istruzione)
Eccezione dovuta ad errore di indirizzamento (memorizzazione)
Errore sul bus durante il prelievo di un’istruzione
Errore sul bus durante il caricamento o la memorizzazione di un dato
Eccezione dovuta ad una chiamata di sistema
Eccezione dovuta ad un breakpoint
Eccezione dovuta ad un’istruzione riservata
Eccezione dovuta ad un overflow aritmetico
9.9 INTERRUZIONI PRECISE/IMPRECISE
Un ‘interruzione che conserva lo stato della macchina, come se il programma avesse eseguito
fino all’istruzione “colpevole” esclusa, viene detta interruzione precisa, ed ha le seguenti
proprietà:
1. Il PC è salvato in un posto noto
2. Tutte le istruzioni precedenti a quella puntata dal PC sono state eseguite
completamente.
3. Nessuna istruzione successiva a quella puntata dal PC è stata eseguita.
4. Lo stato di esecuzione dell’istruzione puntata dal PC è noto.
In questo modo non si impedisce l’inizio dell’esecuzione delle istruzioni successive a quella
puntata dal PC, ma ogni cambiamento che esse apportano ai registri o alla memoria deve
essere annullato prima dell’interruzione.
Questa è una posizione adottata nei calcolatori IBM; ha il vantaggio di rendere il codice di
sistema trasportabile senza problemi su un’implementazione diversa dalla stessa architettura,
ma allo stesso tempo è difficile da implementare in presenza di pipeline, out-of-order
execution (v. Cap. 20).
Le interruzioni che non hanno queste caratteristiche vengono dette interruzioni imprecise; in
questo caso è il software di sistema che deve occuparsi di scoprire in che stato il sistema si
trova e quindi prendere le opportune decisioni.
I processori MIPS utilizzano questo tipo di interruzioni. Le macchine con interruzioni
imprecise solitamente riversano una gran quantità di stati interni sulla pila, per dare al
sistema operativo la possibilità di capire cosa stia succedendo.
Questa soluzione ha il vantaggio di snellire l’hardware del microprocessore nei casi più
frequenti, ma il fatto di dover salvare grandi quantità di informazioni in memoria ad ogni
interruzione, rende le interruzioni e i ripristini molto lenti. Ciò porta ad avere delle CPU
superscalari molto veloci che spesso non sono adatte a lavorare in real time a causa del fatto
che la gestione delle interruzioni è lenta.
Alcuni processori sono progettati in modo tale che alcune interruzioni e operazioni di trap
siano precise e altre no; ad esempio avere interruzioni di I/O precise e trap imprecise non è
9.6
grave, in quanto non si dovranno fare tentativi di far ripartire il processo in esecuzione.
Alcune macchine hanno un bit che può essere impostato per fare in modo che tutte le
interruzioni diventino precise; il lato negativo dell’impostare questo bit è che si forza la CPU a
tener traccia di ogni cosa che stia facendo, e a mantenere copie dei registri così che possa
generare interruzioni precise in ogni istante; tutto questo sovraccarico è di grande impatto
sulle prestazioni.
Avere interruzioni precise significa avere una logica delle interruzioni all’interno della CPU
estremamente complessa, e di conseguenza aumentare la complessità della progettazione
dell’unità di controllo; il tutto rende la CPU più lenta.
Il motivo principale per cui in alcuni processori si evita l’uso degli interrupt precisi è legato
essenzialmente a obiettivi di prestazioni elevate. D’altro canto, interruzioni imprecise rendono
il sistema operativo troppo lento e complicato.
9.10 INSERIMENTO DELLA LOGICA PER GESTIRE LE ECCEZIONI
Il processore MIPS finora studiato può essere descritto come una macchina a stati finiti
(finite state machine, FSM). L’unità di controllo a stati finiti corrisponde essenzialmente ai
cinque passi necessari al processore per eseguire un’istruzione, dove ciascun passo della
macchina a stati finiti corrisponde ad un ciclo di clock:
• IF (Instruction Fetch): prelievo dell’istruzione dalla memoria istruzioni
• ID (Instruction Decode): decodifica dell’istruzione e lettura dei registri
• EX (Execute): esecuzione dell’istruzione
• MEM (Memory): lettura/scrittura dei dati da/verso la memoria dati
• WB (Write Back): scrittura del risultato nei registri
Esistono cinque classi di istruzioni come mostrato dalla tabella 9.4, e ciascuna di esse richiede
passi diversi per essere eseguita.
Tabella 9.4. Schema riassuntivo dei passi intrapresi durante l’esecuzione delle diverse classi di istruzioni.
NOME DEL
PASSO
Prelievo
dell’istruzione
Decodifica
dell’istruzioni
e lettura dei
registri
Esecuzione
istruzione
Lettura e
scrittura dati
Scrittura
risultato
AZIONE PER
ISTRUZIONE
DI TIPO-R
AZIONE PER
ISTRUZIONI DI
LETTURA DATI
AZIONE PER
ISTRUZIONI DI
SCRITTURA DATI
AZIONE PER
SALTI
CONDIZIONATI
AZIONE PER SALTI
INCONDIZIONATI
IR <= MEM[PC]
PC <= PC + 4
A<=R[rs]
B<= R[rt]
S <= PC + SX
S <= A fun B
R[rd] <= S
S <= B + SX
S <= B + SX
M <= MEM[S]
MEM[S] <= A
R[rs] <= M
if A = B
then PC <= S
S <= PC op ZX
PC <= S
La macchina a stati finiti è composta da diverse parti: dato che i primi due passi di esecuzione
sono identici in tutte le istruzioni, i primi due stati della macchina saranno comuni a tutte le
operazioni; invece i passi da 3 a 5 dipendono dal tipo di istruzione da eseguire.
Al termine dell’esecuzione dell’ultimo passo di una particolare classe di istruzioni, la macchina
dovrà ritornare allo stato iniziale per cominciare a prelevare l’istruzione successiva (Figura
9.10)
9.7
Ciascun nodo rappresenta uno stato della macchina, mentre gli archi rappresentano le
transizioni da uno stato all’altro.
I nodi, per una più facile comprensione della macchina, sono etichettati da 0000 a 0111 e
contengono al proprio interno le operazioni che vengono svolte.
Richiamiamo brevemente i tre formati delle istruzioni del MIPS.
Per le istruzioni R-type (Figura 9.7) ci sono 6 campi:
• op: operazione base dell’istruzione, chiamata tradizionalmente codice operativo o
opcode
• rs: primo operando sorgente (registro)
• rt: secondo operando sorgente (registro)
• rd: registro destinazione che contiene il risultato dell’operazione
• shamt: valore dello scalamento (shift amount)
• funct: funzione. Seleziona una variante specifica dell’operazione base definita in op
op
rs
rt
rd
6 bit
5 bit
5 bit
5 bit
shamt
5 bit
Figura 9.7. Campi per le istruzioni di formato R-type.
funct
6 bit
Per il formato I (usato nelle istruzioni di lettura e scrittura della memoria (LW, SW e BEQ)
(Figura 9.8) ci sono 4 campi, i primi 3 sono gli stessi del precedente tipo (op, rs e rt), ma in
questo caso rt rappresenta una costante di offset, mentre l’ultimo è il campo address (SX),
che indica una locazione di memoria.
op
6 bit
rs
5 bit
rt
5 bit
address
16 bit
Figura 9.8. Campi per le istruzioni di tipo I.
Per le istruzioni di formato J ci sono 2 campi (Figura 9.9), op e il campo per l’indirizzo di salto,
address (ZX).
op
6 bit
address
26 bit
Figura 9.9. Campi per le istruzioni di tipo J.
La macchina quando si trova all’inizio nello stato 0000 preleva dalla memoria l’istruzione da
eseguire e la carica in IR (IR <= MEM[PC] ) e aggiunge 4 al PC in modo da far puntare al ciclo
successivo la prossima istruzione (PC <= PC + 4).
A questo punto transita nello stato 0001 il quale effettuerà la decodifica dell’istruzione
prelevata nello stato precedente.
In questo stato e nel precedente non si conosce ancora quale sia l’istruzione, quindi si possono
compiere solamente azioni applicabili a tutte le operazioni (come prelevare l’istruzione nello
stato 0000) o che non siano dannose, nel caso in cui l’istruzione non sia quella ipotizzata.
Quindi in questo stato si possono leggere i due registri rs e rt dell’istruzione, poiché non è
dannoso leggerli anche nel caso in cui non siano richiesti.
Quindi i campi rs e rt vengono letti dal register file e memorizzati nei registri temporanei A
(A <= R[rs]) e B (B <= R[rt]). Viene anche calcolato l’indirizzo di destinazione dei salti
9.8
condizionati, il che non è dannoso dal momento che si può ignorare tale valore se l’istruzione
non sia di branch (S <= PC+SX, dove S è un registro d’appoggio).
Dopodichè si passerà ad un diverso stato in base alla classe di istruzioni da eseguire.
Quando l’istruzione da eseguire è un salto incondizionato (J) si usa ZX e il registro PC per
calcolare l’indirizzo della prossima istruzione da eseguire, e questo viene caricato in S.
L’indirizzo è ottenuto concatenando i 26 bit dell’indirizzo con i 4 bit più significativi del PC
(S <= PC op ZX). Successivamente, prima di tornare nello stato di partenza, il contenuto di S
verrà caricato in PC.
Le istruzioni di lettura e scrittura seguono due stati diversi(1000 e 1011), ma con la stessa
funzione.
Il registro B è usato insieme a SX per calcolare l’offset (S <= B+SX) della memoria in cui il
MIPS andrà a prelevare o scrivere il contenuto.
IR <= MEM[PC]
PC <= PC + 4
0000
“instruction fetch”
Write-back
A<=R[rs]
“decode”
B<= R[rt]
S <= PC +SX
0001
R-type
J
Memory
S <=A fun B S<= PC op ZX
0100
0110
LW
BEQ
SW
S <= B + SX
1000
Execute
M <= MEM[S]
1001
S <= B + SX
1011
If A = B
then PC <= S
0010
MEM[S] <= A
1100
R[rd]<= S
0101
PC <= S
0111
R[rs]<= M
1010
R[rt]
<= M
1010
Figura 9.10. Macchina a stati finiti che descrive il MIPS.
Se si tratta di SW la macchina passa nello stato 1100, memorizza il contenuto di A nella
posizione di memoria indirizzata da S (MEM[S] <= A) e ritorna allo stato iniziale.
Se si tratta di una LW la macchina passa nello stato 1001, preleva il contenuto della memoria
all’indirizzo S e lo carica in un registro di appoggio M (M <= MEM[S]).
Passando allo stato 1010 il contenuto di M viene scritto nel registro rs (R[rs] <= M) e poi c’è il
ritorno allo stato iniziale.
Qualora l’istruzione da eseguire è un salto condizionato (BEQ) la macchina a stati finiti
transita nello stato 0010 e si effettua il controllo per determinare se il contenuto di A è
uguale al contenuto di B (if A=B).
9.9
Se il confronto dà esito positivo PC assumerà il valore di S (PC <= S), che contiene l’indirizzo di
destinazione del salto calcolato nello stato precedente e c’è il passaggio allo stato iniziale.
Altrimenti ci sarà direttamente la transizione allo stato iniziale senza eseguire altre
operazioni. Il grafo della macchina a stati finiti della figura 9.11 mostra l’aggiunta dello stato
1101 come sesta uscita dallo stato 0001.
Quando si verifica un’eccezione di tipo “Undefined Instruction” questa viene riconosciuta
nello stato 0001 e non avendo uno stato successivo dove andare quando c’è un op-code
sconosciuto, la macchina a stati finiti si porta nel nuovo stato 1101.
Le operazioni dello stato 1101 da eseguire sono: i) caricare l’indirizzo dell’istruzione che ha
causato l’eccezione in EPC (EPC <= PC-4), ii) mettere in PC l’indirizzo della routine di gestione
dell’eccezione (PC <= eh_addr) e iii) mettere nel registro causa la causa dell’eccezione
(Cause <= 10 (RI)). Infine, si torna allo stato iniziale.
IR <= MEM[PC]
PC <= PC + 4
0000
“instruction fetch”
undefined instruction
EPC Å PC- 4
PC Åeh_addr
CauseÅ10(RI)
Write-back
A<=R[rs] “decode”
B<= R[rt]
S <= PC +SX
0001
other
R-type
Memory
S <=A fun B
0100
J
S <=PC op ZX
0110
LW
SW
S <= B + SX
1000
Execute
M <= MEM[S]
1001
1101
BEQ
S <= B + SX
1011
If A = B
then PC <= S
0010
MEM[S] <= A
1100
R[rd]<= S
0101
PC <= S
0111
R[rs]<= M
1010
R[rt]
<= M
1010
Figura 9.11. Macchina a stati finiti con l’inserimento della gestione per l’eccezione “undefined instruction”.
La figura 9.12 mostra l’inserimento dello stato 1110 necessario per gestire l’eccezione di tipo
“overflow aritmetico”. Il nuovo stato può essere raggiunto dallo stato 0100 in caso di overflow
e la macchina esegue le seguenti operazioni: caricare l’indirizzo dell’istruzione che ha causato
l’eccezione in EPC (EPC <= PC-4), mettere in PC l’indirizzo della routine di gestione
dell’eccezione (PC <= eh_addr) e mettere nel registro causa la causa dell’eccezione
(Cause <= 12 (Ovf)). Infine, si torna allo stato iniziale.
9.10
IR <= MEM[PC]
PC <= PC + 4
0000
“instruction fetch”
undefined instruction
“decode”
EPC Å PC - 4
PC Å eh_addr
Write-back
Cause Å12 (Ovf)
1110
R-type
Memory
S <=A fun B
0100
EPC Å PC- 4
PC Åeh_addr
CauseÅ10(RI)
A<=R[rs]
B<= R[rt]
S <= PC +SX
other
0001
J
S <=PC op ZX
0110
LW
SW
S <= B + SX
1000
S <= B + SX
1011
overflow
Execute
M <= MEM[S]
1001
1101
BEQ
If A = B
then PC <= S
0010
MEM[S] <= A
1100
R[rd]<= S
0101
PC <= S
0111
R[rs]<= M
1010
R[rt]
<= M
1010
Figura 9.12. Macchina a stati finiti con l’inserimento della gestione per l’eccezione “overflow aritmetico”.
9.11 GESTORE DELLE INTERRUZIONI
Le seguenti istruzioni sono utilizzate per la lettura e/o la scrittura dei registri del
coprocessore 0:
• mtc0 xx, $2
→ xx <- $2
memorizza nel coprocessore 0 il contenuto del registro $2
• mfc0 $2, xx
→ $2 <- xx
memorizza nel registro $2 il contenuto del coprocessore 0
• lwc0 xx, 100($2)
→
xx <- MEM[$2 + 100]
carica nel coprocessore 0 una parola prelevata dalla memoria
• swc0 xx, 100($2)
→
MEM[$2 + 100] <- xx
salva nella memoria una parola prelevata dal coprocessore 0
Il codice dell’esempio che segue descrive un semplice gestore delle interruzioni.
Per prima cosa il gestore delle interruzioni salva il contenuto dei registri $a0 e $a1, che userà
in seguito per passare gli argomenti alla procedura di gestione.
Il gestore delle interruzioni non può memorizzare sullo stack i vecchi valori di questi registri,
come farebbe una normale procedura, perché la causa dell’interruzione potrebbe essere un
riferimento alla memoria attraverso un valore non consentito (ad esempio 0) del puntatore allo
stack. Invece di usare lo stack il gestore delle interruzioni salva il contenuto dei registri in
due locazioni di memoria, save0 e save1. Se la stessa procedura di interruzione può essere a
sua volta interrotta due locazioni non sono sufficienti, dato che la seconda interruzione
sovrascriverebbe i dati salvati durante la prima interruzione. Questo semplice gestore delle
interruzioni, però, termina l’esecuzione della procedura prima di riabilitare le interruzioni,
quindi il problema non sussiste.
9.11
.ktext 0x80000080
sw $a0, save0
# il gestore non è rientrante e non può usare
sw $a1, save1
# lo stack per salvare $a0 e $a1 non è necessario salvare $k0 e $k1
Quindi il gestore delle interruzioni sposta il contenuto dei registri Causa e EPC nei registri
della CPU. L’istruzione mfc0 $k0, $13 sposta il contenuto del registro 13 del coprocessore 0
(il registro Causa) nel registro $k0 della CPU. Si noti che il gestore delle interruzioni non deve
necessariamente salvare i registri $k0 e $k1 perché i programmi utente non usano tali
registri.
Il gestore delle interruzioni usa i valori all’interno del registro Causa per controllare se
l’eccezione è stata provocata da un’interruzione (vedere la tabella 9.3); in questo caso
l’interruzione è ignorata, mentre in caso contrario il gestore chiama la procedura.
mfc0 $k0, $13
mfc0 $k1, $14
# sposta Causa in $k0
# sposta EPC in $k1
andi $v0, $k0, 0x3C # prelevo il tipo dell’eccezione (bit da 5 a 2 del registro Causa
#memorizzato in $k0)
beq $v0, $0, fatto # ignora le interruzioni (ma non le eccezioni)
add $a0, $k0, $0 # memorizza Causa in $a0
add $a1, $k1, $0 # memorizza EPC in $a1
jal
gestione
# routine di gestione dell’eccezione, con parametri in ingresso $a0 e $a1
Prima di terminare, il gestore delle interruzioni ripristina il contenuto dei registri $a0 e $a1 e
poi esegue l’istruzione rfe (ritorno da un eccezione), che ripristina i bit della maschera delle
interruzioni e kernel/utente che erano presenti in precedenza nel registro Stato. Questo fa
sì che il processore ritorni allo stato precedente al verificarsi dell’eccezione e che si prepari
a riprendere l’esecuzione del programma. Il gestore delle interruzioni quindi restituisce il
controllo al programma saltando all’istruzione successiva a quella che ha causato l’eccezione.
fatto:
lw
lw
$a0, save0
$a1, save1
addiu
$k1, $k1, 4
rfe
jr
$k1
# non riesegue l’istruzione che ha provocato
# l’eccezione, ma salta a quella successiva
# ripristina lo stato delle interruzioni
.kdata
save0: .word0
save1: .word0
L’istruzione mfc0 (move from system control) può venire usata per copiare il registro EPC in
un registro general purpose, in maniera tale che il software del MIPS possa tornare
all’istruzione che ha causato l’eccezione attraverso un’istruzione di salto tramite registro.
9.12
Problema: se si deve copiare EPC in un altro registro per usarlo come registro di salto, come
fa il salto tramite registro a tornare al codice interrotto e ripristinare i valori originari di
tutti i registri? Due sono le opzioni:
1) Ripristinare i vecchi registri, distruggendo così l’indirizzo di ritorno contenuto in EPC
che era stato precedentemente copiato in un registro per essere usato in un salto
tramite registro
2) Ripristinare tutti i registri tranne quello con l’indirizzo di ritorno così da poter saltare,
con la conseguenza che un’eccezione comporta il cambiamento di quel registro in un
qualunque istante di esecuzione di un programma
Nessuna delle due opzioni risulta accettabile. Per ovviare a ciò, i programmatori del MIPS
hanno riservato i registri $k0 e $k1 per il sistema operativo: tali registri non sono ripristinati
durante le eccezioni. Le procedure di eccezione scrivono l’indirizzo di ritorno in uno di questi
registri e poi usano un salto tramite registro per ripristinare l’indirizzo dell’istruzione.
Nota: nei processori MIPS reali il processo di ritorno dal gestore delle interruzioni è più
complesso. L’istruzione rfe deve essere eseguita nel branch delay slot dell’istruzione jr che
restituisce il controllo al programma utente, in modo che nessuna istruzione di gestione delle
interruzioni venga eseguita con i bit della maschera delle interruzioni e kernel/utente propri
del programma utente. In più il gestore delle interruzioni non sempre può saltare all’istruzione
seguente a quella contenuta nel registro EPC; ad esempio se l’istruzione che ha provocato
l’eccezione era in un branch delay slot, l’istruzione seguente potrebbe non essere quella che si
trova nella posizione di memoria successiva.
9.11 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] Andrew S. Tanenbaum, “Structured Computer Organization”, Fourth Edition, 1999
[3] Andrew S. Tanenbaum “Modern Operating Systems”, Second Edition, 2001
[4] LSI Logic, “LR33000 Self-Embedding Processor User’s Manual”, Second Edition, 1992, Order n.
J14001.A
9.13
LEZIONE 10
Implementazione di semplice CPU MIPS
10.1 INTRODUZIONE
Un processore consta generalmente di due parti principali:
• Data Path: costituito dai dispositivi di ALU, registri e bus e dai percorsi seguiti dai
dati;
• Control Path: costituito dai percorsi dei segnali di controllo verso i dispositivi del data
path, distinti in segnali di selezione e di temporizzazione.
Scopo del processore e’ di supportare un’efficiente esecuzione del software. Con riferimento
al modello di seguito considerato, i segnali di controllo vengono utilizzati per coordinare il
funzionamento di tutti gli elementi interni al processore (e in particolare del datapath) in
modo da massimizzare le prestazioni. Per riferimento, si considerera’ il caso del processore
MIPS. Nel seguito si introdurranno brevemente gli elementi principali che costituiscono il
data-path.
10.2 REGISTER FILE
La struttura intorno alla quale è costituita l'unità di elaborazione è il Register File (RF). Il
Register File (vedi fig. 10.1) è costituito da un insieme di registri che possono essere letti o
scritti fornendo il numero (o indice) del registro a cui fare accesso. In generale l’indice del
registro e’ un numero su k bit, mentre il dato e’ espresso su m bit (nel caso del processore
MIPS qua analizzato k=5, m=32).
Register file
k
Read register
number 1
k
Read register
number 2
k
Write
register
m
Write
data
Read
data 1
m
Read
data 2
m
Write
Figura 10.1 Struttura del Register File.
10.2.1 Logica di Lettura
Poiché la lettura di un registro non ne modifica lo stato, in ingresso occorre fornire solo il
numero del registro ed in uscita si otterrà il dato contenuto in esso. Nel RF vengono utilizzate
due porte di lettura che possono essere implementate con n (n=2k) registri mediante una
coppia di multiplexer ad n vie, ciascuno di ampiezza pari a m=32 bit . (vedi fig. 10.2).
10.1
Read
register
number 1
k
m
Register 0
Register 1
Register n – 1
M
u
x
Read
m da ta 1
M
u
x
Read
m data 2
Register n
Read
re gister
number 2
k
Figura 10.2 Porte di lettura dei registri per un Register File ampio 32bit.
10.2.2 Logica di Scrittura
Per la scrittura in un registro occorrono tre ingressi che sono, il numero del registro, il dato
da scrivere ed un segnale di scrittura, detto anche “gate” di scrittura (vedi fig. 10.3). Viene
utilizzato un decodificatore per generare un segnale in grado di determinare in quale registro
scrivere. Notare che lo stato dei registri può cambiare solo in corrispondenza di un fronte in
salita del gate di scrittura, essendo i registri realizzati con flip-flop di tipo D (v. Appendice
10.A).
Write
C
0
1
k
Register
number
Register 0
D
n-to-1
decoder
C
Register 1
D
n– 1
n
C
Register n – 1
D
C
m
Register n
D
Register
data
Figura 10.3 Logica di Scrittura.
10.3 ALU (UNITÀ ARITMETICO LOGICA)
La ALU (Arithmetic & Logical Unit) è il dispositivo che esegue le operazioni aritmetiche come
la somma o sottrazione, o operazioni logiche come AND o OR. Analizzeremo innanzitutto una
ALU da 1 bit (vedi fig. 10.4), considerando solo quattro operazioni (+,-,&,|) per poi considerare
una possibile estensione ad una ALU a 32 bit.
Per realizzare la ALU elementare ad 1 bit e’ possibile utilizzare un multiplexer in uscita che
selezioni il risultato di una delle tre reti che producono AND, OR, SOMMA (la sottrazione
puo’ essere vista come un caso particolare della somma) a seconda del valore assunto dal
segnale di controllo (‘Operation’ su 2 bit).
10.2
Operation
CarryIn
2
a
0
1
2
b
CarryOut
Figura 10.4 ALU ad 1 bit.
In particolare, il sommatore avra’ due ingressi per gli operandi ed un uscita ad un bit per la
somma; inoltre ci sara’ una seconda “uscita” per generare il riporto, CarryOut, ed un terzo
ingresso, CarryIn, per tener conto del CarryOut proveniente da un eventuale sommatore
adiacente.
I valori delle uscite del sommatore sulla base degli ingressi sono presentati in tabella 10.1.
Tabella 10.1 Algebra del sommatore.
a
0
0
0
0
1
1
1
1
INGRESSI
b
0
0
1
1
0
0
1
1
CarryIn
0
1
0
1
0
1
0
1
USCITE
CarryOut
Somma
0
0
0
1
0
1
1
0
0
1
1
0
1
0
1
1
COMMENTI
0+0+0 = 00
0+0+1 = 01
0+1+0 = 01
0+1+1 = 10
1+1+0 = 01
1+0+1 = 10
1+1+0 = 10
1+1+1 = 11
Il valore dell’uscita CarryOut può essere convertito in una equazione logica:
CarryOut = (b ∗ CarryIn) + (a ∗ CarryIn) + (a ∗ b) + (a ∗ b ∗ CarryIn)
1442443
assorbimento
CarryIn
a
CarryOut
b
Figura 10.5 Generatore di CarryOut.
Il CarryOut è generato da 3 porte AND ed una porta OR (vedi fig. 10.5). Le tre porte AND
corrispondono ai termini dell'espressione sopraindicata.
10.3
Il bit somma vale 1 quando un solo ingresso vale 1, oppure quando tutti e tre gli ingressi
valgono 1. Il segnale somma corrisponde quindi all'espressione booleana:
Somma = a * b * CarryIn + a * b * CarryIn + a * b * CarryIn + a * b * CarryIn
10.3.1 ALU a 32bit
La ALU a 32 bit può essere ottenuta collegando 32 ALU da 1 bit (vedi fig 10.6). Collegando
direttamente i riporti dei sommatori ad 1 bit, si ottiene il sommatore a propagazione di
riporto (Ripple Carry Adder).
Carr yIn
a0
b0
Operation
2
Carr yIn
ALU0
Result0
CarryOut
a1
b1
Carr yIn
ALU1
Result1
CarryOut
a2
b2
a31
b31
Carr yIn
Result2
ALU2
CarryOut
Carr yIn
Result31
ALU31
Figura 10.6 ALU a 32bit.
La sottrazione corrisponde alla somma con un operando negato:
a − b = a + (−b)
Per ottenere un numero negato ( −b ) basta fare il complemento a due di tale numero, che
consiste nel complementare ciascun bit del numero e poi sommarci 1.
Per sommare 1, CarryIn viene forzato ad 1; mentre per effettuare l’operazione di
complemento si imposta Binvert ad 1.
a − b = a + (−b) = a + (b + 1) = a + b + 1
Per decidere se negare o meno un bit si puo’ usare un mux a due ingressi che scelga tra b e b
a seconda del segnale di controllo Binvert (vedi fig 10.7).
10.4
Binvert
Operation
CarryIn
a
2
0
1
0
b
Result
2
1
CarryOut
Figura 10.7 Controllo Binvert.
Oltre a questo tipicamente si aggiunge la logica per il rilevamento dell’overflow (vedi fig. 10.8).
A questo punto, si puo’ facilmente aggiungere alla ALU anche il supporto per l’istruzione: "Set
on Less Than" (slt), che genera 1 se il primo operando è minore del secondo, 0 altrimenti. Per
fare questo si puo’ effettuare una sottrazione a − b , se a − b < 0 (e quindi a < b ) il bit meno
significativo varrà 1, mentre quando a − b > 0 (cioè a > b ) esso varrà 0. Bastera’ cosi’ portare
in uscita tale bit. Vediamo come si puo’ procedere nell’implementazione.
Innanzitutto, si espande la singola ALU da un bit aggiungendo un ingresso al multiplexer che
chiameremo Less (vedi fig. 10.8). Si aggiunge poi una nuova uscita al sommatore, denominata
Set.
Binvert
Operation
CarryIn 2
a
0
1
Result
b
0
2
1
Less
3
Set
Overflow
detection
Overflow
Figura 10.8 ALU a 32bit con riporto del bit più significativo.
Nel costruire la ALU a 32 bit, le [ALU1…ALU31] hanno il bit Less collegato a 0, mentre la
ALU0 ha l’ingresso Less che proviene dall’uscita Set della ALU31 (bit più significativo) (vedi
fig. 10.9). In questo modo si genera sull’uscita complessiva Result[0..31] il valore 1 quando il bit
piu’ significativo di a − b vale 1.
10.5
Operation
2
Binvert
a0
b0
CarryIn
ALU0
Less
CarryOut
a1
b1
0
CarryIn
ALU1
Less
CarryOut
a2
b2
0
CarryIn
ALU2
Less
CarryOut
Result0
Result1
Result2
CarryIn
a31
b31
0
CarryIn
ALU31
Less
Result31
Set
Overflow
Figura 10.9 Evoluzione ALU a 32bit.
Possiamo infine aggiungere il supporto per verificare se due operandi a e b sono uguali (vedi
fig. 10.10): cio’ e’ utile nelle istruzioni tipo beq dove viene effettuato un salto se il contenuto
di due registri è uguale.
Il modo più semplice per verificare l’uguaglianza tramite la ALU consiste nel sottrarre b da a
e nel guardare se il risultato è zero, dal momento che ( a − b = 0 ) ⇒ a = b . Per ottenere un
segnale che vale 1 se Result[0..31] e’ zero basta mettere in OR tutte le uscite delle ALU e far
passare il segnale attraverso un inverter (vedi fig. 10.10).
0 = (result 31 + result 30 + ... + result1 + result 0)
10.6
Binvert
Operation
2
a0
b0
CarryIn
ALU0
Less
CarryOut
32
Result0
32
a1
b1
0
CarryIn
ALU1
Less
CarryOut
Result1
a2
b2
0
CarryIn
ALU2
Less
CarryOut
Result2
a31
b31
0
CarryIn
ALU31
Less
3 ALUcontrol
Zero
ALU ALU
result
32
Zero
Result31
Set
Overflow
Figura 10.10 Evoluzione ALU a 32bit. L’ALU così ottenuta (parte sinista) può essere schematizzata con il
blocco a destra, dove il segnale ALUControl comprende i segnali Operation e Binvert.
10.3.2 Addizionatore Carry Look Ahead (CLA)
Nella ALU appena progettata, se si risale lungo la catena delle dipendenze, il bit più
significativo è collegato a quello meno significativo ed il bit più significativo della somma deve
attendere la valutazione sequenziale di tutti i 32 sommatori ad 1 bit. Tale reazione
sequenziale a catena è troppo lenta. Perciò si utilizza la tecnica del Carry Look Ahead dove i
riporti vengono propagati in parallelo. Sappiamo che il CarryIn per il bit 2 (ALU1) del
sommatore è esattamente il CarryOut del bit 1.
Perciò:
CarryIn2 = (b1 ∗ CarryIn1 ) + (a1 ∗ CarryIn1 ) + (a1 ∗ b1 )
ed analogamente:
CarryIn1 = (b0 ∗ CarryIn0 ) + (a0 ∗ CarryIn0 ) + (a0 ∗ b0 )
Ponendo:
c1 , c2 ,..., cn = CarryIn1 , CarryIn2 ,..., CarryInn
si può scrivere:
ci +1 = (bi ∗ ci ) + (ai ∗ ci ) + (ai ∗ bi ) = (ai + bi )ci + (ai ∗ bi )
Osserviamo che un valore 1 di questo può essere dovuto a due fatti mutuamente esclusivi:
1. Il riporto si genera localmente allo stadio i-esimo perché i due bit ai e bi sono
entrambi 1; questo è espresso dal termine prodotto ai ∗ bi che viene detto termine di
generazione; quando ciò accade l’altro termine (ai + bi ) ∗ ci è ininfluente.
2. Il riporto si genera nello stadio precedente (i-1)-esimo) e lo stadio i-esimo
semplicemente lo propaga al successivo, essendo ai o bi (ma non entrambi) 1: di ciò è
10.7
responsabile il termine (ai + bi ) che prende il nome di termine di propagazione; quando
il termine di propagazione vale 1, il termine di generazione è nullo.
Detti g e p i due termini sopra introdotti, possiamo scrivere il riporto ci +1 nella forma:
ci +1 = gi + pi ∗ ci
Iterando questa equazione per i = 0,1,..., n − 1 otteniamo (nota c0=CarryIn0, ...,cn-1=CarryInn-1
mentre Cn=CarryOutn-1):
c1 = g0 + p0c0
c2 = g1 + p1c1
c3 = g 2 + p2c2
...
cn −1 = g n − 2 + pn − 2cn − 2
cn = g n −1 + pn −1cn −1
Infine sostituendo c1 in c2, c2 in c3, e cosi’ via l’espressione di ci-1 data dall’equazione
precedente nell’equazione i-esima di ci, si arriva alle seguenti equazioni:
c1 = g 0 + p0 c0
c2 = g1 + p1 g 0
c3 = g 2 + p2 g1 + p2 p1 g 0
...
ci = g i −1 + pi −1 g i −2 + pi −1 pi −2 g i −3 + ... + pi −1 pi −2 ... p2 g1 + pi −1 pi −2 ... p2 p1 g 0
...
cn = g n−1 + pn−1 g n−2 + pn−1 pn−2 g n−3 + ... + pn−1 pn−2 ... p 2 g1 + p n−1 pn−2 ... p2 p1 g 0
le quali mostrano che il riporto
ci in uscita allo stadio i − 1 esimo è dovuto al termine di
generazione di quello stadio, oppure al termine di propagazione di quello stadio ed al termine
di generazione dello stadio (i − 2) esimo, oppure al termine di propagazione di quello stadio, al
termine di propagazione dello stadio (i − 2) esimo ed al termine di generazione dello stadio
(i − 3) esimo e così via.
Per ciascuna equazione possiamo costruire una rete combinatoria a due livelli di logica che
riceve in ingresso il termine di generazione e quello di propagazione prodotti nello stadio
dell’addizionatore ad essa relativo e genera il riporto per lo stadio successivo.
L’insieme di tali reti costituisce un modulo detto Carry Look-Ahead Generator (CLAG).
I vari stadi dell’addizionatore con CLAG possono operare in parallelo, producendo il risultato
con un ritardo costante pari a Δ s + 2Δ g dove Δ s è il tempo di propagazione dall’ingresso
all’uscita della somma e Δ g è il ritardo di una porta.
Questa tecnica può essere applicata fino a valori di n non superiori a 5 o 6, infatti al crescere
di n il numero di porte e dei segnali di ingresso ad esse associati cresce a livelli inaccettabili.
Per addizionatori di lunghezza maggiore è necessario trovare un compromesso tra complessità
realizzativa e velocità di risposta.
Una soluzione consiste nel dividere gli operandi in k gruppi di q bit, in modo che sia n = k ∗ q e
q ≤ 6 e nel propagare i riporti con tecnica CLA sia all’interno di ogni gruppo che tra i gruppi.
Occorre disporre di almeno 2 livelli di CLAG: al primo livello si trovano i generatori di riporti
anticipati che lavorano sui singoli gruppi; al secondo livello quelli che lavorano tra i gruppi
ricevendo i termini di generazione e di propagazione dai generatori del primo livello.
10.8
10.3 PROGETTAZIONE DEL DATAPATH
Analizziamo adesso la struttura di un’unità di elaborazione dati (datapath) elementare, a cui
aggiungeremo in seguito la relativa parte di controllo, fondendo in un’unica rete le unità di
elaborazione presentate in precedenza.
Questa semplice implementazione coprirà le istruzioni di load word (lw), store word (sw),
Branch on Equal (beq) e le istruzioni logico-aritmetiche add, sub, and, or e Set on Less Than
(slt).
10.3.1 Prelievo delle istruzioni
Il prelievo delle istruzioni (fetch) ha inizio caricando l’indirizzo memorizzato nel Program
Counter (PC – Figura 10.11a). Il PC è un registro a 32 bit che viene scritto alla fine di ogni ciclo
di clock.
L’indirizzo prelevato dal PC è quindi utilizzato per caricare l’istruzione vera e propria
dall’Instruction Memory (Figura 10.11b), in cui sono memorizzate tutte le istruzioni del
programma. L’Instruction Memory deve perciò essere in grado di fornire in uscita le istruzioni
a fronte di un indirizzo in ingresso.
Per prepararsi ad eseguire l’istruzione successiva, occorre inoltre incrementare il PC in modo
che punti alla prossima istruzione: è quindi essenziale un addizionatore (Add – Fig.11.10c) per
incrementarlo. Tale elemento combinatorio è generato a partire da un’ALU in cui uno degli
addendi è fissato a 4.
Instruction
address
PC
Sum
Instruction
memory
Add
Instruction
Figura 10.11 a) Instruction Memory.
b) Program Counter.
c) Addizionatore.
In figura 10.12 è mostrata la rete in grado di svolgere le suddette operazioni.
Add
4
PC
Instruction
address
Instruction
memory
Figura 10.12 Rete utilizzata per prelevare istruzioni ed incrementare il PC.
10.9
10.3.2 Decodifica
Proseguiamo analizzando in maniera approfondita ogni singola tipologia d’istruzione che il
nostro Datapath sarà in grado di gestire.
10.3.2.1 Supporto per le istruzioni in formato “R-Type”
Le prime istruzioni che esamineremo sono quelle che operano in formato R (R-Type) come add,
sub, and, or, e slt.
Tutte le istruzioni di questo genere seguono i seguenti passi:
1. leggono 2 registri;
2. eseguono un’operazione con l’ALU sui contenuti dei registri;
3. scrivono il risultato in un terzo registro.
I 32 registri del processore costituiscono il Register File (Figura 10.13).
Per compiere una lettura dobbiamo fornire un input al Register File che specifichi il numero
del registro da leggere (su Read-register-1 o Read-Register-2) ed prelevare dall’output
(Read-data-1 o Read-data-2 rispettivamente) il valore letto da tale registro, mentre per
un’operazione di scrittura ci servono due input al Register File: uno per specificare il numero
del registro da scrivere (Write-register) e l’altro per il dato da memorizzare (Write-data).
Gli ingressi che specificano il numero dei registri sono a 5 bit (25= 32), mentre i bus di input e
di output di dato sono a 32 bit ciascuno.
Per poter operare sui valori letti dai registri, è necessaria un’ALU con due ingressi ed un
output a 32 bit.
5
5
5
32
Read
register 1
32
Read
data 1
Read
register 2
Register
file
3
ALU Control
Zero
32
Write
register
Write
data
32
Data
Read
data 2
32
ALU
ALU
result
32
RegWrite
Figura 10.13 Elementi necessari per implementere istruzione in formato R.
10.3.2.2 Supporto per le istruzioni d’accesso alla memoria
Soffermiamoci adesso sulle istruzioni di load e di store, quali ad esempio:
lw
$t1, offset_value($t2)
sw
$t1, offset_value($t2)
Queste istruzioni calcolano un indirizzo di memoria sommando al registro base (in questo caso
$t2) il valore dell’offset “offset_value” (rappresentato su 16 bit con segno).
Poiché entrambe le istruzioni hanno bisogno di accedere ai registri (per un’operazione di
lettura nel caso dello store, per una di scrittura nel caso del load), per eseguirle sono
essenziali sia il Register File che l’ALU.
Occorre inoltre introdurre due nuove unità (Figura 10.14):
1. Un’unità, chiamata Sign-extend, che “estende” (con il segno corretto) l’offset da 16 a
32 bit per poterlo utilizzare nell’ALU;
2. Una memoria (Data memory) in cui scrivere durante operazioni di store, dotata perciò
di segnali di controllo sia per la scrittura che per la lettura.
10.10
MemWrite
Address
32
Read
data
Sign
extend
32
Write
data
16
32
Data
memory
32
RegWrite
Figura 10.14 Unità necessarie per implementare istruzioni di load e store.
La rete in figura 10.15 mostra come vengono connessi questi elementi nel costruire la rete per
le istruzioni lw e sw.
ALU Control
Istruzione
3
Read
register 1
Read
Read
data 1
register 2
Read
Write
data 2
register
Register
Write
file
data
ALU
ALU
result
Address
Read
data
Write
data
RegWrite
16
MemWrite
Sign
extend
32
Data
memory
MemRead
Figura 10.15 Datapath per istruzioni di load e store.
10.3.2.3 Supporto per le istruzioni di “Branch”
Analizziamo infine il supporto per l’istruzione di Branch on Equal.
L’istruzione beq ha tre operatori: due registri che sono confrontati per verificarne
l’uguaglianza ed un offset a 16 bit.
Per calcolare l’indirizzo di destinazione del salto (branch target) dobbiamo sommare al valore
del PC il nostro offset (esteso a 32 bit con segno). Rammentiamo che l’offset è riferito a
word: perciò è necessario traslarlo di 2 bit a sinistra (effettuando in pratica una
moltiplicazione per 4).
Oltre a calcolare il branch target, la rete dovrà essere capace di determinare se la prossima
istruzione da eseguire sia quella nella posizione di memoria successiva (PC+4) o quella
all’indirizzo specificato dal salto.
Nel caso in cui l’uguaglianza risulti verificata il branch target diventa il nuovo PC (in questo
caso si dice branch taken); altrimenti il PC incrementato và a rimpiazzare il PC attuale (branch
not taken) come per ogni altra istruzione.
I valori da confrontare sono caricati tramite il Register File. L’operazione di confronto vera e
propria avviene tramite l’ALU: poiché questa fornisce un segnale di output (Zero) che indica
se il risultato è pari a 0, effettuando una sottrazione tra i due operandi l’uguaglianza risulterà
verificata solo se il segnale di output Zero è 1.
In figura 10.16 è mostrato il datapath per tale classe d’istruzioni.
10.11
PC+4
Add
sum
Shift
left 2
Istruzione
3
Read
register 1
Read
register 2
Write
register
Write
data
Read
data 1
Destinazione del
branch
ALU
Control
Zero
Register
file
Read
data 2
1
M
u
x
0
Alla logica di
controllo del branch
ALU
RegWrite
16
Sign
extend
32
Figura 10.16 Datapath per istruzioni di Branch.
10.3.3 Creazione del Datapath
“Assembliamo” adesso i singoli componenti per creare un unico datapath in grado di eseguire
tutte le classi d’istruzioni viste in precedenza (lw, sw, beq, add, sub, and, or e slt).
Il nostro datapath elementare è in grado di eseguire tutte le istruzioni in un solo ciclo di
clock (implementazione single-cycle). Ciò comporta che nessuna unità possa essere utilizzata
più di una volta per ogni istruzione e che qualsiasi elemento di cui si ha bisogno più volte deve
essere duplicato. Sono perciò necessarie due unità di memoria esterne separate: l’Instruction
Memory ed il Data Memory.
Fortunatamente molti altri elementi possono essere invece condivisi tra differenti tipi
d’istruzioni: sono però necessari collegamenti multipli agli input di tali unità. Per fare ciò
utilizziamo dei multiplexer (vedi Figura 10.17) controllati tramite segnali che specificheremo
in seguito.
10.12
1
M
u
x
Add
4
0
Add
Shift
Left 2
Read
register 1
Instruction
address
PC
Instruction
memory
Instruction
Read
data 1
Read
register 2
0
M
u
x
Read
data 2
Write
register
Write
data
ALU
Register
file
16
Zero
ALU
result
0
M
u
x
Address
Read
data
1
Write
data
32
Data
memory
Sign
extend
0
M
u
x
1
Figura 10.17 Datapath elementare.
10.4 RETE DI CONTROLLO
Avendo completato il nostro Datapath elementare, aggiungiamo adesso la rete di controllo:
essa deve essere in grado di elaborare i propri ingressi e di generare un segnale di scrittura
per il Register File e la Data Memory, un segnale di controllo per la selezione di ogni
multiplexer (4) ed il segnale a 3 bit di controllo dell’ALU, per un totale di 2+4+3 segnali di
controllo.
Poiché il controllo della ALU è per molti versi particolare, conviene suddividere la rete di
controllo in due sottoreti: la rete “Main-Control” e la rete “ALU-Control”.
0
M
u
x
1
Add
Shift
left 2
Add
RegDst
4
4
Branch
MemRead
Mem toReg
Instruction [31:26]
Control
ALUOp
MemWrite
ALUSrc
RegWrite
Instruction [25:21]
PC
Instruction
address
Instruction
memory
Instruction
[31:0]
Read
register 1
Instruction [20:16]
0
Instruction [15:11]
Read
data 1
Read
register 2
M
u
x
1
Write
data
ALU
Read
data 2
Write
register
0
Zero
ALU
result
Address
M
u
x
1
Register
file
Read
data
Write
data
Instruction [15:0]
16
Sign
extend
32
3
ALU
Control
Instruction [5:0]
Figura 10.18 Datapath con la relativa rete di controllo.
10.13
Data
memory
0
M
u
x
1
10.4.1 Rete ALU-Control
Sappiamo che l’ALU ha 3 ingressi di controllo, ma soltanto 6 delle 8 possibili combinazioni sono
utilizzate. Nella tabella 10.2 sono elencate tali combinazioni.
Tabella 10.2 Operazioni eseguibili dalla ALU
Segnale di
controllo ALU
Operazione
000
001
010
110
111
AND
OR
add
sub
slt
Secondo il tipo d’istruzione, l’ALU dovrà eseguire una delle 5 funzioni.
Possiamo generare il segnale di controllo a 3 bit mediante un’unità di controllo con due
ingressi: un gruppo di fili derivato dal campo funct a 6 bit dell’istruzione e un’altro derivato
dal segnale ALUOp a 2 bit (generato a sua volta dalla rete Main-Control).
ALUOp indica se l’operazione da eseguire è una somma (00) per istruzioni di lw e sw, una
sottrazione (01) per beq oppure l’operazione codificata nel campo funct (10) per istruzioni RType. Nella tabella 10.3 è riassunto quanto appena detto.
Tabella 10.3 Generazione dei segnali di controllo in uscita alla rete ALU-control.
Istruzione
ALUOp
lw
sw
beq
00
00
01
10
10
10
10
10
R-Type
R-Type
R-Type
R-Type
R-Type
Operazione
dell’istruzione
load word
store word
branch equal
add
sub
AND
OR
slt
Campo
Operazione ALU
desiderata
add
add
sub
add
sub
AND
OR
slt
funct
xxxxxx
xxxxxx
xxxxxx
100000
100010
100100
100101
101010
Segnale di
controllo ALU
010
010
110
010
110
000
001
111
Poiché soltanto una piccola parte dei 64 (26) possibili valori del campo funct sono significativi
e dato che il campo funct è utilizzato solo quando i bit ALUOp sono 10, possiamo utilizzare un
semplice blocco logico che riconosca il sottoinsieme dei valori ammissibili.
Creiamo quindi la tabella di verità per tale rete (Tab.10.4).
Tabella 10.4 Tabella di verità per la rete ALU-Control.
Campo funct
ALUOp
ALUOp1
0
x
1
1
1
1
1
ALUOp2
0
1
x
x
x
x
x
F5
x
x
x
x
x
x
x
F4
x
x
x
x
x
x
x
F3
x
x
0
0
0
0
1
F2
x
x
0
0
1
1
0
F1
x
x
0
1
0
0
1
Operazione
F0
x
x
0
0
0
1
0
010
110
010
110
000
001
111
Per mantenere la tabella compatta consideriamo solo le combinazioni significative di input ed
includiamo dei don’t care.
10.14
E’ quindi possibile implementare il comportamento descritto dalla tabella 10.4 con il seguente
circuito logico (Figura 10.19):
ALUOp
ALU control block
ALUOp0
ALUOp1
Operation2
F3
F (5– 0)
F2
Operation1
Operation
F1
Operation0
F0
Figura 10.19 Rete ALU Control.
10.4.2 Rete Main-Control
Prima di procedere con l’implementazione vera e propria della rete, consideriamo in dettaglio il
formato delle classi di funzione che il nostro datapath può gestire:
• Il campo op (anche chiamato opcode) è sempre contenuto nei bit 31:26. Ci riferiremo a
questo campo con Instr[31:26].
• I registri che devono essere letti sono sempre specificati nei campi rs (bit 25:21) e rt
(bit 20:16). Questo per istruzioni R-Type, beq e sw.
• Per istruzioni sw e lw, il registro di base è sempre indicato nei bit 25:21 (rs).
• L’offset a 16 bit per istruzioni beq, lw e sw è sempre nelle posizioni 15:0.
• Per il registro di destinazione esistono due possibilità: per lw è in posizione 20:16 (rt),
mentre per istruzioni R-Type è in 15:11 (rd). Dobbiamo perciò aggiungere un
multiplexer sull’ingresso Write register del Register file per selezionare il campo
corretto.
Con queste informazioni è possibile aggiungere al nostro datapath le etichette che riportano i
numeri dei bit dell’istruzione ed un ulteriore multiplexer sull’ingresso Write register (vedi
Figura 10.20).
10.15
0
M
u
x
1
Add
Shift
left 2
Add
PCSrc
4
RegWrite
Instruction [25:21]
PC
Instruction
address
Instruction
memor y
Instruction
[31:0]
Read
register 1
Instruction [20:16]
0
Instruction [15:11]
Instruction [15:0]
M
u
x
1
RegDst
Read
data 1
Read
register 2
Write
register
Write
data
Read
data 2
Register
file
16
Sign
extend
MemWrite
ALUSrc
0
ALU
Zero
ALU
result
M
u
x
1
Read
data
Write
data
32
ALU
Control
MemtoReg
Address
3
Data
memor y
0
M
u
x
1
MemRead
Instruction [5:0]
ALUOp
Figura 10.20 Datapath completo di tutti i multiplexer necessari
e con l’identificazione di tutti i segnali di controllo.
Avendo già definito il funzionamento di ALUOp, resta da determinare come operano i restanti
segnali di controllo (vedi Figura 10.5).
Tabella 10.5 Effetti dei 7 segnali di controllo generati dalla rete Main-Control.
Nome del segnale
Effetti quando negato
Effetti quando affermato
RegDst
Il numero del registro di destinazione per Il numero del registro di destinazione per il
il Write register proviene dal campo rt (bit Write register proviene dal campo rd (bit
20:16).
15:11).
RegWrite
Nessuno.
Il valore presente sull'ingresso Write data
viene scritto nel registro specificato da
Write register.
ALUSrc
Il 2° operando della ALU è il 2° output del Il 2° operando della ALU è costituito dai 16
Register file (Read data 2).
bit meno significativi, estesi a 32 bit con
segno, dell'istruzione.
PCSrc
Il valore del PC è sostituito dal valore in
Il valore del PC è sostituito dall'uscita
uscita dall'addizionatore che calcola PC+4. dell'addizionatore che calcola il branch
target.
MemRead
Nessuno.
Il contenuto della cella del Data memory,
designata dall'ingresso Address, viene posto
in Read data.
MemWrite
Nessuno.
Il contenuto della cella del Data memory,
designata dall'ingresso Address, è sostituito
dal valore presente sull'ingresso Write data.
MemtoReg
Il valore inviato all'ingresso Write data del Il valore inviato all'ingresso Write data del
Register file proviene dalla ALU.
Register file proviene dalla Data memory.
10.16
L’unità Main-Control (vedi Figura 10.21) genera tutti i segnali di controllo, tranne uno, sulla
base dei bit Instr[31:26]. L’eccezione è il segnale PCSrc, che deve essere asserito se
l’istruzione è beq e se l’uscita Zero dell’ALU è pari a 1: dobbiamo perciò compiere
un’operazione di AND tra il segnale Branch proveniente dall’unità Control e l’uscita Zero
dell’ALU.
La tabella 10.6 definisce il valore dei segnali di controllo per ognuna delle classi di funzione
MIPS esaminate.
Tabella 10.6 Valore dei 7 segnali di controllo per le varie tipologie di istruzione.
Istruzione
Reg
Dst
ALU
Src
Memto
Reg
Reg
Write
Mem
Read
Mem
Write
Branch
ALUOp
1
ALUOp
2
R-Type
1
0
0
1
0
0
0
1
0
lw
0
1
1
1
1
sw
x
1
x
0
0
0
0
0
0
1
0
0
0
beq
x
0
x
0
0
0
1
0
1
10.4.2.1 Funzionamento del Datapath
Prima di procedere alla sintesi dell’unità logico-combinatoria Control, esaminiamo l’uso che
ciascuna istruzione fa del datapath.
•
Istruzioni “R-Type”
Analizziamo per esempio cosa accade per l’istruzione
add $t1, $t2, $t3.
Anche se tutte le operazioni avvengono in un unico ciclo di clock, risulta di più facile
comprensione pensare che l’istruzione sia eseguita attraverso una serie di passi:
1. viene prelevata un’istruzione dall’Instruction memory ed incrementato il PC
(vedi Figura 10.21 dove le unità attive ed i segnali di controllo affermati sono
evidenziati in rosso);
2. sono letti dal Register File due registri, nel nostro caso $t2 e $t3, mentre le
unità Main-Control calcola tutti i valori da attribuire alle linee di controllo
(anche quelli non necessari per questo tipo d’istruzione) (Figura 10.21 in verde);
3. la ALU elabora i dati letti dal Register file grazie ai segnali di controllo
provenienti dalla rete ALU-Control, che li genera utilizzando il codice funzione
(il campo funct dell’istruzione, bit 5:0) (Figura 10.21 in blu);
4. il risultato calcolato dall’ALU viene scritto nel Register file utilizzando i bit
15:11 dell’istruzione per selezionare il registro di destinazione ($t1) (vedi Figura
10.21 in viola).
10.17
0
M
u
x
1
Add
Shift
left 2
Add
Re gD st
Bran ch
4
PCS rc
Mem Re a d
Memto R eg
Instructi o n [31: 26]
Control
ALUO p
MemWr ite
AL USrc
Re gWrit e
Instructi o n [25: 21]
PC
Instructi o n
add res s
Instruction
me mo ry
Instructi o n
[31:0]
Re ad
regi ster 1
Instructi o n [20: 16]
0
Instructi on [1 5:1 1]
M
u
x
1
Re ad
data 2
Write
regi ster
Write
data
Sign
ext end
ALU
Zero
0
AL U
resu lt
Addr ess
M
U
X
1
Regis te r
file
16
Instructi o n [15: 0]
Re ad
data 1
Re ad
regi ster 2
Re ad
data
Write
Data
32
Dat a
me mo ry
1
M
u
x
0
ALU
Control
Instructi o n [5:0]
Figura 10.21 CPU completa.
•
Istruzioni di “Load” e “Store”
I passi da compiere in caso di istruzioni di load e store, come ad esempio
lw $t1, offset($t2)
sono i seguenti cinque:
1. è prelevata un’istruzione dall’Instruction memory ed incrementato il PC;
2. viene letto il valore di un registro ($t2) dal Register file;
3. la ALU somma il valore letto dal Register file ai 16 bit meno significativi
dell’istruzione (offset), esteso da 16 a 32 bit con segno dall’unità Sign extend;
4. la somma calcolata dalla ALU è utilizzata come indirizzo per la Data memory;
5. il dato proveniente dalla Data memory è scritto nel Register file: il registro di
destinazione è calcolato a partire dai bit 20:16 dell’istruzione.
•
Istruzioni di “Branch”
Esaminiamo infine i passi da compiere in caso di istruzioni di Branch, come ad esempio
beq $t1, $t2, offset
1. viene prelevata un’istruzione dall’Instruction memory ed incrementato il PC;
2. vengono letti dal Register file due registri ($t1 e $t2);
3. la ALU calcola la differenza fra i valori dei dati letti dal Register file, mentre il
valore di PC+4 viene sommato ai 16 bit meno significativi dell’istruzione (offset),
dopo averli estesi con segno a 32 bit e scalati a sinistra di due posizioni,
ottenendo così l’indirizzo di destinazione del salto;
4. il valore Zero calcolato dall’ALU è utilizzato per decidere da quale
addizionatore prelevare il nuovo valore da memorizzare nel PC.
10.4.2.2 Completamento dell’unità Main-Control
Ora che abbiamo visto come sono eseguite le istruzioni nei diversi passi, procediamo infine
all’implementazione dell’unità Main-Control.
10.18
Sappiamo che riceve in ingresso i 6 bit Op[5:0] (corrispondenti a Instr[31:26] e che le sue
uscite sono i 9 segnali di controllo.
Possiamo quindi creare la tabella di verità 10.7:
Tabella 10.7 Tabella di verità per l’unità Control.
Nome
Segnale
R-Type
lw
sw
beq
Op5
0
1
1
0
Op4
0
0
0
0
Op3
Op2
0
0
0
0
1
0
0
1
Inputs
Outputs
Op1
0
1
1
0
Op0
RegDst
0
1
1
0
1
x
0
x
0
AluSrc
0
1
1
MemtoReg
0
1
x
x
RegWrite
1
1
0
0
MemRead
0
1
0
0
MemWrite
0
0
1
0
Branch
ALUOp1
0
1
0
0
0
0
1
0
ALUOp2
0
0
0
1
Una possibile sintesi della tabella di verità 10.7 è rappresentata dal circuito in Figura 10.22.
Inputs
Op5
Op4
Op3
Op2
Op1
Op0
Outputs
R-format
Iw
sw
beq
RegDst
ALUSrc
MemtoReg
RegWrite
MemRead
MemWrite
Branch
ALUOp1
ALUOp
Figura 10.22 Unità Main-Control.
10.4.3 Perché non si utilizzano implementazioni a singolo ciclo (syngle-cycle)
Sebbene il progetto della CPU a singolo ciclo appena presentato funzioni correttamente, non è
utilizzato nelle implementazioni moderne a causa della sua scarsa efficienza.
10.19
Per comprendere ciò, basta osservare che il periodo di clock deve necessariamente avere la
stessa durata in tutte le istruzioni previste. Il periodo di clock è determinato dal “cammino”
più lungo nel sistema, che è dovuto quasi certamente ad un’istruzione di load, poiché utilizza 5
unità in serie: l’Instruction Memory, il Register File, l’ALU, la Data Memory e nuovamente il
Register File. Sebbene cosi’ facendo risulti un CPI sia pari ad 1, le performance complessive di
un implementazione single-cycle non sono molto buone poiché per molte classi di istruzioni
sarebbe sufficiente un periodo di clock più breve.
Inoltre, non è possibile sfruttare tecniche implementative che riducano il ritardo dei casi più
comuni ma solo del caso peggiore, visto che la durata del ciclo di clock deve essere pari al
ritardo massimo tra quelli generati dalle diverse istruzioni: viene violato perciò il principio
fondamentale di progetto di rendere più veloci i casi più frequenti.
Infine, poiché ciascuna unità non può essere utilizzata per più di una volta per ciclo di clock,
alcune unità dovranno essere necessariamente duplicate, contribuendo così ad un maggior
costo dell’implementazione.
Una progettazione a singolo ciclo è dunque inefficiente sia dal punto di vista delle
performance che da quello dei costi.
10.5 IMPLEMENTAZIONE MULTICICLO (MULTI-CYCLE)
Un implementazione di tipo multiciclo permette di realizzare un sistema composto da meno
hardware rispetto a quello single-cycle poichè ogni operazione verrà eseguita in più passi: ogni
passo richiederà un ciclo di clock completo. Tale implementazione, oltre ad un risparmio di
risorse hardware, porta con se la possibilità di avere istruzioni con diverso numero di cicli di
clock.
Le principali caratteristiche della versione multiciclo sono:
- una sola unità di memoria per istruzioni e dati;
- una sola ALU anziché una ALU e due sommatori;
- l’aggiunta di uno o più registri per ciascuna unità funzionale allo scopo di memorizzarne
l’uscita fino a quando questa sarà riutilizzata in un ciclo di clock successivo.
I dati utilizzati da istruzioni successive sono memorizzati nel RF, nel PC o nella memoria,
mentre i dati utilizzati nella stessa istruzione, ma in un ciclo di clock successivo, sono
memorizzati in uno dei registri aggiuntivi.
Nel progetto multiclico, il ciclo di clock conterrà un‘operazione di accesso in memoria,
un’operazione di accesso al RF o un’operazione della ALU: i dati prodotti da tali operazioni
verranno salvati in un registro temporaneo per poi essere utilizzati nei cicli di clock
successivi.
Il registro contente l’istruzione corrente (IR) ed il registro dei dati di memoria (MDR)
utilizzano il bus verso la memoria durante le operazioni di lettura di un istruzione e durante
quella di accesso a un dato rispettivamente. I registri A e B memorizzano i valori letti dal RF.
Il registro ALUOut memorizza l’uscita della ALU.
Avendo sostituito le tre ALU dell’unità di elaborazione a singolo ciclo con una sola ALU,
quest’ultima dovrà ricevere tutti gli ingressi che erano diretti alle diverse unità. Dovremo
quindi aggiungere un nuovo multiplexer sul primo ingresso della ALU che seleziona il registro A
oppure PC ed inoltre sostituire il multiplexer a due vie presente sul secondo ingresso della
ALU con uno a quattro vie in cui i due ingressi aggiuntivi sono la costante 4 (che incrementa
PC) ed il valore del campo offset esteso è scalato per il calcolo dell’indirizzo di salto.
Otterremo così un elaboratore con due sommatori ed un unità di memoria in meno (vedi fig.
10.23).
10.20
IorD
PC
0
M
u
x
1
MemRead MemWrite
RegDst
RegWrite
Memory
MemData
Instruction
[20– 16]
Instruction
[15– 0]
Instruction
register
Instruction
[15– 0]
Memory
data
register
ALUSrcA
0
M
u
x
1
Read
register 1
Instruction
[25– 21]
Address
Write
data
IRWrite
Read
Read
data 1
register 2
Registers
Write
Read
register
data 2
0
M
Instruction u
x
[15– 11]
1
0
M
u
x
1
A
B
4
Write
data
16
Sign
extend
32
Shift
left 2
Zero
ALU ALU
result
ALUOut
0
1 M
u
2 x
3
ALU
control
Instruction [5– 0]
MemtoReg
ALUSrcB ALUOp
Figura 10.23 Implementazione multiciclo.
Il PC, i registri ed il registro IR necessitano di segnali di controllo per la scrittura, mentre la
memoria richiede anche segnali di controllo per la lettura; anche il multiplexer a quattro vie
richiederà un segnale di controllo supplementare. Abbiamo così bisogno di sviluppare
ulteriormente la parte di controllo con tali segnali di controllo aggiuntivi.
10.5.1 Definizione dell’unità di controllo nel caso multiciclo
Nell’unità di controllo dell’implementazione multiciclo si ha maggiore complessita’ perche’
ciascuna istruzione viene eseguita in più passi: tale unita’ deve infatti specificare sia i segnali
da asserire al passo attuale, sia determinare quale sia il passo successivo della sequenza.
Si analizzano due tecniche di specifica dell’unità di controllo, una basata sulle macchine a
stati finiti, l’altra sulla microprogrammazione.
10.5.1.1 Unita’ di Controllo come Macchina a Stati Finiti
Una macchina a stati finiti è caratterizzata da un insieme di stati e da due funzioni dette
funzione di stato futuro e funzione di uscita.
L’insieme di stati corrisponde, nel nostro caso, a tutte le possibili situazioni in cui il
processore multiciclo si puo’ trovare durante l’elaborazione delle istruzioni (questo verra’
approfondito nei successivi paragrafi).
La funzione di stato futuro è una funzione combinatoria che, dato il valore dell’ingresso e
dello stato presenti, determina il prossimo stato assunto dal sistema.
La funzione di uscita produce invece le uscite a partire dallo stato presente e dagli ingressi.
Ciascuno stato specifica l’insieme delle uscite che vengono affermate quando la macchina si
trova in esso (si assume che le restanti uscite, non esplictiamente menzionate siano nonaffermate). In alcuni casi e’ importante che i valori delle uscite siano effettivamente 0 o 1,
evitando situazioni di “non-specificato” che potrebbero generare comportamenti indesiderati
nel processore: ad esempio,i segnali di controllo dei multiplexer selezionano un dato ingresso
se valgono 0 e un altro ingresso se valgono 1: non si deve lasciare il segnale di controllo “nonspecificato”.
Sicuramente, sono quindi presenti almeno cinque passi di esecuzione, corrispondenti a ciascuno
dei 5 cicli di clock necessari per eseguire l’istruzione piu’ lunga (lw).
10.21
I primi due passi di esecuzione sono comuni a tutte le istruzioni, mentre quelli da 3 a 5
dipendono direttamente dal codice operativo: alla fine dell’esecuzione dell’ultimo passo, la
macchina torna allo stato iniziale per prelevare la nuova istruzione. Inoltre sara’ presente uno
stato iniziale (stato 0).
Con riferimento alla figura 10.24, nello stato 0 si asseriscono i segnali MemRead e IRwrite per
leggere un’istruzione dalla memoria e scriverla in IR e si pone IorD (che determina se
l’indirizzo di accesso alla memoria proviene dal PC oppure dalla ALU) a 0 per selezionare PC
come sorgente dell’indirizzo.
I valori dei segnali ALUSrcA (0), ALUSrcB (01), ALUop (00), PCWrite e PCSource (00) fanno
si che PC+4 venga calcolato e memorizzato in PC.
Nello stato 1 si decodifica l’istruzione, si prelevano gli operandi dai registri (ponendoli nei
registri di appoggio A e B) e si calcola l’indirizzo di destinazione del salto (branch target
address) forzando ALUSrcB ad 11, ALUSrcA a 0 ed ALUop a 00: il risultato viene
memorizzato nel registro ALUout (che viene scritto ad ogni ciclo di clock).
Adesso ci troviamo di fronte a quattro possibili operazioni:
• accesso alla memoria (stati 2, 3, 4, 5)
• tipo R (stati 6 e 7)
• salto condizionato -ovvero diramazione- (stato 8)
• salto incondizionato –ovvero jump- (stato 9).
10.5.1.1 Accesso alla memoria
Nello stato 2 viene calcolato l’indirizzo di memoria effettivo, forzando ALUSrcA ad 1,
ALUSrcB a 10 e ALUop a 00 affinchè sul primo ingresso della ALU vada il registro A (che
conterra’ l’indirizzo base) e sul secondo ingresso si ottenga -tramite estensione del segno- il
valore dell’offset indicato nell’istruzione: il risultato viene scritto nel registro ALUout.
Dopo il calcolo dell’indirizzo di memoria, quest’ultima deve essere letta o scritta: se il codice
operativo dell’istruzione è lw allora lo stato 3 effettua la lettura (MemRead attivo); se invece
è sw lo stato 5 effettua la scrittura in memoria (MemWrite attivo).
Sia nello stato 3 che 5 il segnale IorD è posto ad 1 per forzare la provenienza dell’indirizzo di
memoria dal registro ALUout.
Dopo l’esecuzione della scrittura, l’istruzione sw è completa e lo stato futuro è lo stato 0.
Se l’istruzione è invece una load è necessario lo stato 4 per scrivere il risultato proveniente
dalla memoria nel RF (RegWrite attivo, MemtoReg=1, RegDst=0).
10.5.1.2 Istruzione di tipo R
Richiede due stati corrispondenti ai passi di Esecuzione (stato 6) e completamento
dell’istruzione di tipo R (stato 7).
Il primo passo (stato 6) pone ALUSrcA=1 e pone ALUSrcB=00, facendo si’ che i due registri
letti dal RF vengano utilizzati come ingressi della ALU: ALUop viene forzato ad 10 in modo da
istruire l’unità di controllo della ALU ad utilizzare il campo funzione nella determinazione dei
segnali di controllo della ALU.
Nello stato 7 viene attivato il segnale RegWrite per forzare una scrittura nel Register File,
RegDst viene posto a 1 in modo da utilizzare il campo rd come numero di registro
destinazione, mentre MemtoReg=0, in modo da selezionare il registro ALUout come sorgente
del valore che verrà scritto nel Register File.
10.22
10.5.1.3 Salto condizionato
Nel caso del salto condizionato è necessario un solo stato (stato 8), nel quale occorre forzare
i segnali di controllo in modo da causare il confronto dei registri A e B (ALUSrcA=1,
ALUSrcB=00, ALUOp=01) e la scrittura condizionale di PC con l’indirizzo presente nel registro
ALUout (PCWriteCond attivo e PCSource=01).
10.5.1.4 Salto incondizionato
Richiede un solo stato (stato 9) per il completamento: si attiva il segnale PCWrite per forzare
la scrittura di PC e si pone PCSource ad 10 in modo che il valore scritto sia costituito da i 26
bit meno signficativi dell’IR, a cui si concatenano 00due come bit meno significativi ed i 4 bit
più significativi di PC per determinare l’indirizzo effettivo di destinazione del salto (modalita’
di indirizzamento pseudo-indiretta).
Instruction decode/
re gister fetch
Instruction fetch
p
r (O
') o
'LW
=
( Op
2
W
= 'S
')
6
')
Q
'B
E
e)
t yp
R=
p
(O
Branch
com pletion
Exe cution
AL USrcA = 1
ALU SrcB = 10
AL U Op = 0 0
8
AL USrcA = 1
ALU SrcB = 00
ALUO p = 10
Jump
comp letio n
9
AL U Src A = 1
AL USrcB = 0 0
ALU O p = 0 1
PC WriteCond
PC Sourc e = 01
PC W rite
PCS ou rce = 10
(O
p
=
'S
W
')
( Op = 'LW')
AL US rcA = 0
AL U SrcB = 11
AL U Op = 00
(O p = 'J')
Me mory ad dress
com p utatio n
1
=
St art
M emRe ad
ALUSrcA = 0
IorD = 0
IR Write
AL USrcB = 0 1
ALU Op = 0 0
P C Write
PC Sou rce = 00
(O
p
0
Me mory
ac ce ss
3
Mem ory
access
5
M em R ead
Io rD = 1
R-type com ple tion
7
Mem W rite
IorD = 1
R e gD st = 1
Re gWrite
M em toReg = 0
W rite-back step
4
R egDst = 0
R eg Write
M em toReg = 1
Figura 10.24 Diagramma a stati finiti dell’unità di controllo del caso multiciclo.
10.5.1.5 Realizzazione dell’unita di controllo multiciclo con macchina a stati finiti
Una macchina a stati finiti può essere implementata mediante un registro temporaneo che
memorizza lo stato corrente ed un blocco di logica combinatoria che determina sia i segnali
che devono essere affermati nell’unità di elaborazione, sia lo stato futuro (vedi fig. 10.25).
10.23
PCWrite
PCWriteCond
IorD
MemRead
MemWrite
IRWrite
Control logic
MemtoReg
PCSource
Outputs
ALUOp
ALUSrcB
ALUSrcA
RegWrite
RegDst
NS3
NS2
NS1
NS0
Instruction register
opcode field
S0
S1
S2
S3
Op0
Op2
Op1
Op4
Op3
Op5
Inputs
State register
Figura 10.25 Implementazione macchina a stati finiti.
La medesima macchina può essere anche implementata attraverso una PLA (Programmable
Logical Array per la quale rimandiamo alla Appendice 10.A [1]) come in figura 10.26.
Op5
Op4
Op3
Op2
Op1
Op0
S3
S2
S1
S0
PCWrite
PCWriteCond
IorD
MemRead
MemWrite
IRWrite
MemtoReg
PCSource1
PCSource0
ALUOp1
ALUOp0
ALUSrcB1
ALUSrcB0
ALUSrcA
RegWrite
RegDst
NS3
NS2
NS1
NS0
Figura 10.26 Implementazione macchina a stati finiti tramite PLA.
10.24
E’ possibile implementare la macchina a stati finiti anche attraverso una ROM (Redable Only
Memory per la quale rimandiamo ugualmente all’Appendice 10.A [1]) nella quale però compaiono
più volte le stesse combinazioni di valori sulle uscite e per questo risulta non essere
ottimizzata come una PLA, in cui compaiono solo le righe affermate della tabella di verità e
vengono presi in considerazione gli opportuni “non-specificati” e gli eventuali termini prodotto.
Con la ROM otteniamo quindi una rete combinatoria ridondante e non opportunamente
semplificata.
10.5.2 Realizzazione dell’unita’ di controllo multiciclo con Microprogrammazione
Se si vuole implementare un intero set di istruzioni MIPS, l’unità di controllo può richiedere
migliaia di stati con centinaia di sequenze distinte, perciò l’implementazione tramite una
macchina a stati finiti diventerebbe poco gestibile. Si ricorre quindi alla
microprogrammazione che consiste nel progettare l’unità di controllo come un programma che
implementa le istruzioni macchina facendo uso di microistruzioni più semplici. La
microprogrammazione viene utilizzata anche nei processori Pentium .
Una microistruzione rappresenta l’insieme dei segnali di controllo che devono essere asseriti
in un determinato stato; eseguire una microistruzione significa affermare i segnali di
controllo da essa specificati. Nel nostro caso, esamineremo come la macchina a stati finiti
descritta in figura 10.24 possa essere implementata facendo ricorso alla
microprogrammazione.
10.5.2.1 Formato microistruzioni
Nel nostro caso, il formato delle microistruzioni è costituito da sette campi. I primi sei campi
controllano l’unità di elaborazione, mentre il campo Sequencing (il settimo) specifica la
modalità di selezione della microistruzione successiva.
I valori permessi per ciascun campo e l’effetto dei diversi valori dei campi sono riportati nella
tabella 10.8.
10.25
Tabella 10.8 Formato delle microistruzioni.
Field name
ALU control
Value
Signals active
Comment
Add
ALUOp = 00
Cause the ALU to add.
Subt
ALUOp = 01
Cause the ALU to subtract; this implements the compare for
Func code
ALUOp = 10
branches.
SRC1
SRC2
Use the instruction's function code to determine ALU control.
PC
ALUSrcA = 0
Use the PC as the first ALU input.
A
ALUSrcA = 1
Register A is the first ALU input.
B
ALUSrcB = 00
Register B is the second ALU input.
4
ALUSrcB = 01
Use 4 as the second ALU input.
Extend
ALUSrcB = 10
Use output of the sign extension unit as the second ALU input.
Extshft
ALUSrcB = 11
Use the output of the shift-by-two unit as the second ALU input.
Read two registers using the rs and rt fields of the IR as the
register
Read
numbers and putting the data into registers A and B.
Write ALU
Register
control
RegWrite,
Write a register using the rd field of the IR as the register number
RegDst = 1,
and the contents of the ALUOut as the data.
MemtoReg = 0
Write MDR
RegWrite,
Write a register using the rt field of the IR as the register number
RegDst = 0,
and the contents of the MDR as the data.
MemtoReg = 1
Read PC
Memory
Read ALU
MemRead,
Read memory using the PC as address; write result into IR (and
lorD = 0
the MDR).
MemRead,
Read memory using the ALUOut as address; write result into MDR.
lorD = 1
Write ALU
ALU
MemWrite,
Write memory using the ALUOut as address, contents of B as the
lorD = 1
data.
PCSource = 00
Write the output of the ALU into the PC.
PCWrite
PC write
control
ALUOut-cond
jump address
PCSource = 01,
If the Zero output of the ALU is active, write the PC with the
PCWriteCond
contents of the register ALUOut.
PCSource = 10,
Write the PC with the jump address from the instruction.
PCWrite
Sequencing
Seq
AddrCtl = 11
Choose the next microinstruction sequentially.
Fetch
AddrCtl = 00
Go to the first microinstruction to begin a new instruction.
Dispatch 1
AddrCtl = 01
Dispatch using the ROM 1.
Dispatch 2
AddrCtl = 10
Dispatch using the ROM 2.
10.5.2.2 Implementazione del microprogramma
Come possiamo vedere dalla figura 10.27, il microprogramma viene assemblato, memorizzato
nella Microcode Memory ed indirizzato dal contatore di microprogramma, nello stesso modo in
cui un normale programma è memorizzato nella memoria di programma e l’istruzione successiva
viene determinata dal PC. La Microcode Memory può consistere in una ROM oppure essere
implementata tramite una PLA. Le ROM sono modificabili con maggior facilità.
10.26
Control unit
Microcode memory
Outputs
Input
PCWrite
PCWriteCond
IorD
MemRead
MemWrite
IRWrite
BWrite
MemtoReg
PCSource
ALUOp
ALUSrcB
ALUSrcA
RegWrite
RegDst
AddrCtl
1
Microprogram counter
Adder
Op[5– 0]
Address select logic
Instruction register
opcode field
Figura 10.27 Implementazione del microprogramma.
10.27
Datapath
10.A APPENDICE
Poiché nel presente capitolo si fa ampio riferimenti a concetti, seppur basilari, di Reti
Logiche, può essere utile un richiamo veloce a queste nozioni così da rendere la lettura più
chiara.
10.A.1 Latch SR
In una rete sequenziale risulta indispensabile fare uso di elementi che mantengano nel tempo
le informazioni. Il più semplice di questi è il Latch SR che prende questo nome per le
operazioni che compie: il Set ed il Reset.
Il concetto che sta alla base di questa piccola rete sequenziale è un dispositivo che dia in
uscita il valore booleano 1 quando si esegue un Set (ingresso S ad 1 ed ingresso R a 0), il valore
0 quando si esegue un Reset (ingresso S ad 0 ed ingresso R a 1) ed il valore precedentemente
presente in uscita quando non si compie nessuna di queste due operazioni (entrambi gli
ingressi a 0).
S
Q’
R
Q
Figura 10.28 Latch SR.
La proprietà prima del Latch è quella di essere trasparente cioè mutare le uscite ad ogni
cambiamento degli ingressi: questo risulta scomodo specialmente quando dobbiamo essere
sicuri che la logica combinatoria abbia effettuato determinate operazioni prima che nuovi
valori vengano immessi nella rete.
Per fare ciò dovremo temporizzate il lavoro del Latch tramite un segnale di clock che abiliti o
disabiliti la sua commutazione. Ci troveremo così in presenza di un Latch con segnale di
abilitazione come in fig 10.29 detto Clocked Latch SR .
S
Q’
Ck
Q
R
Figura 10.29 Latch SR “clockato”.
L’evoluzione naturale di questo sistema è quello che prende il nome di Latch SR edge triggered
o più propriamente Flip-Flop Master Slave realizzato a partire da due Latch SR a cascata con
opposti segnali di abilitazione.
10.28
Q1
S
Q2
Ck
S
Q
Ck
Ck
R
Q
R
Q1
Figura 10.30: Flip – Flop Master Slave.
Latch SR edge triggered.
Questo si differenzia in due sottocategorie: SR positive edge triggered (”apre le orecchie”
sul fronte in salita del clock) e l’SR negative edge triggered (”apre le orecchie” su un fronte in
discesa del clock). La rappresentazione grafia dei due casi e’ riportata in figura 10.31.
Ck
Ck
Positive
Negative
Figura 10.31 Flip – Flop SR eccitati sul fronte in salita e sul fronte in discesa del clock.
10.A.2 Latch D
Poichè il Latch SR in presenza di una particolare combinazione di ingresso dei segnali
(entrambi 1) tende a divenire instabile, è possibile realizzare partendo da questo un
particolare latch che impedisce la comparsa di questa combinazione e funziona in modo ancora
più elementare del Latch SR impulsato: questo è il Latch D realizzato inviando il segnale che
vogliamo memorizzare, affermato sulla porta S e complimentato sulla porta R così che se
l’ingresso vale 1 avremo un operazione di Set, se vale 0 un operazione di Reset.
S
D
Q
Ck
Q
R
Figura 10.32 Latch D.
Analogamente al SR positive e negative edge triggered possiamo implementare un Flip-Flop D
positive e negative edge triggered.
D
S
Q
Ck
R
Q
Figura 10.33 Latch D edge triggered.
10.29
10.A.3 Multiplexer
Nella logica combinatoria, l’ operazione di “if” può essere realizzato tramite una rete logica
caratterizzata da un decodificatore ad n ingressi e 2^n uscite opportunamente collegate ai
2^n segnali tra cui vogliamo scegliere. Un dispositivo con queste caratteristiche è il Mux
(Multiplexer).
A
B
0
1
Figura 10.34 Multiplexer.
10.7 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] F. Fummi, M.G. Sami, C. Silvano, “Progettazione Digitale”
10.30
LEZIONE 11
BUS
11.1 INTRODUZIONE
In un sistema di elaborazione i vari sottosistemi (processore, memoria, dispositivi I/O)
devono essere interfacciati l’uno con l’altro per comunicare tra loro. Tale obiettivo è
normalmente ottenuto tramite l’uso di un BUS.
Il BUS è un canale di comunicazione condiviso che usa un insieme di fili per collegare diversi
sottosistemi (Fig.11.1).
CPU
RAM
ROM
BUS
n fili
PERIF.
I/O
PERF.
I/O
Figura 11.1 : Esempio di BUS.
11.2 DESCRIZIONE DEL BUS
Un BUS tipicamente contiene almeno un insieme di linee di controllo ed un insieme di linee per
trasferire i dati (Fig.11.2).
•
•
Linee di Controllo : sono utilizzate per avanzare delle richieste e segnalare la ricezione,
nonché per indicare il tipo di informazione presente sulle linee dati.
Linee Dati :
portano le informazioni dalla sorgente alla destinazione; tali
informazioni possono consistere in dati o indirizzi.
Linee di controllo
Linee dati
n
p
Figura 11.2 : n=numero di linee di controllo, p=numero di linee dati.
11.1
Esempio: se un disco vuole scrivere in memoria dei dati, le linee dati verranno utilizzate
per specificare l’indirizzo in memoria nel quale collocare i dati, nonché per
trasportare i dati effettivi provenienti dal disco; le linee di controllo saranno usate
per indicare quale tipo di informazione sia contenuta sulle linee di dato del BUS in
ciascun momento del trasferimento.
Alcuni BUS possiedono due insiemi di linee per trasferire separatamente dati e indirizzi nel
corso di un'unica fase di trasmissione su BUS (le linee di controllo continuano a svolgere la
stessa funzione). In questo caso si parla di BUS non-multiplexato.
11.3 VANTAGGI E SVANTAGGI DEL BUS
VANTAGGI
ƒ Versatilità : tramite definizione di un unico standard di connessione si possono
aggiungere facilmente nuovi dispositivi al BUS. Inoltre una data periferica può essere
usata su un calcolatore diverso che usa un BUS che segue lo stesso standard;
ƒ Basso costo : un unico insieme di fili può bastare per gestire i collegamenti con più
dispositivi;
ƒ Gestione complessità : suddividendo il sistema servendosi di più BUS si riduce la
complessità.
SVANTAGGI
ƒ “Collo di bottiglia” nelle comunicazioni : la banda del BUS limita il massimo throughput
ottenibile per I/O : è necessario minimizzare il tempo richiesto per l’accesso al BUS ma,
per garantire elevate prestazioni di I/O, si deve massimizzare l’ampiezza di banda;
ƒ Limiti sulla velocità : le caratteristiche fisiche del BUS (lunghezza, numero di dispositivi
collegati) non permettono velocità di lavoro arbitrarie che inoltre sono limitate dalla
necessità di supportare dispositivi aventi tempi di latenza e velocità di trasferimento dati
ampiamente variabili (es. tastiere, video,…).
11.4 CONNESSIONI TRA UNITÀ FUNZIONALI TRAMITE BUS
Il modo più semplice di interconnettere più unità funzionali tra di loro è quello di utilizzare un
unico BUS, detto BUS di Sistema, al quale sono collegati CPU, memoria e dispositivi di I/O
(Fig.11.3).
Bus di Sistema
CPU
PERIF.
I/O
PERIF.
I/O
Figura 11.3: BUS di sistema
11.2
MEMORIA
A volte può essere più efficace (ma sicuramente più costoso) collegare le varie unità usando
due o più BUS (Fig.11.4). In questo caso distingueremo:
• BUS di memoria, collegante CPU e unità funzionali di memoria;
• BUS di I/O, collegante CPU e unità funzionali di I/O.
BUS I/O
Input
CPU
MEMORIA
Output
BUS Memoria
Figura 11.4: BUS di Memoria e di I/O.
E’ opportuno, a questo punto, classificare i BUS in tre categorie : BUS processore-memoria,
BUS di I/O, BUS di backplane.
11.5 TIPI DI BUS
¾ BUS processore-memoria
: seguono di solito standard PROPRIETARI, ovvero
standard definiti ed utilizzati da una singola azienda, e sono caratterizzati da
lunghezza limitata ed alte velocità di lavoro; vengono ottimizzati rispetto alle
caratteristiche del sistema di memoria tale da memorizzare l’ampiezza di banda
memoria-processore (Fig.11.6,11.7);
¾
BUS I/O : diversamente dal BUS processore-memoria sono tipicamente abbastanza
lunghi e lenti, devono essere adatti a collegare dispositivi I/O molto diversi tra di loro
e quindi tollerano un ampio intervallo di banda dei dati di tali dispositivi; non si
interfacciano direttamente con la memoria, ma si connettono al BUS processorememoria o al backplane BUS tramite un adattatore di BUS (Fig.11.6);
¾
BACKPLANE BUS : sono progettati in maniera da permettere ai processori, alla
memoria e ai dispositivi di I/O di coesistere e cooperare tra di loro su un singolo BUS
ottenendo un notevole risparmio di risorse (Fig.11.5,11.7).
Backplane Bus
Processore
Memoria
Disp
.I/O
Disp
.I/O
Disp
.I/O
Disp
.I/O
Figura 11.5: Sistema con un solo tipo di BUS: semplice e a basso costo ma lento e genera un“collo di
bottiglia”.
11.3
Bus Processore-Memory
Processore
Memoria
Adattatore
di Bus
BUS
I/O
Adattatore
di Bus
Adattatore
di Bus
DispI
/O
DispI
/O
DispI
/O
DispI
/O
DispI
/O
DispI
/O
Figura 11.6 : Sistema con due tipi di BUS : i BUS di I/O si connettono al BUS processore-memoria attraverso
degli “adattatori” o “bridge” e forniscono gli slot di espansione per i dispositivi di I/O.
Bus Processore-Memory
Processore
Memoria
Disp
.I/O
Adattatore
di Bus #1
Backplane Bus
Adattatore
di Bus #2
Adattatore
di Bus #2
Disp
.I/O
Bus di I/O
Bus di I/O
Disp
.I/O
Disp
.I/O
Figura 11.7 : Sistema con tre tipi di bus : il carico sul BUS processore-memoria è fortemente ridotto.
Durante la fase di progetto, il progettista di un BUS processore-memoria conosce
esattamente il tipo di tutti i dispositivi che devono essere connessi al BUS, mentre il
progettista di un BUS di I/O o di backplane deve progettare il BUS in modo da poter
accettare dispositivi non noti a priori, con caratteristiche diverse in termini di latenza e
ampiezza di banda. Per questo motivo, di norma, un BUS di I/O presenta un’interfaccia
relativamente semplice e di basso livello verso il dispositivo.
11.6 CONTROLLO DEL BUS : MASTER/SLAVE
Una volta descritte le caratteristiche principali del BUS, valutiamo come un dispositivo che
desidera comunicare tramite il BUS possa utilizzarlo. E’ ovvio che è necessario uno schema
per controllare l’accesso dei vari dispositivi al BUS: se non ci fosse alcun controllo, dispositivi
diversi che volessero comunicare cercherebbero ciascuno di forzare sulle linee di controllo e
di dato i valori corrispondenti a trasferimenti diversi, rendendo di fatto impossibile l’uso. Per
gestire l’accesso è necessario che un’unità funzionale detenga il controllo del BUS: il BUS
MASTER. E’ questo dispositivo che controlla tutte le richieste sul BUS. Le rimanenti unità
funzionali, che non detengono il controllo del BUS, si dicono SLAVE. Generalmente la CPU
possiede il controllo del BUS, ma può anche cedere temporaneamente questo ruolo ad altre
11.4
unità funzionali. Viceversa la memoria è normalmente uno slave, dal momento che risponde alle
richieste di lettura e scrittura, ma non genera mai proprie richieste (Figura 11.8).
MASTER
CPU
CPU
CPU
I/O
Coprocessore
SLAVE
Memoria
Unità I/O
Coprocessore
Memoria
CPU
ESEMPIO
prelievo istruzione e lettura/scrittura dati
ricezione/invio dati da/a unità di I/O
la CPU dà istruzioni al coprocessore
accesso diretto alla memoria
il coprocessore legge operandi dalla CPU
Figura 11.8 Unità funzionali che detengono il controllo del BUS.
Il sistema più semplice tra quelli possibili è costituito da un singolo BUS MASTER che
controlla tutte le richieste di BUS. Il principale svantaggio di un simile sistema sta nel fatto
che il master deve essere coinvolto in ciascuna operazione. Lo schema alternativo a questo
consiste nell’avere più master, ognuno dei quali in grado di iniziare un trasferimento. In
questo caso, deve esistere un meccanismo per arbitrare l’accesso al BUS così che questo
avvenga in modo coordinato.
11.7 FUNZIONAMENTO DEL BUS : PROTOCOLLO E TRANSAZIONI
Un protocollo di BUS definisce come una parola o un blocco di dati debbano essere trasferiti
su un insieme di fili. L’operazione che permette a master e slave di comunicare tra loro sul
BUS è detta Transazione ed include due fasi:
•
•
la richiesta (dare il comando o l’indirizzo);
l’azione (trasferire i dati);
Il master inizia la transazione sul BUS dando il comando o specificando l’indirizzo; lo slave
risponde alla richiesta inviando i dati al master (se il master richiede dati) o prelevando i dati
inviati dal master (se il master vuole mandarli) (Figura 11.9).
BUS
MASTER
Il MASTER dà il comando
…i dati vanno in una delle direzioni…
BUS
SLAVE
Figura 11.9: Transazione MASTER/SLAVE
Più in dettaglio, Un trasferimento di dati si può generalmente scomporre nelle seguenti fasi:
selezione, durante la quale il master seleziona lo slave coinvolto nel trasferimento
precisandone l’indirizzo; attesa, fase che viene eseguita solo se lo slave è relativamente lento
e quindi richiede un tempo di accesso maggiore (stati di wait); infine, trasferimento dei dati
verso l’unità di destinazione.
11.5
11.8 ARBITRAGGIO DEL BUS
Una transazione, come visto, non può avere inizio se non si definisce chi è il MASTER e chi è lo
SLAVE. Che succede se si hanno più master in un BUS? Quale tra questi avanzerà per primo la
propria richiesta di accesso? Quale userà il BUS al ciclo di BUS successivo?
L’operazione che permette di decidere quale dispositivo tra i vari master utilizzerà il BUS
viene chiamata Arbitraggio del BUS. In un qualunque schema di arbitraggio, i vari master
inviano un segnale di richiesta del BUS (BUS request, BReq) ad un arbitro che invia solo ad
uno di questi un segnale di assegnazione del BUS (BUS grant, BGnt). Ricevuto tale “permesso”,
il dispositivo può utilizzare il BUS, segnalando poi all’arbitro il termine della sua transazione;
l’arbitro può quindi assegnare il BUS ad un altro dispositivo. La maggior parte dei BUS a
master multiplo possiede un insieme di linee per eseguire le richieste e le assegnazioni; ogni
dispositivo dovrà inoltre avere una linea per indicare il rilascio del BUS (BUS release, BRel).
Queste due linee hanno due linee lo stesso ruolo di richiesta e di acknowledge nei protocolli
asincroni (vedi 11.9.2). Nella scelta del dispositivo a cui assegnare il BUS, gli schemi di
arbitraggio tentano di trovare un compromesso tra due fattori : priorità e fairness. Ogni
dispositivo possiede una priorità per quanto riguarda il BUS ed il dispositivo con la priorità
più elevata deve essere servito per
primo. D’altra parte, si vorrebbe che tutti i dispositivi, anche quelli con bassa priorità, non
fossero mai completamente esclusi dal BUS; questa proprietà, chiamata fairness (equità),
garantisce che ciascun dispositivo che vuole usare il BUS prima o poi lo otterrà. Si cerca
inoltre di ridurre il tempo necessario per eseguire l’arbitraggio sul BUS, minimizzando i costi
aggiuntivi che questa operazione comporta.
Gli schemi di arbitraggio possono essere suddivisi in quattro principali categorie:
1)
2)
3)
4)
Arbitraggio
Arbitraggio
Arbitraggio
Arbitraggio
Centralizzato Daisy-Chain;
Centralizzato Parallelo;
Distribuito con Auto-Selezione;
Distribuito con Rilevamento delle Collisioni.
La scelta dello schema di arbitraggio più adatto è determinato da una varietà di fattori, tra
cui il livello di espandibilità che deve essere garantito dal BUS in termini di dispositivi di I/O
e di lunghezza del BUS, la velocità richiesta per l’arbitraggio, il grado di fairness richiesto.
11.8.1 Arbitraggio Centralizzato Daisy-Chain
In questo schema, l’abilitazione ad usare il BUS passa da un dispositivo all’altro a partire da
quello con priorità alta (la priorità è determinata dalla posizione sul BUS) tramite
l’attivazione del segnale di grant. Un dispositivo con priorità elevata, nel momento in cui
desidera accedere al BUS, disattiva il segnale di grant in uscita, impedendo ai dispositivi con
priorità inferiore di accedere al BUS (Fig.11.10).
11.6
Device 1
Device 2
Alta
priorità
BGnt
. . .
Device N
Bassa
priorità
BGnt
BGnt
BRel
Arbitro
BReq
Figura 11.10 : Organizzazione di un BUS in Daisy-Chain
Il vantaggio di questo schema di arbitraggio è la semplicità; gli svantaggi stanno nel fatto che
la propagazione “in catena” del segnale di assegnazione limita la velocità del BUS e nel fatto
che la fairness non può essere garantita : una richiesta a bassa priorità può restare bloccata
indefinitamente.
11.8.2 Arbitraggio Centralizzato Parallelo
Gli schemi appartenenti a questa categoria usano linee multiple di richiesta ed i dispositivi
avanzano indipendentemente le loro richieste (segnale Req) di BUS. L’ arbitro centralizzato
sceglie uno tra i dispositivi che hanno avanzato richiesta di accesso e gli comunica che a quel
punto diventa master del BUS inviando il segnale di grant (Figura 11.11).
DEVICE
DEVICE
2
1
BGnt
. . .
DEVICE
N
BReq
Arbitro
BUS
Figura 11.11 : Organizzazione di un BUS con arbitraggio centralizzato
Lo svantaggio di questo schema sta nel fatto che richiede un arbitro centralizzato che può
divenire “collo di bottiglia” nell’uso del BUS e inoltre limita il numero di dispositivi collegabili
(tipicamente fisso). Questo tipo di arbitraggio è usato nella quasi totalità dei BUS
11.7
processore-memoria e nei BUS di I/O ad alta velocità, nonché nei BUS PCI, EISA,
MULTIBUS I.
11.8.3
Arbitraggio Distribuito con Auto-Selezione
Questi schemi utilizzano anch’essi delle linee di richiesta multiple, ma i dispositivi che
richiedono accesso al BUS determinano essi stessi chi riceverà l’assegnazione del BUS.
Ciascun dispositivo che vuole accedere al BUS scrive sul BUS un codice che lo identifica.
Dall’esame del BUS, i dispositivi possono determinare qual è il dispositivo con priorità più
elevata tra quelli che hanno avanzato la richiesta. Non vi è necessità di un arbitro
centralizzato: ciascun dispositivo determina indipendentemente se è il richiedente con più
alta priorità. Questo schema non richiede comunque un numero più elevato di linee di richiesta
rispetto all’arbitraggio centralizzato (Fig.11.12).
DEVICE
1
DEVICE
2
. . .
DEVICE
N
BUS
Figura 11.12 : Schema arbitraggio distribuito
11.8.4 Arbitraggio Distribuito con Rilevamento delle Collisioni
In questo schema, ciascun dispositivo avanza indipendentemente la sua richiesta di controllo
del BUS. Richieste multiple simultanee provocano una collisione. La collisione viene rilevata e si
usa uno schema per la selezione di un dispositivo tra quelli che hanno causato la collisione. Se
un potenziale master prova a trasmettere e trova il BUS occupato non può dare inizio ad una
transazione e deve riprovare l’accesso al BUS in un secondo momento. Il tempo di attesa si
calcola con una legge di decadimento casuale esponenziale per evitare che si ripeta la stessa
combinazione di unità che tentano l’accesso. Un esempio di arbitraggio distribuito con
rilevamento delle collisioni è la rete Ethernet molto utilizzata oggi nelle schede di rete locale
(Figura : 11.12).
11.8.5 Ulteriori tecniche per incrementare le prestazioni del BUS
•
Arbitraggio sovrapposto: si guadagna tempo effettuando la fase di arbitraggio per la
transazione successiva a quella in corso.
11.8
•
•
•
“BUS parking”: il master mantiene il BUS ed effettua varie transazioni senza passare
nuovamente alla fase di arbitraggio fino a che qualche master non si fa avanti.
“Split – phase” (o “packet switched”) BUS: le fasi di indirizzamento e accesso sono
completamente separate ed ognuna necessita di un arbitraggio separato; durante
l’indirizzamento si produce anche un identificatore (tag) del pacchetto che verrà
associato ai dati in fase di risposta per permettere l’abbinamento indirizzo-dato.
Sovrapposizione delle fasi di indirizzamento e accesso (Figura 11.13).
Nei BUS moderni si usano tutte le tecniche precedenti eventualmente combinate tra di loro.
CLK
active
write
wait
data
XXX
RDATA1
addr
ADDR 1
ADDR 2
RDATA2
WR DATA3
ADDR 3
Figura 11.13 : Sovrapposizione fasi indirizzamento e accesso : esempio con singolo master (processorecache).Durante la fase di accesso ai dati inizio già a specificare l’indirizzo del trasferimento successivo.
RDATA1, RDATA2=dati letti, WR DATA=dati scritti, ADDR1, ADDR2, ADDR3=indirizzi associati ad
RDATA1, RDATA2, RDATA3.
11.9 BUS SINCRONI E BUS ASINCRONI
11.9.1 BUS Sincrono
Un BUS sincrono comprende tra le linee di controllo un segnale di clock (Φ) a frequenza
prestabilita, distribuito a tutte le unità funzionali collegate al BUS, ed un protocollo fisso per
la comunicazione basato sulla tempistica di clock. Il clock scandisce le varie transizioni di
segnale ed il passaggio da un ciclo di BUS a quello successivo. Poiché il protocollo è
predeterminato e richiede una quantità ridotta di logica, il BUS può lavorare molto
velocemente e la sua logica di interfaccia sarà altrettanto ridotta (Figura 11.14).
11.9
Protocollo Sincrono
BReq
BGnt
R/W
Address
Cmd+Addr
Wait
Data
Data1
Data1
Data2
Figura 11.14: Diagramma Temporale : lettura da memoria
BReq (BUS Request) : il Master comanda allo slave un’operazione di accesso; è attivo basso. WAIT : lo
Slave richiede al Master una proroga dell’operazione (cioè un ciclo di BUS aggiuntivo, o STATO di
ATTESA), perché ha bisogno di tempo; è attivo basso. BGnt : BUS Grant. R/W ADDRESS : indirizzo
lettura/scrittura.
11.9.2 BUS Asincrono
Un BUS asincrono non possiede un segnale di clock, quindi può supportare un’ampia gamma di
dispositivi ed il BUS può essere reso lungo a piacere senza che insorgano problemi di “clock
skew” 1 o di sincronizzazione. Per coordinare la trasmissione dei dati tra il trasmettitore ed il
ricevitore, un BUS asincrono usa un protocollo detto di Handshaking (“stretta di mano”)
(Figura 15,16). Tale protocollo consiste in una serie di passi nei quali il dispositivo che invia i
dati e quello che li riceve accedono al passo successivo solo quando ambedue le parti sono
d’accordo.
1
Il “clock skew” non e’ altro che un disallineamento del clock che si propaga su fili che raggiungono
periferiche diverse. A causa della differente lunghezza dei fili tali perifieriche non operano in perfetto
sincronismo.
11.10
Handshake Asincrono: Scrittura
Il Master specifica Address
Data
Next Address
Il Master specifica Data
Read
BReq
Ack
t0
t1
t2
t3
t4
t5
Figura 11.15 : t0 : il Master ha ottenuto il controllo e specifica l’indirizzo, la direzione e dati; attende poi
un certo intervallo di tempo affinche’ lo Slave capisca di essere coinvolto in quel trasferimento… t1 : il
Master attiva la linea “Request”. t2 : lo Slave attiva la linea “Ack”, per indicare di aver ricevuto il dato Æ
ha finito. t3 : il Master rilascia “Ack” Æ ha capito che lo Slave ha finito. t4 : lo Slave rilascia “Ack” Æ ha
capito che il Master ha capito. t5 : inizio nuova transazione.
Handshake Asincrono: Scrittura
Il Master specifica Address
Next Address
Data
Read
BReq
Ack
t0
t1
t2
t3
t4
t5
Figura 11.16 : t0 : il Master ha ottenuto il controllo e specifica l’indirizzo, la direzione e dati; attende poi
un certo intervallo di tempo affinche’ lo Slave capisca di essere coinvolto in quel trasferimento… t1 : il
Master attiva la linea “Request” . t2 : lo Slave attiva la linea “Ack”, per indicare che e’ pronto a
trasmettere il dato. t3 : il Master rilascia “Ack” avendo ricevuto il dato Æ ha finito. t4 : lo Slave rilascia
“Ack” Æ ha capito che il Master ha finito. t5 : inizio nuova transazione.
11.9.3 CONFRONTO Bus Sincroni/Asincroni
9 Il BUS sincrono ha il vantaggio di essere semplice da progettare e da controllare,
richiede pochissima logica e va molto veloce.
9 Il BUS sincrono ha lo svantaggio di portare a sprechi di tempo poiché ogni
operazione, anche se si potrebbe completare in meno di un ciclo di clock, si deve
svolgere in un numero intero di cicli. Inoltre ogni dispositivo sul BUS deve andare alla
stessa frequenza di clock e, nel caso di BUS lunghi, per evitare il “clock skew” (vedi
nota 1) si deve abbassare la velocità.
11.11
9 Il BUS asincrono ha il vantaggio di essere più efficiente nell’uso dei cicli: l’operazione
si completa nel tempo di cui necessita.
9 Il BUS asincrono ha lo svantaggio di essere complesso da progettare e da
controllare.
11.10 COME AUMENTARE LA BANDA DEL BUS
Le prestazioni del BUS sono influenzate sicuramente dal protocollo sincrono o asincrono e dai
parametri temporali caratteristici del BUS (caratteristiche fisiche). Per aumentare la banda
disponibile, altri parametri su cui agire sono:
1) Linee di indirizzo e di dato distinte oppure condivise: se si introducono delle linee
distinte per gli indirizzi e i dati potranno essere trasferiti in un singolo ciclo di BUS. In
questo modo, però, il sistema necessita di più fili e di conseguenza la sua complessità
aumenta.
2) Ampiezza del BUS Dati: aumentando l’ampiezza del BUS dati il trasferimento di parole
richiede un numero minore di cicli di clock. Anche in questo caso il sistema necessita di più
fili (ad esempio nello SPARCstation 20 il BUS di memoria è a 128 bit).
3) Trasferimento a Blocchi (burst transfer): questa soluzione permette di ridurre il tempo
necessario per il trasferimento di un blocco di grandi dimensioni, trasferendo più parole in
cicli di BUS consecutivi. In questo modo evito di ripetere l’indirizzo quando trasferisco più
parole successive specificando solo quello della prima parola; il BUS non viene rilasciato
fino a che l’ultima parola non è arrivata. La complessità del sistema aumenta come anche il
tempo di risposta nei casi di singola richiesta.
11.11 ESEMPIO DI BUS: IL BUS USB
Ai calcolatori sono spesso collegate numerose periferiche a bassa/media velocità: tastiere,
mouse, casse acustiche,cuffia, microfono, ecc. Inizialmente ognuna di queste periferiche
aveva un proprio BUS di I/O indipendente. Il BUS USB è stato ideato allo scopo di fornire un
BUS di I/O universale per le periferiche a bassa/media velocità.
11.11.1 Vantaggi e obbiettivi del BUS USB
I principali vantaggi e obbiettivi del BUS USB sono:
ƒ Facilità di connessione fisica;
ƒ Unico tipo di cavo di BUS;
ƒ Alimentazione fornita dal cavo del Bus;
ƒ Economicità;
ƒ Periferiche installabili a “caldo”;
ƒ Eliminazione dei cavallotti dalle schede di I/O.
11.11.2 Caratteristiche del BUS USB
Le caratteristiche principali del BUS USB sono:
ƒ BUS di I/O sincrono seriale (i dati vengono trasmessi un bit alla volta su un unico filo
di collegamento);
ƒ cavo elettrico a 4 fili;
ƒ banda dati a 12 Mbit/sec. (ver. 1.0) oppure a 480Mbit/sec. (ver. 2.0).
11.12
LEZIONE 12
BUS PCI, SCSI e USB
12.1 IL BUS PCI
La sigla PCI (Peripheral Component Interconnect) indica un bus sincrono a 32/64 bit,
caratterizzato da clock a 33/66 Mhz e alimentazione a 3.3V o 5V (fig. 12.1). Le specifiche
rilasciate da Intel per quanto riguarda il connettore (slot) PCI dichiarano 124 piedini (pin) per
la versione a 32 bit e di 188 piedini per la versione a 64 bit. Di questi, 8 (LOCK) hanno un
significato esclusivamente strutturale (sono necessari per il corretto alloggiamento delle
periferiche) e non corrispondono ad alcuna linea. I segnali in gioco, come vedremo in seguito,
sono perciò 116 o 180 (rispettivamente per la versione a 32 o 64 bit). Nella parte che segue
sono indicati i dettagli di funzionamento.
Figura 12.1. Connettori PCI.
12.1.1 Segnali PCI
Lo schema generale di interconnessioni di un dispositivo PCI è riportato in figura 12.2.
Figura 12.2. Segnali previsti dallo standard PCI (# indica i segnali attivi bassi).
Dal punto di vista elettrico i segnali possono appartenere a una di queste categorie:
12.1
•
•
•
•
•
IN : segnale di solo ingresso,
OUT : segnale di sola uscita,
T/S : segnale tristate bidirezionale (I/O),
S/T/S : segnale tristate pilotato da un solo agente per volta (un agente che lo attiva
deve anche disattivarlo per un ciclo di clock prima di lasciarlo in un terzo stato),
O/D : segnale open drain.
I segnali sono divisi inoltre in 2 classi: richiesti e opzionali. Quelli richiesti sono indispensabili
per realizzare le funzionalità base dell’interfaccia, mentre quelli opzionali non sono necessari
ma devono essere impiegati per realizzare funzionalità aggiuntive.
Segnali richiesti (47 per il target e 47 + 2 di arbitraggio per il master):
• 32 linee di address/data - AD[31..0],
• 4 linee per Comandi o Byte_Enable – C/BE[3..0],
• 1 linea per indicare la parità – PAR,
• 6 Linee di Controllo dell’interfaccia - FRAME#, TRDY#, IRDY#, STOP#, DEVSEL#,
IDSEL,
• 2 Linee di gestione Errori - PERR#, SERR#,
• 2 Linee di Arbitraggio - REQ#, GNT#, sono linee NON-condivise che vanno all’arbitro
PCI ,
• 2 Linee di Sistema - CLK, RST#, clock e reset.
Opzionali (52 linee di cui 37 per l’estensione a 64 bit e 15 comuni):
Estensione a 64 bit:
•
•
•
32 Linee aggiuntive per l’estensione del bus a 64 bit - D[63..32] usate per i 32 bit
aggiuntivi dei dati,
4 Linee per Byte_Enable sui 32 bit aggiuntivi - BE[7..4],
1 Linea per la parita’ sui 32 bit di dati aggiuntivi - PAR64.
Comuni:
•
•
•
•
•
•
2 Linee per la negoziazione di trasferimenti a 64 bit - REQ64#, ACK64# ,
1 Linea di supporto per transazioni multiple – LOCK,
2 Linee di supporto Cache Coherency - SBO, SDONE,
4 Linee di gestione delle Interruzioni - INTA#, INTB#, INTC#, INTD#,
5 Linee di JTAG/Boundary Scan (IEEE 11491) - TDI, TDO, TCK, TMS, TRST, usate per
testing e diagnostica,
1 Linea per indicare il funzionamento a 33 o 66 MHz - M66EN (solo presente nella
versione a 3.3V).
I rimanenti sono segnali di alimentazione, riservati o di I/O (52 comuni e altri 27 nella versione a 64
bit), per arrivare a un totale di 116 segnali sulla porzione a 32 bit e 180 su quella a 64 bit.
12.2
12.1.2 Principi di funzionamento
La funzione più importante è il trasferimento di dati in modalità ‘burst’, ovvero a pacchetti di
dati di lunghezza variabile, con il quale si raggiungono velocità di trasferimento di 132
Mbyte/s (per la versione a 32 bit, 33 Mhz) .
Naturalmente è possibile trasferire anche un dato alla volta con velocità di 66/33Mbyte/s
(R/W per la versione a 32 bit, 33 Mhz). Il clock sincronizza tutte le azioni del bus, e nella
versione 2.0 può raggiungere i 66 Mhz. I segnali sono campionati sul fronte in salita del clock
(positive edge).
12.1.2.1 Trasferimenti
Inizialmente il bus si trova in stand-by, ovvero in una fase di riposo in cui non viene compiuta
alcuna operazione. La negoziazione avviene tra un dispositivo master principale e un dispositivo
slave.
In ogni trasferimento possiamo individuare una fase di indirizzamento in cui vengono
comunicati gli indirizzi, il tipo di trasferimento che deve essere effettuato e una o più fasi di
trasferimento effettivo di dati che terminano con il ritorno allo stand-by.
Per effettuare un trasferimento il dispositivo che prende il controllo del bus (master o
initiator) deve essere autorizzato da un arbitro centralizzato e parallelo di cui parleremo in
seguito, mentre la verifica della disponibilità del bus spetta al suddetto dispositivo.
Cerchiamo di capire meglio, anche in relazione ai segnali in gioco (trattati nel paragrafo
successivo) come si struttura il trasferimento.
Il segnale FRAME# attivato dal master segna l’inizio della transazione (fig. 12.3):
•
•
•
Fase di indirizzamento : dura un periodo di clock; una volta che l’iniziatore ha attivato
FRAME# , identifica il dispositivo target (slave) tramite l’indirizzo su AD[0..31]
(Address/Data) e definisce il tipo di transazione tramite il bus dei comandi C/BE[3:0]
(Command/Byte Enable). Il target (ad es. una memoria) memorizza l’indirizzo e lo
autoincrementa per i successivi dati. Quando un dispositivo PCI si riconosce come
target, attiva il segnale DEVSEL# che, se non viene ricevuto entro un tempo limitato
dal master, provoca l’annullamento della transizione (timeout) da parte dell’initiator.
Fase Dati : ad ogni colpo di clock le linee C/BE[3:0] hanno la funzione di Byte Enable.
IRDY# e TRDY# indicano rispettivamente se iniziatore e target sono pronti. Se sono
entrambi attivati viene scambiato un dato per ciclo di clock, altrimenti viene introdotto
un ciclo di attesa. La durata totale del burst è sancita dal segnale FRAME#, attivo
dall’inizio della fase indirizzamento fino all’inizio dell’ultimo trasferimento dati.
Ritorno in stand-by : l’iniziatore disattiva il segnale FRAME# sul penultimo dato e
IRDY# dopo l’ultimo. Successivamente il bus torna in stato di stand-by.
12.1.2.2 Indirizzamento
Per quanto riguarda l’indirizzamento il PCI distingue tra spazio degli indirizzi di memoria,
spazio degli indirizzi I/O e spazio degli indirizzi di configurazione. Quest’ultimo (parte del
sottosistema I/O) è caratteristica dei dispositivi PCI, che permettono due tipi di decodifica.
•
•
Decodifica in positivo : il dispositivo controlla se l’indirizzo è all’interno del campo a lui
assegnato (metodo usuale).
Decodifica sottrattiva : il dispositivo risponde solo se tutti gli altri non hanno risposto.
12.3
12.1.3 Transazioni PCI (R/W)
Come detto in precedenza tutte le transazioni PCI partono dall’attivazione da parte del
master del segnale FRAME#. L’initiator specifica anche il tipo di transazione voluta sulle linee
C/BE nel primo ciclo di clock. A quello successivo può cominciare la fase di trasferimento dati.
La sorgente dei dati (master o slave rispettivamente per write o read) deve attivare il
corrispettivo segnale xRDY# (x = I o T per W/R) tutte le volte che i dati sono validi. I dati
sono trasferiti su tutti i fronti del clock nei quali sia IRDY# che TRDY# sono attivi. Se uno
dei due non lo è si genera un ciclo di wait (attesa). Nei diagrammi che seguiranno le 2
freccette che si susseguono su una linea interrotta stanno a indicare la fase di turnaround, in
cui un dispositivo rilascia il segnale che aveva pilotato fino a quel momento per permettere ad
un altro di entrarne in controllo.
12.1.3.1 Fase di Read (lettura)
Nella figura seguente (12.3) è illustrata la temporizzazione di una tipica fase di lettura.
1
2
3
4
6
5
7
8
9
CLK
FRAME#
a
b
AD
Address
C/BE#
Bus CMD
g
d
DATA-1
DATA-2
DATA-3
c
Byte Enable
Byte Enable
Byte Enable
f
IRDY#
h
e
TRDY#
DEVSEL#
Fase Indirizzi
Fase Dati
Fase Dati
Ciclo di attesa
Ciclo di attesa
Fase Dati
Ciclo di attesa
Transazione del bus
Figura 12.3. Temporizzazione della fase di lettura del bus PCI.
La transazione di read si svolge nel seguente modo: sul primo ciclo di clock viene attivato il
segnale FRAME# (a) dal master, che ha posto sulle linee AD[0..31] l’indirizzo e sulle linee
C/BE[0:3] il comando di lettura (b). Durante il secondo ciclo di clock il master asserisce
IRDY#, C/BE assume la funzione di Byte-Enable (c) e le linee AD cambiano proprietario,
richiedendo di lasciar passare il ciclo per stabilizzarsi. Con il terzo ciclo di clock ha inizio la
fase dati. Il dispositivo slave risponde al master attivando il segnale DEVSEL#, il segnale
TRDY# e disponendo i dati (d). Il primo dato, ritenuto valido sul fronte in salita del quarto
ciclo di clock, viene trasferito. Nel caso in cui lo slave non sia in grado di produrre un secondo
dato, come nel quinto ciclo di clock, disattiva TRDY# (e) causando un ciclo di attesa; tuttavia
12.4
il dato data-1 rimane sulle linee AD. Il periodo di wait finisce sul fronte in salita del sesto
ciclo di clock e lo slave trasferisce il secondo dato considerato valido dopo la sua disposizione
sulle linee (IRDY# e TRDY# attivi). Nel settimo ciclo di clock è l’initiator ad introdurre una
fase di wait disattivando IRDY# (f), mentre, in previsione della conclusione durante il ciclo
successivo, disattiva anche FRAME# (g). Il target mantiene i dati sul bus per un ciclo extra (il
ciclo 8), sul quale si conclude la transazione (h).
12.1.3.2 Fase di Write (scrittura)
Per quanto riguarda la fase di scrittura, il protocollo è del tutto simile a quello di lettura, ma
manca il turnaround sulle linee AD, che sono sempre pilotate dall’initiator (questo implica che
la scrittura del dato singolo avviene più velocemente rispetto alla lettura).
1
2
3
Address
DATA-1
5
4
6
7
8
9
CLK
FRAME#
AD
C/BE#
Bus CMD
Byte
En.
DATA-2
DATA-3
Byte Enable
Byte
En.
IRDY#
TRDY#
DEVSEL#
Fase Indirizzi Fase Dati
Fase Dati
Fase Dati
3 Cicli di attesa
Transazione del bus
Figura 12.4. Temporizzazione della fase di write.
Anche in questo caso si trasferiscono 3 dati, ma la transazione può iniziare a partire dal
secondo ciclo di clock per quanto appena detto.
Si noti (fig 12.4) che FRAME# sembra apparentemente disabilitato molto presto, ma in realtà
è disabilitato su quello che, trascurando i cicli di wait, è il penultimo ciclo di dati per il master.
12.1.3.3 Fine trasferimento
La richiesta di fine trasferimento può pervenire sia dal master che dallo slave, ma il
trasferimento e’ effettivamente concluso dalla disattivazione del segnale FRAME# da parte
del master.
12.5
•
Fine trasferimento comandata dal master: può avvenire per completamento come negli
esempi precedenti, per timeout se è finito il tempo a disposizione del master e il
segnale GNT# è stato disattivato dalla logica di arbitraggio, o per conclusione
anticipata (master abort), se i segnali TRDY# e DEVSEL# non vengono mai attivati
perchè nessun dispositivo risponde al master (che disasserisce IRDI# e FRAME#).
Inoltre si distinguono 3 categorie fra i dispositivi che decodificano in positivo in base
alla velocità con cui attivano DEVSEL# : Fast se rispondono al primo ciclo di clock dopo
FRAME#, Med se rispondono al secondo, Slow al terzo. Se al quarto ciclo non ha
ancora risposto nessun dispositivo a decodifica positiva e non risponde un dispositivo
con decodifica sottrattiva si ha appunto la conclusione anticipata.
•
Fine trasferimento comandata dallo slave : viene usato il segnale STOP# per chiedere
al master la fine del trasferimento. Esistono tre modalità: Retry , in cui lo slave chiede
al master di ritentare più tardi, Disconnect, dove lo slave chiede di interrompere il
trasferimento corrente e Target abort, con il quale lo slave indica che non vuole
riattivare il trasferimento nemmeno in un secondo tempo.
12.1.4 Arbitraggio
Lo schema dell’arbitraggio è del tipo centralizzato e parallelo, sovrapposto alla fine del
trasferimento precedente in modo che nessun ciclo di bus venga penalizzato. Per
comprenderne meglio il funzionamento ci riferiamo a questo esempio in cui il master B ha
priorità più alta rispetto ad A ma inoltra la richiesta di uso del bus dopo che A ne è già
entrato in controllo. La sequenza è schematizzata in fig. 12.5.
1
2
3
4
5
6
7
CLK
REQ#-A
REQ#-B
b
GNT#-A
a
GNT#-B
c
FRAME#
IRDY#
TRDY#
AD
Address
DATA
Dato di A
Address
Dato diB
Figura 12.5. Arbitraggio nel caso dell’esempio precedente.
12.6
DATA
GNT# è il segnale di grant, ovvero di assegnazione del bus da parte dell’arbitro, mentre
REQ# indica la richiesta di grant inoltrata da un master.
Come si vede inizialmente A ha il grant (a), ma in seguito alla richiesta da parte di B (b)
l’arbitro sposta i privilegi su di lui (c). Il trasferimento di A potrà riprendere quando sarà
finito quello di B (ovvero quando A riconoscerà il bus libero, in questo caso sull’ottavo fronte
in salita del clock).
Se il master mantiene asserito REQ# e gli viene mantenuto il GNT# per mancanza di
richieste a priorità più alta, può iniziare una serie di transizioni consecutive senza necessità di
arbitraggio (cicli back-to-back), impiegando al massimo il bus. Questo entra a far parte di una
tecnica di ottimizzazione PCI detta bus parking.
12.1.5 Bloccaggio risorse
Tramite il segnale LOCK# un master può bloccare uno slave a suo esclusivo utilizzo (lo slave
risponderà con il segnale STOP# ai master non proprietari). Il master che dispone del grant
dovrà sincerarsi che questa linea, utilizzabile solo da un initiator per volta, non sia già in uso,
mentre se è già stata attivata dovrà attendere che si liberi. I master possono comunque
accedere agli slave che non sono bloccati, infatti lo stato del segnale LOCK# non ha effetto
sull’abilità di un target di rispondere alle transizioni.
12.2 IL BUS SCSI
12.2.1 Introduzione
Il bus SCSI(Small Computer System Interface) è un tipico esempio di bus con protocollo
asincrono, I/O parallelo di basso costo e molto flessibile. E’ molto semplice dal punto di vista
costruttivo, infatti il bus è fisicamente un cavo piatto con i suoi connettori, lungo non più di 6
metri. A causa delle frequenze in gioco i conduttori possono presentare però dei
comportamenti irregolari (riflessione del segnale….); per minimizzare gli effetti di disturbo si
inseriscono due terminatori di linea agli estremi del bus, solitamente uno dentro la scatola del
sistema e l’altro connesso all’ultima periferica della catena. Infine è prevista una linea di
potenza che fornisce tensione alle reti resistive dei due terminatori.
La complessità del bus è quindi interamente ai controllori che equipaggiano i dispositivi e negli
eventuali adattatori al bus del sistema.
12.2.2 La configurazione
Terminatore
Z= 50 ohm
Bus SCSI
T
T
HOST
ADAPTER
7
Device
Controller
6
Device
Controller
5
COMPUTER
Device
Controller
0
ID
Figura 12.6 Configurazione del bus SCSI.
12.7
Occorre precisare che ogni dispositivo sul bus SCSI ha un suo identificativo (ID, fig. 12.6)
che entra in ballo durante le fasi operative. Poiché i dispositivi possono essere al massimo 8 si
è scelto di rappresentare l’identificatore con un bit che è trasmesso/ricevuto su uno degli 8
bit delle linee dati.
I dispositivi hanno tutti differente priorità: il dispositivo a priorità più alta è quello
identificato da DB(7), che è tipicamente l’”host adapter”, il meno prioritario identificato da
DB(0) tipicamente quello che fa il ”boot” del sistema.
12.2.3 Il funzionamento
Lo standard SCSI permette il collegamento di differenti periferiche (dischi, stampanti, ecc..)
per le quali è comunque richiesta una interfaccia conforme.
In un dato istante la comunicazione sul bus è consentita solamente a due dispositivi, un
dispositivo funziona come “Initiator” (iniziatore o master), l’altro funziona da “Target”
(obbiettivo o slave). Solitamente un dispositivo SCSI è o target o initiator ma esistono anche
apparecchiature che possono svolgere entrambi i ruoli.
Initiator: tipicamente ruolo svolto dal calcolatore (Host) ma in generale è il dispositivo che
ordina l’esecuzione di una certa operazione (es. lettura dei dati).
Target: dispositivo che esegue l’operazione ordinata dopo aver acquisito il controllo del bus.
Sul bus devono essere collegati almeno un initiator ed un target, ma possono essercene anche
di più. Il bus SCSI permette di collegare al massimo 8 dispositivi ma ogni loro controllore può
avere 8 unità logiche ed ognuna di queste puo’ avere 256 sottounità, quindi nel caso di singolo
host si arriva teoricamente a 7*8*256=14000 periferiche collegabili (Figura 12.7).
Bus SCSI
HOST
adattatore
Controller
scsi 0
Computer
Controller
scsi 6
Figura 12.7 Schema connessioni SCSI con più obbiettivi.
12.2.4 Modalità di trasferimento
Il trasferimento di informazioni sulle linee dati (che avviene a 8, 16, 32 bit) è asincrono, ed in
un singolo ciclo viene trasferito un byte.
La modalità di funzionamento permette l’esecuzione dei comandi in modo parallelo da parte di
diverse periferiche.
12.8
Normalmente l’host trasmette un comando al controllore SCSI della periferica selezionata
quindi si sconnette dal bus lasciandolo libero per altre transazioni; quindi la periferica tramite
il suo controllore si riconnette all’host per concludere la transazione.
12.2.5 Segnali SCSI
Il bus SCSI è più semplice rispetto agli altri tipi di bus; prevede infatti solamente 9 linee per
i dati (8 bit dati che sono indicati come DB(0)-DB(7), ed un bit di parità indicato con DB(P)), e
9 linee di controllo.
La semplicità del bus deriva dal fatto che i dispositivi SCSI si fanno carico della gestione del
traffico sul bus stesso, delle periferiche e dell’accesso alla memoria del sistema.
I segnali di controllo sono i seguenti:
•
•
•
•
•
•
•
•
•
/BSY (busy) Asserito da una o da entrambe le parti durante una transazione per
indicare che il bus è in uso (occupato). Questo segnale può essere collegato in modalità
wired-or (cablato).
/SEL (select) Asserito nelle fasi di arbitraggio, selezione e rielezione da parte di un
initiator o di un target. Segnale wired-or.
C/D (control data) Asserito dal target, in stato alto indica che sulle linee dati ci sono
informazioni di controllo, in stato basso che ci sono dati.
I/O (input/output) Pilotato dal target indica la direzione del trasferimento
relativamente all’initiator. Alto significa in ingresso, basso significa in uscita.
/MSG (message) Asserito dal target durante le fasi di invio messaggi.
/REQ (request) Asserito dal target per iniziare un trasferimento asincrono sul bus.
/ACK (acknowledgement) Asserito dall’initiator per indicare di aver accettato o fornito
dati in risposta ad un segnale di richiesta.
/ATN (attention) Asserito dall’initiator per avvisare il controllore che l’initiator ha un
messaggio ad esso destinato.
/RST (reset) Segnale wired-or; che può essere pilotato da qualsiasi dispositivo ed è
usato in genere dall’initiator all‘avvio quando il dispositivo selezionato non risponde.
12.2.6 Fasi del BUS
Il bus ha 8 distinti stati detti “fasi operative”:
1)
2)
3)
4)
5)
6)
7)
8)
Bus Libero,
Arbitraggio,
Selezione,
Riselezione,
Comando,
Dati,
Stato,
Messaggio.
Le ultime quattro fasi sono di trasferimento dell’informazione. Il bus è sempre inizializzato
allo stato 1 di Bus Libero, e ci ritorna ogni volta dopo un reset; in questa fase il segnale /BSY
non è asserito.
Un sistema SCSI può essere: (a) senza arbitraggio; (b) con arbitraggio del tipo daisy-chain. Si
preferisce un sistema senza arbitraggio, dove cioè non ci sono le fasi di Arbitraggio e di
12.9
Riselezione, nel caso di un host solo ed un solo controllore in modo da rendere più veloci le
operazioni.
Accensione o Reset
SELEZIONE O
RISELEZIONE
BUS LIBERO
Qualsiasi fase di trasferimento informazioni:
COMANDO, DATI, STATO o MESSAGGIO
Figura 12.8 Il diagramma di stato del bus SCSI senza arbitraggio.
• Fase di Arbitraggio
Ha lo scopo di assegnare ad un dispositivo SCSI il controllo del bus. Il dispositivo che
acquisisce il controllo può assumere il ruolo di initiator o di target.
Le fasi che si susseguono sono le seguenti (fig. 12.9):
1) il dispositivo che intende acquisire il bus attende che esso sia libero (BSY disasserito),
2) quindi asserisce /BSY e pone il proprio ID (a 8 bit) sul bus dati.
3) dopo un “arbitration delay” il dispositivo esamina le linee dati: se trova asserito un ID
con maggiore priorità allora tale dispositivo si disasserisce; vince il dispositivo che non
trova dispositivi con ID a priorita’ piu’ alta sul bus.
4) il dispositivo che vince la fase di arbitraggio asserisce /SEL.
5) infine ogni dispositivo in arbitraggio si ritira se riconosce /SEL asserito.
12.10
Accensione o Reset
SELEZIONE O
RISELEZIONE
BUS LIBERO
ARBITRAGGIO
Qualsiasi fase di trasferimento informazioni:
COMANDO, DATI, STATO o MESSAGGIO
Figura 12.9 Il diagramma di stato del bus SCSI con arbitraggio.
• Fase di Selezione
In questa fase un initiator seleziona un target allo scopo di inizializzare una qualche funzione
che il tgarget dovrà svolgere. Per un sistema privo di arbitraggio basta che l’initiator selezioni
il target asserendo i loro ID e /SEL. Invece, in un sistema con arbitraggio la fase di selezione
inizia con /BSY e /SEL asseriti dall’initiator che ha vinto dopo l’arbitration delay. Quindi la
fase di Selezione avviene con i seguenti passi :
1) l’initiator asserisce sul bus il proprio ID e quello dell’obiettivo; disasserisce /BSY e I/O
e si pone in attesa della risposta del target;
2) il target che vede il proprio ID sul bus, con /SEL asserito, con /BSY disasserito e I/O
asserito basso capisce di essere stato selezionato e di conseguenza asserisce /BSY;
3) l’initiator che vede tornare /BSY asserito, disasserisce /SEL ed i bit delle linee dati;
Alla fine della fase di selzione l’obiettivo ha il controllo del bus ed è responsabile d’ora in poi
del trasferimento dei dati fino alla fine della transazione.
• Fase di Riselezione
Questa fase è utile nel caso in cui un target abbia necessità di riconnettersi ad un initiator
per continuare un’operazione che richiede più passi per essere eseguita e che era stata in
precedenza interrotta dal target. Questa sospensione ha comportato il ritorno allo stato di
“bus libero” e vi potrebbero essere inseriti altri dispositivi. La riselezione è comandata, al
contrario della selezione, da un target verso un initiator e questo deve anzitutto vincere
l’arbitraggio. La fase inizia con /BSY e /SEL asseriti dall’obiettivo vincente:
1) iIl target disasserisce /BSY, mantiene /SEL, asserisce l’ID proprio, dell’iniziatore e I/O
alto;
2) l’initiator riconosce di essere stato selezionato e risponde asserendo /BSY;
3) il target vedendo /BSY asserito lo riasserisce mantenendolo fino a quando non vorrà
lasciare il bus e disasserisce /SEL;
4) l’initiator vedendo /SEL disasserito rilascia /BSY.
12.11
• Fase di Trasferimento dell’informazione
Dopo la selezione o riselezione, seguono una o più fasi di trasferimento dell’informazione in cui
/BSY è mantenuto asserito dal target che usa i segnali (/MSG, C/D, I/O).
Una tipica transazione SCSI consiste in una fase di Comando, con la quale il target ottiene un
comando dall’initiator, da una serie di fasi Dati, in cui si ha l’effettivo trasferimento e da una
fase di Messaggio durante la quale l’obiettivo invia il messaggio obbligatorio “Command
Complete” (fig. 12.10).
I byte costituenti i messaggi di comandi e i messaggi dati possono essere trasferiti sia in
modo sincrono che asincrono.
Nel trasferimento asincrono viene usato il meccanismo di handshake tramite la coppia
REQ/ACK, ma si può ottenere anche un trasferimento di tipo sincrono logico suddividendo il
trasferimento in finestre temporali dove il target commuta /REQ per oqni byte di dati e
l’initiator deve commutare /ACK lo stesso numero di volte.
Bus
Settle
Delay
Arbitration Delay
Bus Clear Delay + Bus Settle Delay
Bus free delay
Bus set delay
Asserzione di BSY da parte dell’Initiator
Bus clear delay
Asserzione di BSY da parte del Target
Nei sisemi senza arbitraggio, la temporizzazione inizia qui
BSY
SEL
C/D
I/O
MSG
REQ
ACK
ATN
RST
DB
(7-0,P)
Arb. Target
IDs +Init.
IDs
Bus Free
Arbitraggio
Selezione
Primo
Comando
Fase
Comandi
Ultimo
Comando
Byte di
Dati
Fase
Data IN
Byte di
Dati
Byte di
Status
Fase
Invio Status
Comandi
Completati
Fase
Message IN
Bus
Free
Figura 12.10 Transazione completa del bus SCSI.
12.3 BUS USB
Lo Universal Serial Bus (USB) è uno standard di comunicazione seriale nato nel 1995 dalla
collaborazione di quattro multinazionali dell’industria elettronica e informatica: Compaq (oggi
HP), Intel, Microsoft e NEC. Il sistema USB è asimmetrico, consiste di un singolo gestore e
molte periferiche collegate da una struttura simile ad un albero, attraverso dei dispositivi
chiamati hub (concentratori). Supporta fino ad un massimo di 127 periferiche per gestore, nel
computo vanno inclusi anche gli hub e il gestore stesso quindi in realtà il numero totale di
dispositivi collegabili è sensibilmente inferiore.
12.12
Lo standard prevede che il connettore porti anche un segnale per alimentare le periferiche a
basso consumo (fino a 0.5W). Le periferiche che hanno richieste energetiche più elevate di
questa vanno alimentate a parte. I limiti energetici dello standard vanno seguiti
scrupolosamente pena il probabile danneggiamento del gestore, dato che lo standard USB non
prevede nelle specifiche minime la sconnessione in caso di sovraccarico.
Il trasferimento dei dati avviene fisicamente attraverso le variazioni della tensione
differenziale fra due dei quattro fili costituenti l’USB. L’osservazione delle variazioni della
tensione differenziale consente di riconoscere la connessione/disconnessione degli apparati.
USB è stato pensato per consentire un semplice connessione e disconnessione. Lo standard è
stato progettato in modo da consentire un semplice aggiornamento dei sistemi sprovvisti di
USB attraverso una scheda PCI o ISA. Le porte USB supportano la rimozione “a caldo” (hot
swap) e il reinserimento delle periferiche senza dover riavviare il computer (plug and play).
USB può collegare periferiche quali mouse, tastiere, scanner macchine fotografiche digitali,
stampanti, casse acustiche, microfoni e altro ancora. Per i componenti multimediali oramai lo
standard USB è il metodo di collegamento più utilizzato mentre nelle stampanti sopravvivono
ancora molti modelli dotati anche di porta parallela per questioni di compatibilità.
Lo standard USB 1.0 supporta collegamenti a solo 1.5 Mbit/s, velocità adeguata per mouse,
tastiere e dispostivi lenti. La versione 1.1 aggiunge la modalità full speed che innalza la
velocità a 12 Mbit/s. Occorre però tener presente che la banda disponibile è sempre suddivisa
fra le periferiche connesse. Altro aspetto rilevante è che la priorità dipende dalla posizione
(il livello nella catena) del dispositivo.
All'interno del computer, il USB non ha rimpiazzato lo standard ATA (ovvero AT Attachment, è
un bus a 16 bit, nato originariamente per il collegamento di dischi fissi.) o SCSI per via della
sua lentezza. Il nuovo standard serial ATA per esempio consente trasferimenti dell'ordine di
1.2 Gbit/s, una velocità molto più elevata dello standard USB. L'USB viene molto usato negli
hard disk esterni dove si preferisce privilegiare la praticità di poter collegare e scollegare a
caldo il componente rispetto alla velocità di una connessione tipo ATA.
La maggior novità dello standard USB versione 2.0 è l'innalzamento della velocità di
trasferimento che arriva anche a 480 Mbit/s. Questa velocità cosi elevata consente all'USB
di competere con lo standard Firewire, un ulteriore tipo di bus seriale. I punti in comune tra le
due tecnologie sono molteplici. Entrambi supportano il plug and play. Una periferica Firewire o
USB collegata al PC verrà riconosciuta automaticamente e a installare i driver provvederà il
sistema operativo, l'unico intervento richiesto è di fornire il driver e specificarne la
posizione. Sono interfacce hot swap. Infine, entrambi supportano la modalità isocrona, uno
schema di trasmissione che assegna al dispositivo che sta trasmettendo una parte prestabilita
della banda a disposizione e la massima priorità.
Il forum che sovrintende allo sviluppo dello standard USB ha rinominato USB 1.1 come USB
2.0 Full Speed e USB 2.0 come USB 2.0 High Speed.
Un sistema USB si costituisce a partire da tre elementi (fig. 12.11):
• l’host, un PC equipaggiato con il controllore e il software USB; e’ solo grazie al
software USB che i programmi applicativi hanno accesso alle periferiche connesse:
questo può essere visto come uno strato che si interpone tra il controllore del bus e i
programmi applicativi e che detiene il controllo assoluto di ogni tipo di informazione
scambiata fra le applicazioni e le periferiche connesse al bus;
• i dispositivi USB compatibili (periferiche e hub);
• i cavi di interconnessione.
12.13
Host Usb
PC
HUB
HUB
HUB
scanner
mouse
Periferiche
webcam
modem
tastiera
HUB
HD esterno
fotocamera
Figura 12.11. Esempio di rete USB ad albero.
12.3.1 Organizzazione
I tre livelli che costituiscono il protocollo USB sono:
1. Livello di interfaccia al bus,
2. Livello di periferica logica,
3. Livello funzionale.
HOST
PERIFERICA
Livello
funzionale
Applicazione
Funzione
Livello
periferica
logica
Software Usb
di sistema
Livello
interfaccia
al BUS
Flusso Logico
Periferica
Logica USB
Controllore
USB
Interfaccia al
UBS USB
Flusso
Fisico
Figura 12.12 Organizzazione dei flussi dati.
Dal lato Host il controllore USB converte i segnali logici in segnali elettrici, da inviare sul
cavo. Il software USB di sistema è responsabile di tutto ciò che ha a che fare con
l’operatività del bus: la gestione della banda disponibile, l’alimentazione del bus, il
riconoscimento della connessione e della disconnessione. E’ costituito dal driver (che fornisce
una rappresentazione logica della periferica all’applicazione) e da una parte di controllo. Il
software utilizzatore comunica con uno dei due driver al livello inferiore tramite pacchetti.
Lo scambio di informazioni sul bus USB si basa su terminatori e canali. Tutti i dispositivi USB
devono necessariamente presentare lo speciale terminatore 0 (end-pointer 0). A esso sono
associate le informazioni richieste dall’host nella fase di riconoscimento e configurazione
12.14
della periferica. Tali informazioni sono costituite da parti regolamentate dallo standard (ad
es. tipo di dispositivo, consumo di potenza) e da parti a disposizione del costruttore. Il
terminatore 0 è a capo dei flussi della cosiddetta default control line, il canale che si instaura
all’atto del riconoscimento della presenza della periferica da parte del software USB. Questo
canale si instaura logicamente al livello periferica logica. Attraverso il terminatore 0 il
software USB è in grado di configurare la periferica.
12.3.2
Il cavo
Il cavo USB è costituito da quattro fili:
- D+ e D- utilizzati per il trasferimento dati,
- VSS e VBUS trasportano una piccola quantità di potenza (Max 0.5W).
Doppino non intrecciato d’alimentazione
Rivestimento esterno in PVC
Schermatura esterna a treccia di rame
D+
VBUS
VSS
Schermatura interna in poliestere e alluminio
D-
Doppino intrecciato dedicato ai segnali dati
Figura 12.13. Sezione del cavo USB.
La linea D+ è tenuta dall’host ad una tensione positiva e la linea D- è tenuta a massa. La
trasmissione dei dati avviene per variazioni di ΔV= (D+) - (D-) su quattro livelli (anziché su due
come altri standard prevedono). Le linee di alimentazione VSS e VBUS oltre a fornire una piccola
quantità di potenza per i dispositivi a basso consumo hanno un ruolo fondamentale nella fase di
connessione/disconnessone delle periferiche.
Z0= 90 ohm± 15%
VBUS
VBUS
RPU
D+
D+
RPD
D-
ZPD= 15 Kohm ± 5%
ZPU= 1,5 Kohm ± 5%
VSS= 0V
cavo USB
VSS
VSS
RPD
D-
Host
Figura 12.14. Struttura di una periferica veloce.
12.15
VBUS= 5V
VBUS
D+
RPD
D-
D+
cavo USB
VSS
VSS
RPD
DRPU
VBUS
Host
Periferica
Figura 12.15. Struttura di una periferica lenta.
Connessione di una periferica
Appena il cavo USB viene collegato al dispositivo (t0), la tensione di alimentazione trasportata
dal cavo si applica alla resistenza di pull-up Rpu. Si forma così un partitore resistivo fra VSS e
VBUS sulla linea D (D+ se il dispositivo è lento, D- se è veloce) che determina una caduta di
tensione del 10% su D in ingresso alla porta dell’Host. Questa caduta di potenziale è
sufficiente ad alzare il valore della tensione sul cavo ad un livello superiore della soglia VIH
(t1). Nella figura 12.16 si vede cosa accade nel tempo alla tensione di D+ durante la fase di
connessione.
VIH
t0
t1
t
TDCNN
Figura 12.16. Connessione di una periferica USB.
.Se questa condizione permane per un tempo TDCNN>2.5μs viene interpretata dall’hub come
avvenuta connessione di un dispositivo (t2). A questo punto inizia la fase di configurazione
della periferica da parte del software USB.
Disconnessione di una periferica
Durante il normale funzionamento una delle due linee D è sempre sopra ad una tensione di
soglia (VIHZ), mentre l’altra è prossima alla tensione di riferimento (VSS). Al momento della
disconnessione (t0) la resistenza di pull-down abbassa la tensione della linea da livello alto
sotto la soglia VIL (t1) e se vi permane per un periodo di tempo TDCNN>2.5μs l’host disabilita la
porta (t2). L’andamento temporale della tensione ai capi di una delle due linee D è illustrato in
figura 12.17.
12.16
VIHZ(min)
t1
VIL
t2
t0
t
TDDIS
Figura 12.17. Disconnessione di una periferica USB.
12.3.3 Configurazione delle periferiche
Il bus USB è in esclusivo controllo dall’host che, anche nella situazioni di riposo, lo mantiene
attivo trasmettendo un apposito segnale a intervalli regolari di 1 ms.
Successivamente al collegamento di una periferica e il relativo riconoscimento da parte
dell’hub, inizia la fase detta “enumerazione del bus”, che permette all’host di identificare il
dispositivo e di configurarlo. Questo processo si può schematizzare in quattro fasi:
1) l’hub informa l’host che c’è stata una variazione del suo stato;
2) il software USB riceve dall’hub l’identificativo della porta a cui è connessa la nuova
periferica;
3) l’host invia alla porta dell’hub identificata in precedenza un comando per la sua
abilitazione (la periferica può ora dialogare con l’host sul canale di base attraverso il
terminatore 0);
4) l’host assegna alla periferica un indirizzo univoco mentre il software USB determina la
frazione delle risorse da allocare alla periferica (in base alle informazioni ricevute sul
canale di base al passo precedente).
12.3.4 Formato dei pacchetti
Ogni pacchetto è diviso in campi di 8 bit (o multipli), inviati sul bus dal meno significativo al
più significativo. Tutti i pacchetti iniziano con un campo di sincronizzazione, del quale fa parte
il SOP (start of packet) che marca l’effettivo inizio del pacchetto. Successivamente al SOP
viene trasmesso un campo di 8 bit contenente il PID, ossia l’identificativo del tipo di
pacchetto (Tabella 12.1).
12.17
Tabella 12.1 Tipi di pacchetto previsti dal protocolla USB.
Tipo
Nome
bit [3:0] Descrizione
di PID
Token
OUT
0001B
IN
1001B
Indirizzo e numero di endpoint in una transazione host → funzione
Indirizzo e numero di endpoint in una transazione funzione → host
SOF
0101B
Indicatore di inizio frame e numero del frame
SETUP
1101B
Indirizzo e numero di endpoint in una transazione host → funzione per il
setup di un control pipe
Data
Handshake
Speciale
DATA0
0011B
Pacchetto data PID pari
DATA1
1011B
Pacchetto data PID dispari
ACK
0010B
Il ricevente ha accettato i pacchetti dati senza errori
NAK
1010B
Il ricevente non accetta i pacchetti dati
STALL
1110B
L'endpoint è bloccato o non è esaudibile una richiesta di control pipe
PRE
1100B
Preambolo della sessione host. Abilita il traffico sui bus in uscita verso le
periferiche lente.
•
Formato dei pacchetti di tipo ‘Token’:
PID
ADDR
8 bit
7 bit
ENDP
4 bit
CRC5
5 bit
Dove ADDR è l’indirizzo assegnato alla periferica, ENDP identifica il terminatore della
periferica indirizzata e CRC è il Cyclic Redundancy Check
•
•
Formato dei pacchetti di tipo ‘Data’:
PID
DATA
CRC16
8 bit
Max 1024 byte
16 bit
I pacchetti di tipo handshake sono costituiti solo dal campo PID e vengono utilizzati
come controllo per lo stato di una transizione.
12.4 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] G. Bucci, "Architettura dei Calcolatori Elettronici", McGraw-Hill, 2001, ISBN 88-386-0889-X.
[3] Sito web: http://www.usb.org.
[4] Sito web: http://pinouts.ru/data/PCI_pinout.shtml
12.18
LEZIONE 13
Tecniche di pilotaggio dei dispositivi I/O
13.1 GESTIONE DELL’I/O (CARATTERISTICHE GENERALI)
Le unità di I/O (o unità periferiche o semplicemente “periferiche”) consentono di collegare il
calcolatore con il mondo esterno (persone, dispositivi). L’immagine (Figura 13.1) riprende lo
schema di principio dei collegamenti tra le componenti di un calcolatore immaginando di avere
un bus come supporto fisico delle interconnessioni.
CPU
I/O
MEMORIA
Figura 13.1. Collegamento fra le componenti di un calcolatore.
Esistono numerosi tipi di unità di I/O con caratteristiche molto varie:
- trasferiscono differenti quantità di dati;
- funzionano a velocità diverse;
- hanno un formato dei dati, in genere, differente da quello utilizzato nell’unità centrale.
La tabella che segue riporta alcuni dei tipi oggi più comuni e fissa l’attenzione su di un
parametro importante: la velocità di trasferimento delle informazioni, espressa in byte/s.
Tabella 13.1. Velocità di trasferimento dei più diffusi dispositivi.
Vel. Trasferimento (MB/s)
Dispositivo
Tastiera
0.00001
Mouse
0.0001
Modem 56k
0.007
Canale telefonico
0.008
Doppia Linea ISDN
0.016
Stampante Laser
0.1
Scanner
0.4
Ethernet classica
1.25
USB (Universal Serial Bus)
1.5
Cinepresa Digitale
4
Disco IDE
5
6
CD-ROM 40x
Fast Ethernet
12.5
Bus ISA
16.7
Firewire (IEEE 1394)
50
Monitor XGA
60
Rete SONET OC-12
78
Disco SCSI Ultra2
80
Gigabit Ethernet
125
Bus PCI 32-bit/33MHz
133
Nastro Ultrium
320
533
Bus PCI-X
Sun Gigaplane XB backplane
13.1
20000
È bene rivelare che alcuni dispositivi sono in grado di compiere o solo operazioni di input o solo
operazioni di output mentre altri sia le une che le altre. Inoltre le velocità di trasferimento
sono molto inferiori a quelle possibili all’interno delle altre componenti (unità centrale,
memoria) e quindi le unità di I/O devono fronteggiare il problema della conversione tra la
rappresentazione interna ed esterna dell’informazione (operazioni di codifica e decodifica).
13.1.1
Architettura del sistema di I/O
I dispositivi di I/O sono costituiti da:
- un componente sensore (input) e/o un attuatore (output);
- componenti software;
- componenti elettronici (controller).
Per indirizzare le unità di I/O, l’unità centrale ricorre ad una delle tre seguenti tecniche:
a) Riservare all’unità di I/O uno spazio di indirizzamento indipendente: l’unità centrale
utilizza specifiche istruzioni nelle quali fornisce anche l’indirizzo identificativo del
dispositivo da utilizzare nell’operazione.
b) Riservare una porzione dello spazio di indirizzamento in memoria ai dispositivi di I/O,
in modo che ogni volta che l’unità centrale utilizza un indirizzo di questa porzione in
realtà indirizzi un dispositivo di I/O.
c) Soluzione ibrida.
0xFFFF
…
0
Memoria
(a)
Porte di I/O
Memoria
Memoria
(b)
Porte di I/O
(c)
Figura 13.2. Tecniche di indirizzamento di dispositivi I/O.
Se si usano le tecniche (a) e (c) per scambiare dati tra processore e memoria si rendono
necessarie:
- istruzioni speciali di I/O, che specificano il numero del dispositivo;
- il comando da dare al dispositivo.
Il primo può essere trasmesso come un indirizzo sul bus di I/O, il secondo come un dato sul
bus di I/O. Se si utilizza il metodo 2 non è necessario introdurre nuove istruzioni per
comunicare con l’I/O. È sufficiente riservare alcuni indirizzi di memoria e letture e scritture
a tali indirizzi sono interpretati dalle periferiche come comandi.
Per interagire con i dati provenienti da un dispositivo I/O è possibile procedere in tre modi
diversi:
13.2
-
polling;
interrupt;
DMA (Direct Memory Access).
13.2 POLLING
Il primo metodo non richiede hardware aggiuntivo: si chiama polling o interrogazione ciclica
dei sensori.
CPU
Dato
pronto?
no
si
Memoria
IOC
Disp.
Leggi dato
da DR
LEGENDA:
IOC = I/O Controller
DR = Data Register
MEM = Memoria Ram
DISP = Dispositivo
Scrivi dato
in MEM
fatto?
no
si
(b)
(a)
Figura 13.3. (a) Configurazione del bus con IOC. (b) Diagramma di flusso del polling.
La CPU richiede un’operazione di I/O. Il controller di I/O effettua l’operazione, attiva un bit
nello “status register” ma non informa né interrompe la CPU.
Come si vede dal diagramma di flusso sopra disegnato (Figura 13.3 (b)), in momenti opportuni
del programma, ed in modo sincrono con esso, si interroga lo stato del registro DR ed
eventualmente si fa intervenire la routine di servizio, cioè un breve programma che esegue il
trasferimento dati da /verso la periferica. Il ritardo tra l’effettiva interrogazione del
sensore (DR) è variabile, e nel caso peggiore pari al tempo fra due interrogazioni successive.
Inoltre la CPU impiega un tempo lungo a comunicare con tutti i sensori. Qualora il sensore
richieda un tempo di risposta minore o il microprocessore non abbia tempo per interrogare i
sensori, si ricorre al metodo di interrompere il programma principale su iniziativa della
periferica.
Vantaggi:
- non è richiesto hardware aggiuntivo;
- la CPU ha il controllo totale e fa tutto il lavoro.
Svantaggi:
- la gestione del polling può consumare molto tempo della CPU;
- richiesta e verifica periodica dello stato di tutti i dispositivi, compresi quelli
che non ne hanno bisogno;
13.3
- rischio di non soddisfare esigenze di urgenza (Real-Time, per esempio nel
polling vengono visitate le periferiche in modo ciclico e quindi non si possono
gestire eventi all’atto del loro verificarsi).
Per capire come funziona il Polling, riportiamo un esempio di stampa di una stringa con l’uso di
questo metodo (Esempio 13.1):
copy_from_user(buffer, p, count);
/* p e’ un buffer nel kernel */
for (k=0; k<count; ++k) {
/* ripeti per ogni carattere */
while (*printer_status_reg != READY);
/* attendi il bit di READY */
*printer_data_register = p[k];
/* uscita di un carattere */
}
return_to_user();
Esempio 13.1. Stampa di una stringa con Polling.
13.3 INTERRUPT
CPU
3
ROM
MEM
LEGENDA:
IOC = I/O Controller
MEM = Memoria Ram
I/O = Dispositivo I/O
IOC
I/O 1
I/O 2
I/O 3
Figura 13.4. Schema del principio di funzionamento dell’interrupt.
Il meccanismo di interrupt viene utilizzato per la gestione degli apparati di I/O, per
svincolare la CPU dal fare il polling.
La CPU è collegata direttamente con il controllore di interrupt tramite il bus di sistema, e
anche attraverso 3 piedini che vengono utilizzati per gestire le interruzioni.
Il primo piedino, INTR, serve per ricevere le richieste di interruzione dai dispositivi di I/O.
Il secondo, INTA, serve invece per comunicare al controllore la disponibilità della CPU a
gestire la routine di servizio per l’interrupt richiesto precedentemente.
L’ultimo piedino, NMI, serve per gestire interruzioni “non mascherabili” ovvero che hanno la
massima urgenza e provoca un’immediata interruzione dell’attività della CPU per dedicarsi alla
gestione di tali eventi.
13.4
Tutti questi meccanismi devono richiamare l’attenzione della CPU rapidamente e poter
trasferire i dati in modo veloce e efficiente.
Esistono vari tipi di interrupt che si dividono in due categorie principali:
- interrupt esterni;
- interrupt interni.
I primi sono legati alla gestione dei dispositivi nello spazio di I/O e servono per due motivi
principali:
- la CPU deve essere libera di operare senza controllare continuamente lo stato delle
periferiche;
- garantire un accesso diretto e rapido alla CPU per richiedere l’inizio di un’operazione o
comunicarne la fine.
I secondi possono derivare da situazioni particolari della CPU e possono essere di tre tipi:
- TRAP
- ABORT
- FAULT
In generale le interruzioni interne prendono il nome di eccezioni (Vedi lezione 9).
13.3.1 Trasferimento dati a Interrupt
Vediamo ora il meccanismo di gestione di un interrupt (Vedi anche Figura 13.6).
La presenza di un interrupt viene segnalata alla CPU mediante una dei due piedini INTR o
NMI. La richiesta viene memorizzata e testata alla fine di ogni ciclo di istruzioni e viene
deciso, in base alla urgenza, se occuparsi immediatamente della richiesta o se congelarla fino
al prossimo ciclo di istruzioni. Appena possibile la CPU risponde salvando il contenuto dei
registri in memoria e richiedendo al controllore informazioni sul tipo di interruzione o
analizzando l’eccezione intervenuta. In seguito la CPU avvia una procedura legata al tipo di
interrupt pervenuto. Terminata questa routine viene ricaricato in memoria lo stato
precedente e il processore torna ad occuparsi dei programmi precedentemente in esecuzione.
L’evento che causa l’interruzione è asincrono rispetto l’esecuzione del programma.
Per quanto riguarda le interruzioni interne di tipo software queste possono essere individuate
tramite un numero e fanno capo a una tabella IDT (Interrupt Descriptor Table) che si crea in
memoria durante l’inizializzazione del sistema operativo e all’interno della quale si trovano i
riferimenti agli indirizzi in memoria delle routine di servizio legate al singolo interrupt.
13.5
Add
Sub
And
Or
nop
(1)
I/O
inter
rupt
User
program
(2)
Salva PC
(3) interrupt
service addr
lw
sw
:
:
Interrupt
service
routine
rfe
Figura 13.5. Trasferimendo ad Interrupt.
Per determinare il dispositivo che ha generato l’interrupt giunto esistono diversi metodi.
Uso di differenti linee per ogni controller:
- PC (Personal Computer);
- limita il numero di dispositivi, in quanto il IOC ha tipicamente un numero limitato di
linee nelle quali accetta l’interrupt.
Software Poll:
- la CPU chiede a ognuno dei controllori se ha generato l’interrupt;
- lento.
Daisy Chain (Hardware Poll):
- il sistema di Interrupt Acknowledge viene inviato in daisy-chain;
- il controller è responsabile di mettere sul bus un “vettore”;
- la CPU usa il vettore per identificare la routine di servizio.
Bus Master:
- il controller richiede il bus prima di fare interrupt;
- ex PCI, SCSI.
La gestione di interruzioni multiple avviene tramite assegnazione di una priorità a ognuno degli
interrupt e solo quello con maggiore urgenza giunge alla CPU.
L’ordine di priorità degli interrupt tipicamente è il seguente:
- interni (eccezioni);
- non mascherabili;
- software;
- hardware esterni.
Ad esempio fanno parte della prima categoria:
la divisione per zero, ovvero quando nell’operazione DIV il divisore è nullo e il risultato di
questa divisione non può essere trascritto su registro;
la modalità di esecuzione in Single Step Trap in cui alla fine di ogni operazione viene bloccata
l’esecuzione del programma.
13.6
Nella seconda categoria troviamo interruzioni dovute a un calo di alimentazione o a un errore
di lettura in memoria.
Nella terza tutte quelle interruzioni che vengono chiamate dal programmatore tramite la
funzione INT (tipicamente nei processori x86) seguita dal numero dell’interrupt desiderato.
Infine l’ultima tipologia fa capo alle interruzioni generate dall’utilizzo di una periferica del
computer (ex mouse, tastiera, ecc).
E’ possibile impedire le interruzioni se non sono opportune, a ogni richiesta è associata una
urgenza ma la CPU accetta solo richieste con un dato livello di priorità o più elevato.
Per comprendere meglio questo metodo, possiamo riproporre l’esempio 13.1, utilizzando questa
volta l’interrupt (Esempio 13.2):
Routine di servizio dell’interrupt
If(count==0) {
Unblock_user();
} else{*printer_data_register=p[k];
count= count-1;
k=k+1;
}
acknowledge_interrupt();
return_from_interrupt();
copy_from_user(buffer, p, count);
enable_interrupts();
while(* printer_status_reg !=READY);
*printer_data_register = p[0];
scheduler();
Esempio 13.2. Stampa di una stringa ad interrupt.
13.3.2 Esempio – Architettura dei Personal Computer
Facciamo ora un esempio di funzionamento del meccanismo di interrupt per l’architettura
8086, usata a partire dal 1981 nei Personal Computer.
CPU
8086
INTC
INTR
INTA
8259A
DATA
IRQ
IRQ
IRQ
IRQ
IRQ
IRQ
IRQ
IRQ
0
1
2
3
4
5
6
7
Figura 13.6. Interrupt Controller con un’architettura 8086.
I processori della famiglia 80x86 hanno una solo linea di interrupt e sono collegati a un
Interrupt Controller che si chiama 8259A che presenta in ingresso 8 linee di interrupt (IRQ)
a ognuna delle quali può essere collegato un dispositivo (Figura 13.6), oppure a sua volta
collegati ad altri controllori annidati (massimo 2).
L’ 8259A accetta l’interrupt da una delle linee.
L’ 8259A ne determina la priorità secondo l’ordine precedentemente descritto e avvisa l’8086
attivando la linea INTR. La CPU invia la disponibilità a trattare la richiesta tramite il piedino
INTA.
13.7
L’ 8259A mette sul bus dei dati il “vettore” cioè il numero che identifica la sorgente
dell’interrupt e quindi la CPU può servire la richiesta pervenuta eseguendo la relativa routine.
13.4 DMA ( DIRECT MEMORY ACCESS )
Un problema che si verifica frequentemente nelle tecniche dell’ interrupt e del polling per
l’accesso alla memoria è quello di una limitata velocità di trasferimento e di un utilizzo
eccessivo della CPU.
La soluzione per ovviare a questo inconveniente è l’utilizzo del DMA (Direct Memory Access).
13.4.1 Funzionamento del DMA
Il DMA agisce come un master del bus, sostituendosi alla CPU, per il trasferimento di blocchi
di dati da e verso la memoria senza l’intervento da parte della CPU stessa.
Il DMAC deve essere programmato dalla CPU, che gli indica:
• l’indirizzo iniziale della zona di memoria coinvolta;
• l’indirizzo del controller del dispositivo (IOC);
• la direzione da seguire (Read/Write);
• la quantità di dati da trasferire.
CPU
Memori
a
DMAC
LEGENDA:
IOC = I/O Controller
DMAC = DMA Controller
IO
C
Disp.
Figura 13.7. Configurazione di un bus con DMA Controller.
Il DMA Controller genera tutti i segnali necessari per indirizzare la periferica e la memoria.
In questo modo la CPU può svolgere altro lavoro mentre il DMA Controller si occupa del
trasferimento. Quando questo ha finito manda un interrupt alla CPU.
13.4.2 Trasferimento dei dati
L’unità DMA può effettuare il trasferimento dei dati in uno dei seguenti modi: modo burst e
modo cycle stealing.
- modo burst: l’unità trasferisce l’intero blocco di dati, bloccando gli accessi da parte delle
altre periferiche di I/O e della CPU per tutta la durata del trasferimento.
Questo metodo realizza la più alta velocità di trasferimento tra dispositivi di I/O e memoria,
ma può impedire alle altre unità e alla CPU di accedere alla memoria anche per tempi lunghi.
- modo cycle stealing: l’unità trasferisce i dati una word alla volta, consentendo l’accesso alle
altre unità DMA e alla CPU tra una word e l’altra. Con riferimento alla CPU, questa si vede
13.8
sottrarre di tanto in tanto dei cicli di accesso alla memoria, con conseguente rallentamento
del suo funzionamento. Il processore può comunque evitare di fare accesso alla memoria nella
maggior parte dei casi utilizzando la memoria cache. Inoltre, questa modalità riduce la
velocità di trasferimento dei dati, ma anche l’interferenza tra unità di I/O e CPU, che non
dovrà fare il salvataggio/ripristino dello stato come nel caso dell’interrupt, ma al massimo
attendere di più quando dovrà accedere al bus.
13.4.3 Configurazioni
Una delle possibili configurazioni che si possono adoperare per l’utilizzo del DMA prevede
l’uso di una singola linea di Bus e del DMA controller con accesso a un bus comune agli I/O
Device e alla CPU (paragrafo 13.5) (Figura 13.8). In questo caso, ogni trasferimento occupa il
bus due volte: la prima per il trasferimento dal dispositivo di I/O al DMA e la seconda dal
DMA alla memoria. Quindi, in questo caso la CPU troverà il bus occupato per il doppio del
tempo per ogni trasferimento.
CPU
DMA
Controller
I/O
Device
I/O
Device
Main
Memory
Figura 13.8. Configurazione con DMAC separato dagli I/O device e dalla Cpu con accesso a bus comune.
Una seconda configurazione prevede sempre l’utilizzo di una sola linea di bus, ma con il DMA
controller integrato nei dispositivi di I/O (Figura 13.9). Con questa configurazione il
controller può gestire più di un dispositivo per volta e, inoltre, ogni trasferimento occupa il
bus una sola volta: per trasferire i dati dal DMA alla memoria. Questo porta a dimezzare le
volte in cui la CPU trova il bus occupato.
CPU
DMA
Controller
DMA
Controller
Main
Memory
I/O Device
I/O
Device
I/O
Device
Figura 13.9. Configurazione a singola linea di bus con il DMAC integrato negli I/O device.
Una terza possibile configurazione del DMA usa una linea di bus separata per i dispositivi di
I/O (Figura 13.10). Così facendo il DMA controller avrà a disposizione un bus dedicato che
supporta tutti i dispositivi abilitati al DMA. Come la seconda configurazione, anche questa
utilizza il bus ”principale” una sola volta per ogni trasferimento e inoltre dimezza le volte in
cui la CPU trova occupato il bus, rispetto al primo caso.
13.9
CPU
DMA
Controlle
r
I/O
Device
I/O
Device
I/O
Device
Main
Memory
I/O
Device
Figura 13.10. Configurazione con una linea di bus separata per gli I/O device.
13.4.4 I/O Processor
Per evitare ulteriormente di fare riferimento al processore per la gestione dei trasferimenti
di dati, il controllore DMA può essere un po’ più “autonomo” attraverso l’uso di dispositivi
chiamati Processori di I/O (IOP), che gestiscono al loro interno, attraverso un programma
implementato in hardware o salvato in memoria, alcune operazioni di I/O che definiscono
dimensione e indirizzo del trasferimento per le operazioni di lettura e scrittura. In questo
modo l’ IOP interrompe la CPU solo quando è effettivamente pronto per il trasferimento
(Figura 13.11).
13.10
CPU
Mem
IOP
D1
memory
D2
bus
. . .
I trasferimenti da/verso
la memoria sono controllati
direttamente dall’IOP
IOP effettua cycle stealing
Dn
I/O
bus
(a)
(1)
LA CPU
da’ una
istruzione
a IOP
CPU
IOP
(4) IOP
interrompe
la CPU quando
ha finito
(2)
(3)
memoria
(b)
dispositivo target
OP
dove sono i comandi
Device
Address
IOP trova il comando in memoria:
OP
cosa
fare
LEGENDA:
IOP = I/O Processor
MEM = Memoria Principale
D1,D2,…,Dn = Dispositivi I/O
DMAC = DMA Controller
Addr
dove
mettere
i dati
Cnt
Other
quanti
dati
richieste
speciali
(c)
Figura 13.11. (a) Configurazione dell’ IOP. (b) e (c) Funzionamento dell’ IOP
13.11
13.5 TIPI DI CONTROLLER DMA
Il DMA controller è la vera e propria periferica dedicata al trasferimento dei dati tra
memoria e I/O Device o tra due aree di memoria, ed è capace di controllare il bus. In pratica
agisce come una implementazione hardware della routine di gestione dell’ interrupt di bufferpieno o buffer-vuoto. Si posso individuare tre tipi principali di controllers DMA:
- 1D : con un singolo registro per gli indirizzi;
- 2D : con due registri per l’indirizzamento;
- 3D : con tre o più registri per l’indirizzamento.
13.5.1 Struttura di un DMA generico
Un DMA Generico è costituito da varie parti, ognuna specializzata a svolgere un compito
preciso:
• Address Generator :
Genera gli indirizzi di memoria o di periferica coinvolti nei trasferimenti. Generalmente
è costituito da un registro base e un contatore auto-incrementante.
• Address Bus :
Qui finiscono gli indirizzi generati per accedere ad una specifica locazione di memoria
o della periferica.
• Data Bus :
Viene utilizzato per trasferire dati dal DMA alla destinazione. Molto spesso, però, il
trasferimento avviene direttamente dalla sorgente alla destinazione, con il controller
DMA che seleziona solamente i due dispositivi.
• Bus Requester :
Questo dispositivo è utilizzato per richiedere l’utilizzo del bus alla CPU, cosa che si
rende necessaria quando si usa per la prima volta il DMAC.
• Local Peripheral Control :
Questo elemento permette al controller DMA di selezionare la periferica coinvolta nel
trasferimento di dati e di gestirla nel modo appropriato. Il Local Peripheral Control è
fondamentale per l’indirizzamento singolo o implicito.
• Interrupt signals :
Attraverso gli interrupt signals il controller DMA può interrompere la CPU appena il
trasferimento tra due periferiche è terminato o si è verificato un errore.
Il funzionamento di un controller DMA in generale si può dividere in due fasi: la fase di
programmazione e la fase di effettivo trasferimento. Queste due fasi non possono avvenire,
per ovvie ragioni, simultaneamente, nel senso che se si sta effettuando un trasferimento, non
è possibile programmare il controllore.
Riportiamo adesso l’esempio di stampa di una stringa, utilizzando questa volta il DMA (vedi
esempi 13.1 e 13.2):
13.12
copy_from_user(buffer, p, count);
setup_DMA_controller();
scheduler();
acknowledge_interrupt();
unblock_user();
return_from_interrupt();
Esempio 13.3a. Codice di preparazione alla stampa
Esempio 13.3b. Routine di servizio dell’interrupt
Esempio 13.3. Stampa di una stringa con DMA.
Nella fase di programmazione il controller viene visto come un normale dispositivo di I/O
dotato di registri, nei quali vengono definiti gli indirizzi base, la dimensione del trasferimento
da effettuare e le comunicazioni con il processore (ad esempio sulle condizioni su cui si
generano gli interrupt). In questa fase, il controller si comporta da slave e le operazioni da
eseguire vengono impartite dalla CPU (Figura 13.12, punto 1). Inoltre il DMA controller deve
anche comunicare con le periferiche coinvolte nel trasferimento dei dati, ad esempio, per
assegnare ad ognuna una linea di richiesta di DMA e decidere la politica di arbitraggio per le
richieste simultanee al DMA. La definizione del tipo di trasferimento dei dati da fare (per
esempio all-at-once, individuale od altro) chiude questa prima fase (Figura 13.12, punto 2).
Figura 13.12. Un esempio di trasferimento dati attraverso il DMAC.
Dopo aver definito tutti questi parametri si passa alla fase di trasferimento vero e proprio,
nella quale, dopo aver supposto che il controller DMA sia stato configurato correttamente,
inizia il trasferimento dei dati:
• direttamente verso il DMAC: in questo caso il DMAC avrà almeno un registro dati
(data register) dove registrare i dati;
• attraverso il processore (attivato su interrupt);
• utilizzando periferiche in grado di trasferire direttamente il dato verso la MEM.
A questo punto avviene la richiesta del bus da parte del DMAC alla CPU, che ne consente
l’utilizzo. Il processore, in generale, privilegia le richieste che provengono dall’esterno per
l’utilizzo del bus, rispetto alle necessità che possono essere proprie e che provengono
dell’interno: tutto questo fa sì che si evitino perdite di dati quando queste richieste
provengono proprio da un controllore DMA che deve effettuare trasferimenti di I/O ad alte
13.13
velocità. Nei processori che non supportano la gestione delle richieste del bus, il DMAC può
funzionare in “bus-stealing”, cioè il processore resta bloccato, per un breve periodo, su un
accesso al bus mentre il DMAC opera un trasferimento.
La maggior parte dei più recenti DMAC comunque sono progettati in modo da poter funzionare
con diverse famiglie di processori.
A questo punto il trasferimento dei dati può avvenire o attraverso un buffer interno al
controller o direttamente dalla periferica alla memoria come gia detto (Figura 13.12, punto 3).
Una volta terminato il trasferimento, viene calcolato il nuovo indirizzo per un successivo
trasferimento e viene quindi aggiornato il contatore dei trasferimenti.
Appena il DMAC termina di trasferire un blocco, oppure si accorge di un errore nel
trasferimento dei dati, invia alla CPU un’interrupt (Figura 13.12, punto 4).
13.5.2 Modelli del DMAC
Il funzionamento del controller varia in base a due caratteristiche:
- trasferimento dei dati, che può essere singolo o doppio;
- indirizzamento, che può essere singolo o complesso.
Con il trasferimento di dati singolo il DMA utilizza l’address bus per indirizzare la locazione
coinvolta nel ciclo di bus verso la memoria (Figura 13.13(1)). Esso utilizza il segnale di
selezione della periferica (Chip Select, C/S) per selezionare la periferica e rendere
disponibile il suo bus dati; in conseguenza dell’attivazione del Chip Select può essere
necessario un encoder per selezionare un dato indirizzo (implicito) della periferica (A0, A1,
A2 in Figura 13.13(2)). Se è proprio la periferica scelta ad iniziare, il controller attiva la linea
di request.
C/S
Periferica
Periferica
A0 A1 A2
R/W
Peripheral bus
Controller DMA
Data
bus
Address
bus
Encoder
Peripheral Select dal
controller
DMA
MEMORIA
Read/Write
dal controller
DMA
Peripheral bus
(1)
(2)
Figura 13.13. (1) Struttura di un trasferimento dati singolo (2) Selezione Periferica.
Il trasferimento di dati doppio si basa sull’utilizzo di due indirizzi e due accessi per
trasferire i dati dalla periferica ( o dalla memoria) a un’altra locazione di memoria (Figura
13.14).
Esso consuma due cicli di bus e utilizza un buffer temporaneo interno al controller.
13.14
Periferica
Controller DMA
Data bus
Address
bus
MEMORIA
Figura 13.14. Struttura di un trasferimento dati doppio
Un caso particolare è l’ 1D Model, che utilizza un indirizzo base ed un contatore per definire
la sequenza di indirizzi da usare nei cicli DMA.
Esso presenta uno svantaggio: una volta che il blocco di dati è stato trasmesso, l’indirizzo e il
contatore sono resettati potendo sovrascrivere accidentalmente dati precedenti; diventa
allora necessario un’ interrupt dal DMA alla CPU ogni volta che il contatore termina il
conteggio per cambiare l’indirizzo base.
E’ comunque vantaggioso in quanto si può implementare un buffer circolare con wrap around
automatico (Figura 13.15).
Locazione iniziale di
memoria
Reset
Locazione finale
di memoria
Figura 13.15. Buffer circolare con wrap around automatico
Altro modello è il 2D Model, che viene ad esempio utilizzato per l’implementazione di
protocolli di comunicazione basati sullo scambio di pacchetti, dove si devono spezzare grosse
quantità di dati in pacchetti e aggiungere gli header. Invece di riportare l’indirizzo in base al
valore originale, viene sommato un valore di “stride” alla fine di un trasferimento di un blocco
(Figura 13.16).
Questo metodo permette al DMA di utilizzare blocchi di memoria non contigui.
Il registro contatore è diviso in due parti distinte:
- un registro per l’offset all’interno del blocco;
- un registro per contare il numero totale di blocchi di byte trasferiti.
Con il modello 2D si possono facilmente spezzare i blocchi in pacchetti pronti per
l’inserimento degli header. Successivamente può essere usato un modello 1D per inviare i dati
alla scheda di rete.
13.15
Base Address
Incremento tramite contatore
Reset Counter &
change base address
Stride
Figura 13.16. Blocchi del modello 2D.
Infine abbiamo il 3D Model, che utilizza un meccanismo a “stride” come nel modello 2D.
Inoltre in esso può essere cambiato automaticamente lo stride per creare blocchi a indirizzi
variabili. Un modello 3D può essere semplicemente simulato con un modello 2D, al quale viene
applicato un software che programma ogni volta il DMAC per cambiare lo stride.
13.5.2 Canali DMA (Blocchi di controllo)
I 3 tipi di Controllers visionati sopra presentano però molti problemi, che portano ad avere un
notevole carico di lavoro sulla CPU per la programmazione e la gestione degli input:
- ogni DMA deve essere pre-programmata con un blocco di parametri per poter
funzionare correttamente;
- ogni periferica che necessita di un trasferimento deve chiedere alla CPU di
programmare correttamente il DMA (interrupt);
-
spesso una certa periferica programma il DMAC con lo stesso set di parametri.
Questo metodo è molto utile in quanto permette di ridurre l’overhead del processo. Ad ogni
periferica viene assegnata una linea esterna di request (canale DMA) per la quale vengono
programmati una sola volta i parametri per il corrispondente tipo di trasferimento (blocchi di
controllo) (Figura 13.17). Quando una periferica richiede un trasferimento, asserisce il suo
canale DMA e inizia il trasferimento in accordo con i parametri assegnati al canale stesso. Con
questo metodo è possibile anche condividere un singolo controller con più periferiche senza
eccessivo overhead per il processore. Il numero di canali DMA presenti in un Personal
Computer è almeno di 4.
13.16
Trasferimenti operati
con richieste sul
Canale DMA 1
Trasferimenti operati
con richieste sul
Canale DMA 2
Figura 13.17. Uso dei canali DMA.
Da un’estensione della struttura dei canali DMA deriva la concatenazione (chaining). Qui i
canali sono collegati a catena in modo da generare pattern più complessi e una volta terminato
il lavoro di un canale, il controllo passa a quello successivo concatenato. Questo metodo
garantisce la possibilità di utilizzare anche schemi molto complessi di indirizzamento.
13.5.3 Concorrenza di accesso ai canali DMA
Un problema che si può verificare nel DMAC è quello di ricevere richieste multiple di
trasferimento. Da qui nasce la necessità di un arbitraggio. Esso può essere effettuato per
esempio tramite la scelta dei canali con una priorità maggiore o tramite il servizio dei canali
attraverso la politica Rund-Robin (a rotazione), dove viene creata una coda di processi in
attesa che, una volta collegatisi alla CPU per un ciclo, vengono spostati in fondo alla lista in
attesa del nuovo collegamento.
Il DMAC compete con la CPU per quanto riguarda l’utilizzo del bus. Una CPU senza cache (per
esempio la 80286 o la MC68000) utilizza dall’80% al 95% della larghezza di banda del bus e la
condivisione di esso con la DMA porta gravi ritardi come aumenti di tempo di latenza degli
interrupt e lentezza nelle prestazioni. Per quanto riguarda i dispositivi con cache, in essi
l’interferenza del DMA sulle prestazioni è molto minore.
Per consentire il giusto compromesso, molti DMA utilizzano differenti tipi di accesso al bus.
Possiamo avere così tre modelli:
- Trasferimento singolo: con esso, il collegamento del bus ritorna alla CPU alla fine di ogni
singolo trasferimento. Un difetto deriva però dal tempo impiegato per la negoziazione del bus
e per iniziare e chiudere le transizioni di DMA.
- Trasferimento a blocchi (modo cycle stealing): qui il bus è restituito alla CPU dopo il
trasferimento di un blocco (che può essere anche molto lungo). Con questo metodo il DMAC
usa efficacemente il bus.
- Trasferimento su domanda(modo burst): qui il bus viene trattenuto dal DMA per tutto il
tempo che la periferica richiede (in teoria anche per un tempo lunghissimo!). Il DMAC può
usare il bus in maniera non ottimale, lasciando ad esempio dei “buchi”; se però ciò non avviene,
molto spesso questo tipo di trasferimento è migliore di quello a blocchi.
13.5.4 INTEL 8237
E’ il più comune modello di controller DMA attualmente in uso su tutti i PC IBM compatibili.
Esso utilizza 4 canali, ognuno dei quali può effettuare operazioni di trasferimento per un
13.17
massimo di 64K caratteri e può essere programmato in modo da autoreinizializzarsi al termine
di un trasferimento.
Nell’ultimo periodo esso viene direttamente integrato sul chipset della scheda madre.
Supporta 4 differenti modalità di trasferimento:
- trasferimento singolo;
- trasferimento a blocchi;
- trasferimento a domanda;
- trasferimento a cascata, in cui abbiamo più controllers DMA collegati in cascata, che
garantiscono più canali DMA.
Trasferimento di dati: viene garantito un trasferimento da periferica a memoria e quello da
memoria a memoria, che avviene unendo due canali fra loro (solitamente non in uso nei PC).
Verify Transfer Mode: questo è una speciale modalità di trasferimento dei dati. Esso viene
usato nei PC per generare indirizzi dummy per il refresh della memoria DRAM e viene guidato
da un interrupt ogni 15μs, derivato da un canale del timer 8253.
Gestione interna della concorrenza: per gestire tutte le possibili concorrenze interne
vengono utilizzati due differenti metodi:
- schema a priorità fissa;
- schema a priorità variabile;
13.5.5 MOTOROLA MC68300
Questo tipo di tecnologia si basa su processori Motorola MC68000 e MC68020 con controller
DMA integrato sul chip stesso.
La loro architettura è caratterizzata da due canali DMA completamente programmabili e da
una velocità di trasferimento dati che varia da 12.5 Mbytes/s in dual address mode a 25 MHz
a 50.0 Mbytes/s in single address mode a 25 MHz.
Grazie al fatto di trovarsi integrato sulla CPU, esso può gestire trasferimenti fra memorie (o
periferiche) interne ed esterne. Per quanto riguarda i trasferimenti interni è possibile
specificare la quantità di banda da occupare (25, 50, 75 o 100%); per i cicli esterni ho due
differenti modalità di trasferimento: la burst (modo di trasferimento continuo) e la single.
I registri sorgente e di destinazione possono essere programmati indipendentemente per
rimanere costanti o per essere incrementati.
13.5.6 USO DELLA CPU SEPARATA CON FIRMWARE
Una CPU di questo tipo può essere utilizzata quando non è disponibile un DMAC. Essa richiede
una propria memoria e un programma che la faccia funzionare senza creare occupazioni del
bus della memoria nel momento in cui deve essere programmata.
La CPU stabilisce come deve essere effettuato ogni trasferimento DMA.
Un notevole vantaggio che deriva dall’utilizzo di questa tecnica è che la CPU può essere
programmata con software di alto livello.
Molti dei processori usati nei sistemi embedded ricadono in questa categoria.
RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] Sito web: http://www.ing.unisi.it/control
13.18
LEZIONE 14
Esempi di dispositivi di I/O: il timer e la porta seriale
14.1 TIMER INTEL 8254
Il Timer Intel-8254 è un generatore di ritardi, programmabili in maniera accurata; esso
implementa diverse funzioni:
- conteggio degli eventi;
- real time clock;
- generatore di frequenza programmabile;
- generatore di onda quadra programmabile;
- generatore di impulso rettangolare;
- moltiplicatore binario di sequenza;
- generatore di forma d’onda complessa;
- controllo di motori.
E’ costituito da tre contatori indipendenti a 16 bit, ognuno capace di generare segnali a
frequenza massima di 10 MHz . La figura 1.1 rappresenta il diagramma a blocchi del timer
8254 nella figura 14.2 si riassumono i segnali elettrici di ingresso e uscita del chip.
Fig 14.1 Diagramma a blocchi.
2
8
D7-D0
CLK2
GATE2
OUT2
/RD
CLK1
GATE1
OUT1
A1-A0
A1-A0: indirizzamento
D7-D0: scambio dati su
8 bit
/RD, /WR, /CS: Read,
Write, Chip-Select
Vcc-GND: alimentazione
CLKj: clock del timer j
GATEj: trigger
hardware o
congelamento conteggio
OUTj: forma d’onda
prodotta dal timer j
/WR
/CS
2
Vcc-GND
CLK0
GATE0
OUT0
Fig 14.2 i segnali elettrici di ingresso e uscita del chip.
14.1
Analizziamo il diagramma a blocchi (fig. 14.1):
il data bus buffer (8 bit) è utilizzato per interfacciare l’8254 con il bus di sistema; la
logica di scrittura e lettura (/RD,/WR) accetta ingressi dal bus di sistema e genera segnali di
controllo per gli altri blocchi funzionali. Un /RD basso (low) significa che la CPU sta leggendo
uno dei registri; un /WR basso indica che la CPU sta scrivendo. Il segnale /CS abilita sia /WR
che /RD, altrimenti vengono ignorati. A1 e A0 sono i segnali di indirizzamento che selezionano
uno dei tre contatori interni o il Control Word Register.
I bit A1 e A0 indirizzano 4 registri. Con A1,A0=00,01,10 si indirizzano rispettivamente CR0,
CR1, CR2 (vedi fig 14.3).
Il Control Word Register (CWR) è selezionato quando A1,A0=11. Il CWR può essere solo
scritto; si accede alle informazioni in esso contenute con il comando read back (paragrafo
14.4); inoltre il CWR determina il modo di operare del contatore, un contatore esegue delle
operazioni solo se viene programmato, quindi prima di essere utilizzato bisogna programmarlo.
A1-A0 /RD /WB
00
1
0
CR0
01
1
0
CR1
10
1
0
CR2
Counter Register 0
Scrivendo in questo registro fisso la Time Constant 0
Counter Register 1
Scrivendo in questo registro fisso la Time Constant 1
Counter Register 2
Scrivendo in questo registro fisso la Time Constant 0
11
1
0
Control Word Register
Scrivendo in questo registro fisso il funzionamento
di un dato contatore oppure do’ comandi particolari
CW
R
Fig 14.3 Visione logica del chip.
14.2 PROGRAMMAZIONE DEL TIMER
Per programmare il timer bisogna eseguire i seguenti passi:
- scrivere in CWR per selezionare uno dei contatori e il modo (vedi par. successivo);
- scrivere in CRj la costante di tempo (j=0,1,2);
- abilitare il conteggio con un segnale alto sul pin GATEj.
L’uscita OUT produrrà un segnale di uscita alla fine del (oppure durante il) conteggio a
seconda del modo prescelto.
14.3 MODI DI PROGRAMMAZIONE DEI CONTATORI
I contatori sono a 16 bit e supportano sei modi operativi:
- MODO 0: interrupt al termine del conteggio;
- MODO 1: programmable one shot;
- MODO 2: rate generator (divisore di frequenza);
- MODO 3: square wave rate generator (generatore d’onda quadra);
- MODO 4: software triggered strobe (generatore di impulso);
14.2
- MODO 5: hardware triggered strobe.
Analizziamo in dettaglio i sei modi operativi: (in tutti gli esempi si parte da un conteggio
iniziale pari a N).
14.3.1 Modo 0 (Interrupt al termine del conteggio)
Come mostrato in fig. 14.4, dopo che il CWR è stato scritto, l’uscita (OUT) è inizialmente
bassa e vi rimane per un ciclo di clock, dopo di che il contatore inizia il conteggio partendo dal
valore preimpostato. A questo punto, ad ogni ciclo di clock il contatore viene decrementato e
l’uscita (OUT) rimane bassa fino a quando il contatore raggiunge il valore 1, dopo di che
durante la transizione del contatore da 1 a 0 l’uscita (OUT) va a 1 e rimane in tale stato fino a
quando viene scritto un nuovo valore in CWR. In questa modalità, si genera un uscita alta dopo
N+1 cicli. GATE=1 abilita il conteggio, GATE=0 lo disabilita; il GATE non ha effetti sull’uscita.
CL
K
CWR,CRj,/WR
Counter
-
-
-
-
N
N-2
N-1
…
2
1
0
-
OUT
GATE
N+1
Fig 14.4 Rappresentazione del Modo 0.
14.3.2 Modo 1 (programmable one shot)
L’uscita è inizialmente alta e diventa bassa sull’impulso di clock che segue il trigger
(pulsazione one shot), e vi rimane fino a quando il contatore non raggiunge lo zero,
mantenendo tale stato fino alla pulsazione di clock che si verifica nel trigger successivo (fig
14.5).
14.3
CLK
CWR,CRj,/WR
Counter
-
-
N
-
…
N-2
N-1
2
1
0
-
OUT
GATE
N+1
Figura 14.5 Rappresentazione Modo 1
14.3.3 Modo 2 (divisore di frequenza)
Come mostrato in fig 14.6, l’uscita (OUT) è inizialmente alta quando il contatore arriva ad uno
l’uscita diventa bassa per un impulso di clock. Poi l’uscita diventa alta di nuovo, il contatore
ricarica il valore iniziale del conteggio e il processo si ripete. Il modo 2 è quindi periodico in
quanto la stessa sequenza è ripetuta in maniera indefinita. Il duty cycle della forma d’onda
d’uscita (OUT) è (N-1)/N. Se f e’ la frequenza del segnale di clock, la forma d’onda d’uscita
avra’ frequenza f/N.
CLK
CWR,CRj,/WR
Counter
-
-
-
N
N-1 N-2
…
2
1
N
N-1
…
2
OUT
Periodo = N
Fig 14.6 Rappresentazione Modo 2.
14.3.4 Modo 3 (generatore d’onda quadra)
L’uscita (OUT) rimane alta nei periodi N, N-1,……,(N+1)/2 e rimane bassa nei periodi N/2, (N1)/2, ….,2,1. Il duty cycle è ½ se N è pari , altrimenti vale ((N+1)/2)/N = ½+½N . In uscita (OUT)
abbiamo un’ onda quadra con un periodo di N cicli di clock. GATE = 1 abilita il conteggio, GATE
14.4
= 0 lo disabilita (fig 14.7). Se f e’ la frequenza del segnale di clock, la forma d’onda d’uscita
avra’ frequenza f/N.
CLK
CWR,CRj,/WR
Counter
-
-
-
N
N-1 N-2
…
2
1
N
…
N-1
2
1
OUT
Periodo = N
Fig 14.7 Rappresentazione Modo 3.
14.3.5 Modo 4 (Generatore d’impulso)
L’uscita (OUT) è inizialmente alta; diventa bassa per un impulso di clock e poi di nuovo alta.
GATE = 1 abilita il conteggio, GATE = 0 lo disabilita. Il GATE non ha effetto sull’uscita (fig.
14.8).
CL
K
CWR,CRj,/WR
Counte
r
-
-
-
N
N-1
…
N2
2
1
0
-
OU
T
GATE
N+
1
Fig 14.8 Rappresentazione Modo 4.
14.3.6 Modo 5 (Hardware triggered strobe)
Come si vede in fig. 14.9, l’uscita (OUT) è inizialmente alta e il conteggio inizia in
corrispondenza dell’impulso di gate. Quando il conteggio raggiunge lo zero, l’uscita (OUT)
rimane bassa per un impulso di clock e poi diventa nuovamente alta. I successivi impulsi di
GATE vengono ignorati.
14.5
CL
K
CWR,CRj,/WR
Counter
-
-
-
N
N-1
…
N-2
2
1
0
-
-
OUT
GATE
N+1
Figura 14.9 Rappresentazione Modo 5.
14.4 MODALITÀ DI LETTURA/SCRITTURA
I contatori hanno una precisione interna di 16 bit, ma il bus di accesso al chip è solo 8 bit, si
dovra’ perciò specificare se stò lavorando sul byte meno significativo (LSB) o sul byte più
significativo (MSB), oltre a questo è necessario programmare vari altri parametri (modo,
conteggio binario, BCD), ciò si effettua scrivendo alcune informazioni in CWR.
Un contatore è programmato per scrivere/leggere conteggi a 16 bit, nel seguente modo: un
programma non deve trasferire il controllo mentre sta scrivendo nel contatore ad un'altra
routine che a sua volta debba effettuarne la stessa operazione di scrittura; altrimenti il
contatore verrebbe caricato con un valore non corretto.
Se vogliamo scrivere in CWR i segnali da attivare sono:
A1-A0 =1 1, /CS =0, /RD =1, /WR = 0.
Mentre la programmazione di CWR avviene specificando i bit di tabella 14.1 secondo quanto
riportato nelle tabelle 14.2, 14.3, 14.4 e 14.5.
Tab. 14.1. Significato dei bit del registro CWR.
bit7
SC1
bit6
SC0
bit5
RW1
bit4
RW0
bit3
M2
bit2
M1
bit1
M0
bit0
BCD
I simboli presenti nella tabella 14.1 sono dettagliati nella tabella 14.2, 14.3, 14.4, 14.5.
Tab. 14.2. Selezione bit SC0, SC1 ai fini della programmazione.
SC0
0
0
1
1
SC1
0
1
0
1
Seleziona il contatore 0
Seleziona il contatore 1
Seleziona il contatore 2
Read Back Command (lo vedremo nella lettura)
14.6
Tab. 14.3 Selezione RW1 e RW2.
RW1
RW2
0
0
Counter Latch Command (lo vedremo nella lettura)
0
1
Read/Write soltanto il byte meno significativo
1
0
Read/Write soltanto il byte più significativo
1
1
Read /Write scrivi il byte meno significativo seguito da quello più significativo
Tab 14.4. Modi descritti nel paragrafo 14.3.
M2
0
0
X
X
1
1
M1
0
0
1
1
0
0
M0
0
1
0
1
0
1
Mode 0
Mode 1
Mode 2
Mode 3
Mode 4
Mode 5
Tab. 14.5. Bynary Coded Decimal.
0
1
Bynary Counter 16 – bit
Bynary Code Decimal (BCD)
Ogni tipo di programmazione (sequenza di bits) che segue la convenzione sopra è accettabile.
Ci sono tre metodi possibili per leggere i contatori: con operazioni di lettura, con il comando
Counter Latch Command oppure con il comando Read-Back Command:
- Il primo metodo consiste nell’attuare una semplice operazione di lettura del contatore
tramite gli input A1 e A0,
- Nel secondo metodo si utilizza il Counter Latch Command, che viene attivato facendo
accesso al CWR.
A1-A0=11, /CS = 0, /RD =1, /WR=0.
Quindi i bit di CWR, in questo caso i bit di CWR cambiano di significato come mostrato in
tabella 14.6.
Tab. 14.6. Inizializzazione di CWR per effettuare un Counter Latch Command.
bit7
SC1
-
bit6
SC0
bit5
0
bit4
0
bit3
-
bit2
-
bit1
-
bit0
-
Il terzo metodo, il Read Back Command, si attiva facendo ancora accesso al CWR
e si programma attivando i seguenti bits, come mostrato in tab. 14.7.
Tab. 14.7. Inizializzazione di CWR per effettuare un Read Back Command.
bit7
1
bit6
1
bit5
/COUNT
bit4
bit3
/STATUS CNT2
bit5: 0 = OL count of selected Counter(s);
bit4: 0 = Latch status of selected Counter(s);
bit3: 1 = Selezione del Contatore 2;
bit2: 1 = Contatore di selezione 1;
bit1: 1 = Contatore di selezione 0;
bit0: vale 0.
14.7
bit2
CNT1
bit1
CNT0
bit0
0
In fig. 14.10 viene infine mostrato un layout fisco del chip 82C54.
Fig 14.10 Layout fisico del chip 82C54.
14.5 USO DEL TIMER 8254 NEL PERSONAL COMPUTER
Inoltre, osserviamo che nei personal computer il timer viene mappato alle seguenti porte di
I/O e interrupt verificabili attraverso lo strumento di gestione delle periferiche del sistema
operativo windows.
In tabella 14.8 e’ riportato un riepilogo dell’uso dei registri del chip 8254 in un Personal
Computer.
Tab. 14.8. Inizializzazione di CWR per effettuare un Read Back Command.
Chip name
Intel 8254
Intel 8254
Intel 8254
Intel 8254
Register name
CR0
CR1
CR2
CWR
Counter Register 0
Counter Register 0
Counter Register 0
Comtrol Word Register
I/O port
0x0040
0x0041
0x0042
0x0043
Glue logic
SPR
Speaker Register
0x0061
IRQ
IRQ0
-
14.6 Esercizio
Scrivere un programma in linguaggio C per generare, su un Personal Computer, una
interruzione ogni 50ns.
SOLUZIONE
Si calcola innanzitutto la costante di tempo del timer Tc:
Tc=1/1.19MHz=840ns
Da cui si ricava N :
N+1=50·10-3/Tc Æ N=59499
14.8
Un programma in linguaggio C che esegue l’inizializzazione del time
int init_8254 (void)
{
int cr_l, cr_h;
outp(0x0043, 0x30); /* contatore 0, costante di tempo a 16 bit,
modo 0, conteggio binario */
cr_l = 59499 & 0x000000FF;
cr_h = (59499 >> 8) & 0x000000FF;
outp(0x0040, cr_l);
outp(0x0040, cr_h);
}
14.7 COMUNICAZIONE SERIALE ASINCRONA
Nella Comunicazione seriale asincrona l'informazione viene trasmessa come sequenza di
caratteri o di bit. Affinche’ il messaggio trasmesso venga correttamente interpretato, è
necessario che il ricevente abbia la capacità di individuare l'inizio e la fine delle entità logiche
che compongono il messaggio stesso. Tale aspetto della comunicazione prende il nome di
sincronizzazione. Ci sono due modalità di trasmissione:
1) trasmissione asincrona;
2) trasmissione sincrona.
La differenza fondamentale fra le due modalità è che, con la prima i clock dei terminali
comunicanti non sono sincronizzati con la seconda lo sono; inoltre nella trasmissione asincrona
la sincronizzazione di bit e di carattere (byte) è implicita nella modalità di trasmissione
stessa.
La trasmissione asincrona trova naturale impiego quando i caratteri da trasmettere sono
generati ad intervalli casuali, in questo caso nell'intervallo fra la trasmissione di un carattere
e il successivo la linea può rimanere inattiva per un tempo indeterminato, è dunque necessario
che il ricevente si risincronizzi all'istante iniziale del carattere ogni volta che ne riceve uno.
La comunicazione richiede un'unità master (o Tx) che funge da trasmettitore, e una o piu’
unità dette slave (o Rx) che agiscono da ricevitore. La trasmissione può utilizzare uno o più fili
elettrici o anche essere “senza fili” (in inglese wireless).
14.7.1 Caratteristiche principali
La velocità di comunicazione è caratterizzata dal numero di bit trasmessi per secondo o
bit-rate: se T è il tempo per trasmettere un bit, 1/T è il bit-rate.
I pacchetti vengono incapsulati in una trama (in inglese chiamata frame) prima di essere
trasmessi e sono racchiusi tra un preambolo (in inglese header) e una coda (in inglese trailer).
Il preambolo serve a marcare l'inizio della trasmissione, la coda serve per marcare la fine
della trasmissione e per trasmettere un codice di verifica (fig 14.11 in alto).
Le codifiche a correzione d'errore sono ampiamente usate per le trasmissione wireless,
notoriamente rumorosa rispetto ai cavi di rame o le fibre ottiche. Nel caso piu’ semplice si
ricorre pero’ ad alcuni bit (campo CRC, Ciclic Redundancy Code) che servono a verificare se la
trama ricevuta contiene errori o meno. Spesso un solo bit, chiamato “bit di parita’” viene
aggiunto a tale scopo. Il bit di parità viene calcolato in modo che il numero di bit a uno
14.9
(compreso il bit di parita’) nella sequenza di bit sia sempre pari (o dispari). Per esempio per
trasferire 1011010 con parità pari bisogna aggiungere uno zero alla fine (10110100), mentre
con parità dispari un uno (10110101).
1
2
3
4
5
1
5
4
3
2
3
4
5
1
trasmissione
seriale
2
3
2
4
1
5
Fig 14.11. Trama seriale (fig. in alto) e pacchettizzazione (fig. in basso).
14.7.2 Gestione delle reti
L’OSI (Open System Interconnection) è un modello utilizzato per la descrizione delle reti,
esso si fonda sulla proposta dell'International Standards Organization (ISO) come primo
passo della standardizzazione internazionale dei protocolli impiegati nei diversi strati. Si
chiama modello di riferimento ISO/OSI perche’ riguarda la connessione di sistemi aperti,
cioè sistemi che sono aperti verso la comunicazione con altri sistemi simili. Il modello OSI ha
7 strati (fig. 14.12):
14.10
applicazione
interfaccia utente
presentazione
formato dati
(tipi dei dati, trasformazione dei servizi)
sessione
comunicazione fra le applicazioni ai due
estremi (raggruppamento dati, checkpointing)
trasporto
connessione
(gestione connessione, ottimizzazione,
gestione di rete)
rete (network)
servizio end-to-end
(comunicazione dati multi-hop end-to-end)
data link
trasporto dati affidabile
(rilevazione errori e gestione singolo link
(single-hop))
fisico
caratteristiche meccaniche/elettriche
(connettori, codifica dei bit, …)
Fig 14.12. Modello OSI.
Lo strato fisico: si occupa della trasmissione di bit grezzi sul canale di comunicazione.
Lo strato data link: suddivide i dati di ingresso in data frame che vengono trasmessi
sequenzialmente; se l'operazione è andata a buon fine viene rimandato indietro un messaggio
di acknowledgement frame.
Lo strato networks: assegna un indirizzo universalmente unico a ogni unità e indica la strada
da seguire da un punto all’altro della rete.
Lo strato trasporto: ha il compito di preparare i dati da trasmettere. Fra le altre funzioni,
questo strato gestisce il controllo di flusso e la correzione degli errori e ripartisce i dati dalle
applicazioni in segmenti di dimensioni appropiate agli strati inferiori.
Lo strato sessione: permette agli utenti di computer diversi di stabilire fra loro una
sessione.
Lo strato presentazione: si occupa della sintassi e del formato dell'informazione trasmessa.
Lo strato applicazione: comprende una varietà di protocolli comunemente richiesti
dall'utente.
Un protocollo applicativo largamente usato è HTTP che è la base del world wide web.
14.7.3 Standard Ethernet
Ethernet e’ uno standard ampiamente utilizzato nelle reti locali (Local Area Netwrok o LAN)
ed e’ stato proposto agli inizi degli anni 70. Altri tipi reti standardizzate da IEEE si trovano
sotto il nome di “IEEE 802”. Alcune parti di questo standard sono:
1) 802.3 (Ethernet) velocità di trasmissione 10 Mb/s;
2) 802.11 (LAN wireless);
3) 802.15 (Bluetooth);
14.11
4) 802.16 (MAN wireless);
5) 802.3u (Fast Ethernet) velocità di trasmissione 100MB/s;
6) 802.3ab (Gigabit Ethernet) velocità di trasmissione 1 Gb/s.
Ad es., Ethernet (IEEE 802.3) prevede di utilizzare una rete a bus (fig. 14.13): ad ogni istante
può trasmettere al più una sola macchina (nodo), tutte le altre macchine devono astenersi dal
trasmettere. Per stabilire il nodo che trasmette si usa un protocollo di accesso, chiamato
CSMA/CD, spiegato nel paragrafo successivo. La trama Ethernet ha il formato di fig. 14.14.
PE 1
PE 2
PE 3
PE 4
Figura 14.13. Reti seriali a Bus.
header
address
data
ECC
Figura 14.14. Formato della trama su bus seriali tipo Ethernet.
14.7.4 CSMA/CD Carrier Sense Multiple Access with Collision Detection
Ogni stazione prova a trasmettere sul mezzo comune e controlla che non ci siano collisioni. Per
garantire che nessuna stazione trasmetta se il canale risulta occupato, ogni nodo annnulla la
propria trasmissione in caso di collisione: se due stazioni dopo avere controllato il canale
iniziano a trasmettere contemporaneamente, entrambe rilevano la collisione quasi
immediatamente e invece di completare la trasmissione dei rispetivi frame ormai danneggiati,
le stazioni interrompono bruscamente la trasmissione. La terminazione rapida dellla
trasmissione dei frame danneggiati consente di risparmiare tempo e banda del canale; tale
protocollo di accesso al mezzo si chiama CSMA/CD.
Nello specifico il modo di trasmissione è il seguente:
1) un nodo ascolta prima di parlare (Carrier Sense);
2) se il mezzo è libero si attende un tempo “Defer Time”;
3) se ci sono collisioni (Multiple Access) l'energia sul mezzo aumenta;
4) un nodo cerca di rilevare collisioni durante la trasmissione (Collision Detect);
5) se un nodo rileva una collisione, la trasmissione viene ripetuta dopo R*Δt per altri 15
tentativi;
6) dopo 16 tentativi l'errore viene segnalato ai livelli superiori ;
Δt è fisso mentre R viene ricalcolato secondo un algoritmo detto di back-off, illustrato nel
paragrafo successivo.
14.7.4.1 Algoritmo di back-off per il calcolo di R
Dopo una collisione il tempo è suddiviso in intervalli discreti la cui lunghezza è uguale al tempo
di propagazione di andata e ritorno nel caso peggiore sul mezzo di trasmissione. Dopo la prima
collisione, ogni stazione aspetta 0 o 1 intervalli temporali prima di ritentare; se due stazioni
collidono e ognuna sceglie lo stesso numero casuale, la collisione si ripeterà. Dopo la seconda
14.12
collisione ogni stazione sceglie 0 ,1, 2 o 3 a caso e rimane in attesa per quel numero di
intervalli temporali. Se abbiamo una terza collisione la volta successiva il numero di intervalli
di attesa è scelto a caso fra 0 e 23-1. In generale, dopo k collisioni viene scelto un numero
casuale R compreso fra zero e 2k-1 e si salta quel numero di intervalli; dopo 10 collisioni il
tetto massimo dell'intervallo di scelta rimane bloccato a 1023 intervalli (fig. 14.15). Dopo 16
collisioni il chip di controllo comunica al computer un errore.
tempo
di attesa
per
ritrasmissioni
numero di tentativi
Figura 14.15 Grafico tempo di attesa ritrassimssioni
14.7.5 Dettaglio sul formato della trama (pacchetto) Ethernet
In dettaglio, il pacchetto Ethernet contiene i seguenti campi (fig. 14.16):
• Preamble: 31 coppie di bit che vale 10;
• Start frame: 1 coppia di bit che vale sempre 11; (preamble+start_frame sono 8 byte);
• Source address (indirizzo Ethernet del nodo trasmittente): 6 byte;
• Destination address (indirizzo Ethernet del nodo ricevente): 6 byte;
• Lenght (lunghezza complessiva del pacchetto): 2 byte;
• Data payload: sono i dati veri e propri; hanno una lunghezza massima di 1500 byte;
• Padding: utilizzato per riempire il frame quando la parte dei dati è minore di 46 bytes;
• Checksum (CRC): permette di rilevare eventuali errori (non la loro correzione).
preamble
8 byte
start
frame
source
adrs
dest
adrs
length
6 byte
6 byte
2 byte
data
payload
padding CRC
46-1500 byte
4 byte
Figura 14.16. Dettaglio Pacchetto Ethernet.
I bit di ordine più elevato nel campo destination address sono zero per gli indirizzi ordinari,
uno per gli indirizzi di gruppo; questi ultimi permettono a molte stazioni di rimanere in ascolto
di un singolo indirizzo (multicast), invece l’indirizzo composto da tutti bit 1 è riservato per la
trasmissione broadcast; un frame broadcast è inviato a tutte le stazioni su ethernet invece
quello multicast è inviato ad un gruppo selezionato di stazioni su ethernet.
Ethernet richiede che i frame validi siano lunghi almeno 64 byte (dal source address al CRC
incluso), questo perche’ il ricetrasmettitore quando rileva una collisione tronca il frame
corrente, e ciò significa che sul cavo compaiono continuamente bit sparsi e pezzi di frame.
14.13
Imporre una lunghezza minima serve per impedire ad una stazione di completare la
trasmissione di un frame breve prima che il primo bit abbia raggiunto la fine del cavo, dove
potrebbe collidere con un altro frame.
Lo standard Ethernet non richiede l’installazione di alcun software (a parte i driver) e non
dipende da tabelle di configurazione difficili da gestire.
14.7.6 Prestazioni della rete Ethernet
La qualità del servizio (QOS o Quality of Service) tende a decrescere in maniera non lineare
ad alti livelli di carico. Una conseguenza dell’algoritmo di back-off è che non è possibile
stabilire il tempo massimo entro cui verrà trasmessa con successo una trama. Questo significa
che:
- non si possono garantire deadline real-time.
- si possono ottenere livelli molto buoni di servizi purchè si riesca a mantenere il carico
ad un livello appropriato.
14.8 PORTE SERIALI
Le porte seriali sono un tipo periferica per il trasferimento dei dati tra due dispositivi. Il chip
che implementa la porta seriale è tipicamente chiamato UART (Universal Asynchronous
Receiver-Transmitter).
Le UART dei PC hanno una lunga storia; i chip originali 8250 e 16450 non avevano una
bufferizzazione adeguata per memorizzare i caratteri in arrivo; questo non rappresentava un
problema con un sistema operativo tipo DOS, che non doveva gestire più processi, ma per
sistemi operativi che non sono semplici “caricatori di programmi”, questo è diventato un
problema per velocità superiori ai 2400 baud, poiché la UART spesso sovrascrive i dati in
arrivo prima che il kernel del sistema operativo abbia la possibilità di estrarre i dati dal
buffer. Per risolvere questo problema fu introdotta la UART 16550, che includeva un buffer
FIFO (first-in, first-out); sfortunatamente questo chip conteneva un errore che rendeva il
buffer FIFO inutile. Fu percio’ introdotta la UART 16550A in cui si è parzialmente risolto
tale problema.
L'uscita dei dati avviene attraverso alcuni connettori esterni presenti nei computer, (come si
può verificare dalla descrizione dei dati tecnici del PC), con il nome di porta seriale o porta
RS-232.
A tale porta e’ possibile collegare diversi tipi di dispositivi accessori, con i quali e’ possibile
effettuare uno scambio di dati in entrambe le direzioni. L'utilizzazione principale di questa
porta avviene nel caso di collegamenti con periferiche attive, come mouse, modem, scanner,
penne ottiche.
Il traffico dei dati attraverso la porta seriale può avvenire a diverse velocità. In particolare,
poiché spesso lo scambio dei dati avviene a distanza di qualche metro, è necessario prevedere
anche lo scambio di alcuni segnali di controllo che permettano di verificare che i bit ricevuti
siano uguali a quelli trasmessi. L'interferenza di macchinari elettrici, come monitor, computer
o stampanti, potrebbe danneggiare il flusso dei bit durante il loro viaggio lungo il cavo.
La velocità di trasmissione, insieme alla struttura dei pacchetti di bit inviati ed al tipo di
controllo effettuato, determinano il protocollo usato nella trasmissione dei dati. I diversi
protocolli sono stabiliti internazionalmente.
La porta seriale consente anche il collegamento fra computer, sia direttamente tramite un
cavo, che a lunghe distanze, tramite modem. In entrambi i casi è molto importante stabilire in
14.14
precedenza il protocollo di trasmissione da entrambe le parti. La banda di trasmissione che si
può ottenere è limitata dalla banda telefonica, in analogico si può arrivare fino a 56 k e in
ISDN fino a 128 k.
Fisicamente la porta seriale è costituita da 2 registri a scorrimento, uno per trasmettere Tx
e uno per ricevere Rx. L’uscita del Tx è collegata all’ingresso del Rx; quando Tx è vuoto o Rx è
pieno viene generato un interrupt (14.17).
Transmitter (Tx)
n
n+1
n+2
n+3
n+4
01 23
01 2
01
0
n+5
4
3
2
1
0
n+6
5
4
3
2
1
0
n+7
6
5
4
3
2
1
0
n+8
Receiver (RX)
7
6
5
4
3
2
1
0
n
7
6
5
4
3
2
1
0
n+1
7
6
5
4
3
2
1
n+2
7
6
5
4
3
2
n+3
7
6
5
4
3
n+4
7
67
567
4567
n+5
n+6
n+7
n+8
Interrupt: Rx full
Interrupt: Tx empty
Fig 14.17. Registro a scorrimento della porta seriale.
Per evitare di perdere i dati viene usato un buffer con struttura FIFO che può essere
implementata come un array circolare (buffer) dotato di due puntatori, uno relativo al
prossimo carattere da scrivere (Bufln), ed uno per il prossimo carattere da leggere (BuffOut)
(fig.14.18).
Device 1
Device 2
Rx Register
FIFO buffer
FIFO buffer
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7
7 6 5 4 3 2 1 0
Rx Data
Rx Data
Tx Data
Tx Data
0 1 2 3 4 5 6 7
FIFO buffer
7 6 5 4 3 2 1 0
7 6 5 4 3 2 1 0
7 6 5 4 3 2 1 0
FIFO buffer
Tx Register
Fig 14.18. Registro a scorrimento della porta seriale.
14.15
Quando uno dei puntatori raggiunge la fine del buffer, viene messo nuovamente a puntare al
primo elemento della coda. Quando i due puntatori coincidono il buffer è vuoto. Quando il
puntatore di scrittura raggiunge quello di lettura, il buffer è pieno e si perdono i dati.
14.8.1 Trasmissione dati
La trasmissione di un byte avviene secondo il seguente schema:
Lato Tx:
• Il dato scritto nel buffer FIFO viene inviato al Tx che genera un
interrupt non appena il byte è stato trasmesso.
Lato Rx:
• il buffer riceve il byte, bit per bit;
• l’intero byte, viene trasferito nel Rx-register (fig. 14.18);
• viene generato un interrupt ed il byte viene letto.
Il flusso dei dati viene controllato per evitare che il trasmettitore (Tx) invii più dati di quelli
che il ricevitore (Rx) può ricevere e questo viene fatto in due modi:
• Controllo di flusso ad Hardware:
- il chip UART del Rx individua un potenziale intasamento e attiva una linea apposita
per avvertire il Tx;
- Tx interrompe la trasmissione;
- appena Rx può ricevere i dati, la linea viene disattivata.
• Controllo di flusso a Software:
- Il Rx invia caratteri speciali di controllo (XON e XOFF) al Tx;
- XOFF interrompe una trasmissione;
- XON riprende la trasmissione.
14.8.2 I Connettori
Secondo lo standard EIA-RS232C i connettori più comuni sono due :
- DB25: connettore a 25 pin
- DB9: connettore a 9 pin
14.16
Tab. 14.9. Significato dei segnali dei connettori DB9 e mappatura su DB25.
DB-25
1
2
Sigla
GND
TXD
Segnale
Chassis Ground
Trasmit Data
3
RXD
Received Data
4
RTS
Request to Send
5
CTS
Clear to Send
6
DSR
Data Set Ready
7
8
GND
DCD
Signal Ground
Data Carrier Detect
20
DTR
Data Terminal Ready
22
RI
Ring Indicator
Significato
Linea di trasmissione dei dati connessa
alla linea RXD del Rx
Linea di ricezione del dato, connessa
alla linea del TXD del Tx
Utilizzata in congiunzione con CTS
segnala che Tx è pronto ad inviare dati
Utilizzata in congiunzione con RTS
segnala che Rx è pronto a ricevere i
dati
Indica che il data-set (modem) è
pronto a stabilire la comunicazione
Viene usato quando è connesso un
modem; indica che il data set (modem)
ha rivelato la portante sulla linea
telefonica
Indica che il data –terminal UART è
pronto a stabilire la comunicazione
Viene usato quando è connesso un
modem e indica che ci sono degli squilli
in arrivo sulla linea telefonica
DB-9
Not Used
3
2
7
8
6
5
1
4
9
Tx
Rx
DTR
DSR
RT S
DCE
CT S
DTE
DCD
GND
Figura 14.19. Schema DCE/DTE.
Nella terminologia dello standard RS-232, DCE (Data Carrier Equipment) e ad es. è un modem,
mentre il DTE (Data Terminal Equipment) e’ ad es. un terminale, un computer o un video.
Inizialmente questi segnali furono pensati per connettere fra loro i modem. Per la connessione
fra due DTE (es. stampanti o altre periferiche) si utilizzavano dei cavi diretti oppure cavi
null-modem.
14.8.3 UART
Sono circuiti di interfaccia per gestire la comunicazione Asincrona, e sono noti come Universal
Asynchronous Receiver and Transmitter (UART).
14.17
L’attributo “universale” sta a significare che il dispositivo è programmabile e dunque l’utente
può specificare le caratteristiche operative richieste inviando una opportuna parola di
controllo.
14.8.3.1 UART 16550A
L’16550A è un’interfaccia seriale asincrona di tipo UART (il chip e’ prodotto da molti
costruttori, come ad es. la National Semiconductor) che permette di generare internamente il
bit-rate sulla base di un clock esterno. Fra le caratteristiche principali consente il
riconoscimento di errori di trasmissione. La visione elettrica del chip è:
3
8
SIN
A2-A0
SOUT
D7-D0
/RTS
/CTS
/DTR
/DSR
/DCD
/RI
/RD
/WR
/CS
INTR
2
CLK
Vcc-GND
Figura 14.20. Schema dell’16550A.
I pin hanno il seguente significato
• /RD: Read;
• /WR:Write;
• A0-A2: Address Bits;
• D0-D7: Data Bus;
• INTR: Interrupt Request;
• SIN: Serial Input;
• SOUT: Serial Output;
• /RTS: Request To Send;
• /CTS: Clear To Send;
• /DCD: Data Carrier Detect;
• /DTR: Data Terminal Ready;
• /DSR: Data Set Ready;
• /RI: Ring Indicator.
I registri interni del chip sono i seguenti:
• Receiver Buffer Register (RBR);
• Transmitter Holding Register (THR);
• Divisor Latch Registers (DLR, parte piu’ significativa DLM, parte meno significativa DLL);
• Interrupt Enable Register (IER);
• Interrupt Identification Register (IIR);
• Line Control Register (LCR);
• Modem Control Register (MCR);
• Line Status Register (LSR);
• Modem Status Register (MSR).
14.18
e sono identificabili secondo il seguente schema di tabella 14.10.
Tab. 14.10. Indirizzi dei Registri.
NOME REGISTRI
RBR
THR
DLL
N° BIT
8
8
8
A2
0
0
0
A1
0
0
0
A0
0
0
0
DLAB
0
0
1
DLM
8
0
0
1
1
IER
8
0
0
1
0
IIR
LCR
8
8
0
0
1
1
0
1
-
MCR
8
1
0
0
-
LSR
MSR
8
8
1
1
0
1
1
0
-
ACCESSO
LETTURA
SCRITTURA
LETTURA E
SCRITTURA
LETTURA E
SCRITTURA
LETTURA E
SCRITTURA
LETTURA
LETTURA E
SCRITTURA
LETTURA E
SCRITTURA
LETTURA
LETTURA
Per stabilire il bit-rate occorre scrivere una costante C all’interno dei registro DLR
(DLL+DLM), procedendo come segue:
1) Si mette a 1 il bit DLAB (il bit denominato “DLAB” (Divisor Latch Access Bit) è un bit
di indirizzo aggiuntivo implementato nel bit 7 del registro LCR. Esso è normalmente
tenuto a 0, tranne per la programmazione del Divisor Latch Register)
2) Detta FCK la frequenza del clock esterno e BR il bit-rate desiderato si definisce la
costante C come:
FCK
C=
16 * BR
LCR - Line Control Register
Definisce il formato del frame (sia in trasmissione che in ricezione).
DLAB
BE
SP
PS
PE
STOP
L2
L1
• L2, L1 (Number of Data Bits) definiscono il numero di bit di dato (bit per carattere): L2= 0
L1 =0: 5 bit, L2= 0 L1 =1: 6 bit, L2= 1 L1 =0: 7 bit, L2= 1 L1 =1: 8 bit.
• STOP (Number of Stop Bits) Numero di bit di stop: STOP=0: 1 bit di stop,STOP=1: 1.5 bit di
stop (se il carattere è di 5 bit), 2 bit di stop (se il carattere è di 6, 7 o 8 bit).
• PE (Parity Enable)= 1: presenza di un bit di parità. Se PE = 1 i bit SP (Sticky Parity) e PS
(Parity Select) assumono il seguente significato:
- SP=0 PS=0: bit di parità a 1 se nel dato c'è un numero dispari di bit a 1,
- SP=0 PS=1: bit di parità a 1 se nel dato c'è un numero pari di bit a 1,
- SP=1 PS=0: bit di parità a 1 se nel dato c'è un numero dispari di bit a 0,
- SP=1 PS=1: bit di parità a 1 se nel dato c'è un numero pari di bit a 0.
• BE (Break Enable) = 1: trasmissione di un segnale di break: il piedino SOUT si porta a 0 e
rimane in questo stato fino a che BE non viene posto a 0.
• DLAB (Divisor Latch Access Bit). DLAB = 1 per l’accesso ai registri DLR.
14.19
LSR - Line Status Register
È accessibile esclusivamente in lettura. Contiene le informazioni relative allo stato
dell'interfaccia.
-
-
EO
B
FE
PE
OE
FI
Dove:
• FI (Full Input) = 1: un nuovo carattere è stato ricevuto dall'interfaccia ed è disponibile in
RBR. Posto a 0 quando il processore legge RBR;
• OE (Overrun Error) = 1: errore di Overrun;
• PE (Parity Error) = 1: errore di Parità;
• FE (Frame Error) = 1: errore di Frame;
• B (Break) = 1: ricezione di un break, stato in cui la linea di dato è tenuta a 0 per un tempo
superiore al tempo necessario per trasmettere una parola (incluso il tempo di
trasmissione del bit di start, stop e parità);
• EO (Empty Output) = 1: il dato contenuto nel registro THR è stato trasmesso. Posto
a 0 quando il processore scrive in THR.
FCR - FIFO Control Register
1Æ abilita FIFO
1Æ Rx FIFO reset
1Æ Tx FIFO reset
00Æ genera interrupt quando c’e’ un dato nella FIFO,
01Æ genera interrupt quando ci sono 4 dati nella FIFO,
10Æ genera interrupt quando ci sono 8 dati nella FIFO,
11Æ genera interrupt quando ci sono 14 dati nella FIFO
MCR - Modem Control Register
Attraverso il registro MCR il programmatore può forzare lo stato dei corrispendenti due
piedini di uscita del chip seriale (bit 0 e 1). Il bit MIE e’ il bit per l’abilitazione generale degli
interrupt. Per gli altri bit si rimanda ai data-sheet dei costruttori.
-
-
-
-
MIE
-
/RTS
/DTR
MSR - Modem Status Register
Il registro MSR permette di avere informazioni sullo stato e sulle variazioni dei piedini di
input DCD, RI, DSR e CTS.
DCD
RI
DSR
CTS
DDCD
DRI
DDSR
DCTS
I bit 0-3 se ad 1 indicano che lo stato del rispettivo segnale è cambiato nell'intervallo di
tempo che va dalla precedente lettura all'attuale lettura. I bit 4-7 riflettono lo stato del
rispettivo segnale: 1 indica che il piedino ha assunto il valore 0.
14.20
IER - Interrupt Enable Register
Il registro IER permette di abilitare le generazione di richieste di interruzioni su determinati
eventi.
-
-
-
-
SINP
ERBK
TBE
RDR
• RDR (Received Data Ready) = 1: abilitazione ad effettuare una richiesta di interruzione
quando un byte è pronto in RBR;
• TBE (Transmitter Buffer Empty) = 1: abilitazione ad effettuare una richiesta di
interruzione quando THR è vuoto;
• ERBK (Error & Break) = 1: abilitazione ad effettuare una richiesta di interruzione quando
viene rilevato un errore o un segnale di break;
• SINP (Serial Input) = 1: abilitazione ad effettuare una richiesta di interruzione quando uno
dei segnali di input del protocollo RS-232 (CTS, DSR, DCD, RI) cambia stato;
• I bit 4-7 sono sempre riservati.
IIR - Interrupt Identification Register
Il registro IIR è accessibile esclusivamente in lettura e permette di identificare lo stato
delle richieste di interruzioni.
FEI1
FEI0
-
-
FT
ID1
ID0
PND
• PND (Pending Bit) = 1: nessun interrupt è pendente; PND = 0 interrupt pendente;
• ID1, ID0 (Identify Bits): bit di identificazione delle richieste di interruzione.
ID1 = 0 ID0 = 0: variazione dei bit 0-3 di MSR;
ID1 = 0 ID0 = 1: THR vuoto (dato trasmesso);
ID1 = 1 ID0 = 0: RBR pieno (dato ricevuto);
ID1 = 1 ID0 = 1: errore in ricezione o break.
• FT (FIFO Timeout): interrupt per timeout di un interrupt in modalita’ FIFO;
• FE1-FE0 (FIFO Enable Indicator): valgono 11 quando FCR-bit0=1.
Esempio di programma che utilizza la porta seriale
Per provare il programma serialtest.c (riportato nella pagina successiva):
1) utilizzare due workstation che abbiano la seconda porta seriale collegata con cavo
null-modem;
2) fare il boot sotto linux e entrare col proprio account linux;
3) copiare il programma serialtest.c dalla directory /opt/serialtest;
4) compilare il programma avendo cura di modificare la porta usata da ttyS0 a ttyS1
all'interno del programma:
cc serialtest.c
5) su uno dei due PC lanciare:
./a.out -e
6) sull'altro PC lanciare:
./a.out
14.21
#include <sys/types.h>
#include <sys/stat.h> /* open */
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h> /* stdin, stdout, stderr */
#include <sys/ioctl.h>
#include <termios.h>
#include <stdlib.h>
#include <sys/time.h> /* timeval */
#include <stdlib.h>
#include <string.h> /* memset used by FD_ZERO */
#define DEFAULT_DEV "/dev/ttyS0"
#define DEFAULT_SPEED 115200
Else
{
printf("Running as echo server.\n");
while (!error) {
len = 0;
/* wait for 127 chars */
while (len < 127)
{
len += read(serfd, rxdata+len, 127);
printf("Got [%d/127]...\r", len);
fflush(stdout);
}
/* now compare the received data */
printf("-%d- Checking RX data... ", c++);
if (memcmp(txdata, rxdata, 127) == 0)
{
printf("OK...echoing.\n");
write(serfd, rxdata, 127);
}
else
{
printf("ERROR\n");
error = 1;
}
}
}
printf("Not OK !\n");
for (i=0;i<127;i++)
printf("%c", txdata[i]);
printf("\n");
int translate_speed(int spd);
void fd_setup(int fd, int speed, int flow);
int main(int argc, char **argv)
{
int serfd, i, c=0, error=0, len, do_echo = 0;
char txdata[127], rxdata[256];
if (argc > 2) {
printf("wrong syntax ! ./sertest [-e]");
exit(1);
}
if (argc == 2) {
if ((strcmp("-e", argv[1]))==0) do_echo=1;
else printf("wrong syntax ! ./sertest [-e]\n");
}
exit(1);
}
printf("Opening %s\n", DEFAULT_DEV);
serfd = open(DEFAULT_DEV, O_RDWR);
int translate_speed(int spd)
{
int speed_c = 0;
switch(spd) {
case 9600: speed_c = B9600; break;
case 19200: speed_c = B19200; break;
case 38400: speed_c = B38400; break;
case 57600: speed_c = B57600; break;
case 115200: speed_c = B115200; break;
case 230400: speed_c = B230400; break;
case 460800: speed_c = B460800; break;
default: printf(
"Bad baudrate %d is not valid.\n", spd); break;
}
return speed_c;
}
if (serfd < 0) exit(1);
fd_setup(serfd, DEFAULT_SPEED, 1);
for (i=0;i<127;i++) txdata[i]=i+64;
if (!do_echo)
{
while (!error){
len = 0;
i=0;
printf("-%d- Sending data...", c++);
write(serfd, txdata, 127);
printf("done.\n");
/* wait for 127 chars */
while (len < 127)
{
len += read(serfd, rxdata+len, 127);
printf("Got [%d/127]...\r", len);
fflush(stdout);
}
void fd_setup(int fd, int speed, int flow)
{
struct termios t;
if (fd < 0) { perror("fd_setup"); exit(1); }
/* now compare the received data */
printf("Got response, comparing... ");
if (memcmp(rxdata, rxdata, 127) == 0)
printf("OK.\n");
else
error = 1;
}
if (tcgetattr(fd, &t) < 0)
{ perror("tcgetattr"); exit(1); }
cfmakeraw(&t);
t.c_cflag &= ~CBAUD;
t.c_cflag |= translate_speed(speed) | CS8 | CLOCAL;
t.c_oflag = 0; /* turn off output processing */
t.c_lflag = 0; /* no local modes */
if (flow) t.c_cflag |= CRTSCTS;
else t.c_cflag &= ~CRTSCTS;
if (tcsetattr(fd, TCSANOW, &t) < 0)
{ perror("fd_setup : tcsetattr"); exit(1); }
}
return;
}
14.22
14.9 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[3] Andrew S. Tanenbaum “Modern Operating Systems”, Second Edition, 2001.
[4] Intel 8254 PROGRAMMABLE INTERVAL TIMER.
[5] M. Rebaudengo - M. Sonza Reorda Politecnico di Torino Dip. di Automatica e
Informatica
[6] Byte runner technology Link: http://www.byterunner.com/16550.html
[7] Seas education Link: www.seas.ucla.edu /classes
[8] Pklab, società informatica e robotica presso http: //www.pklab.net
[9] Exar data comunication application note http:// www.exar.com/ products/
[10] Ing . Marco Carli corso di reti di telecomunicazioni - http:// www.comlab.ele.uniroma3.it
/Sistemi1.
[11] William Stallings, 'Trasmissione dati e reti di computer,' Jackson 2000
[12] Sistemi di elaborazione università di Ferrara presso il sito http: //www.ing.unife.it / elettr/ SE/
MatDid.htm
[13] Datasheets Intersil presso il sito http: //www.intersil.com /cda /home /
[14] Dontronics presso il sito www.dontronics.com
[15]Università degli studi di Palermo corso di reti di calcolatori tenuto dal Dott. Giuseppe Lo Re slides
disponibili presso il sito http: //www.pa.icar.cnr.it /~lore/RC/
[16] Internet e Reti di Calcolatori", James F. Kurose, Keith W. Ross, MacGraw-Hill seconda edizione
[17] IEEE 802.3 CSMA/CD (ETHERNET) disponibile presso il sito http://www.ieee802.org/3/
[18] Università degli studi di Parma corso di reti di calcolatori corso dal Dott. Luca Veltri materiale
didattico disponibile presso il sito http :// www.tlc.unipr.it /veltri/reti-tlc-a /#slides
[19] Andrew S. Tanenbaum “Reti di calcolatori” 4° edizione.
14.23
LEZIONE 15
Hard disk
15.1 DESCRIZIONE GENERALE
Un hard disk (o disco magnetico) consiste di un insieme di piatti che ruotano attorno ad un
asse ad una velocità compresa tra 3600 e 7200 giri al minuto (oggi anche fino ad 15000). I
piatti, in metallo (recentemente in vetro2) con diametro che può variare da un pollice (2.5cm)
ad otto (20cm), sono coperti su ambedue le facce da materiale magnetico in grado di
memorizzare informazioni. La scrittura su disco è quindi non volatile, ossia i dati vengono
conservati anche quando viene tolta l’alimentazione.
Per leggere e scrivere informazioni su un hard disk al di sopra di ciascuna superficie è
posizionato un braccio mobile contenente una piccola bobina elettromagnetica chiamata
testina (head) di lettura/scrittura. Ciascuna superficie del disco è divisa in cerchi concentrici
chiamati tracce, tipicamente ogni superficie contiene dalle 1000 a 5000 tracce (track). Una
traccia è a sua volta divisa in settori che contengono le informazioni, ognuna di esse può
contenere da 64 a 200 settori, ed il settore è l’unità minima di lettura e scrittura (vedi fig.
15.1). Se sono disponibili piu’ superfici di memorizzazione, si puo’ immaginare che queste
costituiscano un cilindro (cylinder), tale termine viene usato in questo contesto come sinonimo
di traccia. Inoltre nell’accesso a superfici di memorizzazione distinte si fa riferimento alla
testina scrivente.
Piatti
Tracce
Settori
Piatto
Traccia
Figura 15.1
I dischi si suddividono in piatti, tracce e settori.
L’uso di componenti meccanici fa si che i tempi di accesso per i dischi magnetici siano molto
più alti che per le DRAM; per i dischi variano da 5 a 20 millisecondi, per le DRAM si hanno
tempi di accesso compresi tra 5 e 100 nanosecondi, il che rende le DRAM circa 100000 volte
più veloci, tuttavia a parità di capacità di immagazzinamento il costo per megabyte di un disco
è 50 volte minore di quello di una DRAM. Nella figura 15.2 e’ riportato l’andamento dei costi
del disco per MByte.
2
Il substrato in vetro presenta alcuni vantaggi, infatti si ha una:
− maggiore uniformità della pellicola magnetica superficiale, che produce, quindi, un miglioramento
dell’affidabilità del disco;
− riduzione dei difetti della superficie con conseguente riduzione degli errori di lettura/scrittura;
− minore distanza tra testina e supporto;
− maggiore rigidità;
− maggiore resistenza ad urti e danneggiamenti.
15.1
2057.61 $
0.001 $
1965
1975
1985
1995
2005
Figura 15.2 Andamento costo per MByte
15.1.1 Meccanismi di lettura e scrittura magnetica
Il meccanismo di scrittura si basa sul fatto che l’elettricità, fluendo attraverso la bobina
produce un campo magnetico; la testina di scrittura pertanto riceve impulsi, e nella superficie
sottostante vengono registrati schemi magnetici differenti dipendenti dalla corrente
(positiva o negativa).
La testina di scrittura è composta da materiale facilmente magnetizzabile ed ha la forma di
un ferro di cavallo attorno al quale sono avvolti pochi giri di conduttore filiforme (vedi fig.
15.3). Una corrente elettrica nel filo induce, attraverso gli estremi del ferro di cavallo, un
campo magnetico che magnetizza una piccola area del mezzo di registrazione; logicamente
l’inversione della direzione della corrente inverte l’orientamento del campo magnetico sul
supporto di registrazione.
Il meccanismo di lettura è basato sul fatto che un campo magnetico in movimento relativo
rispetto ad una bobina produce in essa una corrente elettrica. La porzione di superficie del
disco passante sotto la testina genera una corrente della stessa polarità di quella che
precedentemente era stata registrata.
Si può utilizzare la stessa testina e per la scrittura e per la lettura poiché hanno la stessa
struttura. Le testine singole sono utilizzate dai floppy disk e dai vecchi sistemi a disco rigido.
Tuttavia i sistemi a disco rigido odierni richiedono una testina di lettura distinta
comprendente un sensore magnetoresistivo (MR) parzialmente schermato.
Il materiale magnetoresistivo presenta una resistenza elettrica che dipende dalla
magnetizzazione del mezzo che si trova sotto di esso; tramite il passaggio della corrente
attraverso il sensore vengono rilevati, come segnali di tensione, i cambiamenti di resistenza.
Questo metodo garantisce maggior densità di memorizzazione e velocità operative piu’
elevate.
15.2
Corrente
di lettura
Corrente di
scrittura
Schermatura
Ampiezza
della traccia
Elemento
di scrittura
a induzione
Magnetizzazione
N
S
N
S
N
S
Sensore
MR
Supporto di
registrazione
N
S
N
S
N
S
N
S
Figura 15.3
Testina di scrittura induttiva e lettura magnetoresistiva.
15.1.2 Organizzazione e formattazione dei dati
Come precedentemente accennato la testina è un dispositivo relativamente piccolo in grado di
leggere e di scrivere su una porzione del disco rotante; ed è ciò che è all’origine della
disposizione fisica dei dati in anelli concentrici chiamati tracce. Esse sono tanto larghe quanto
la testina. Come detto, si usa il termine cilindro per far riferimento a tutte le tracce che in un
certo istante si trovano al di sotto delle testine sulle varie superfici.
Fisicamente, tracce adiacenti sono separate da spazi per prevenire, o quanto meno
minimizzare, gli errori dovuti al disallineamento della testina o all’interferenza tra i campi
magnetici.
Il trasferimento dati avviene per settori; che sono di lunghezza fissa o variabile (la maggior
parte dei sistemi odierni usa tipicamente settori a lunghezza fissa pari a 512 byte). Anche in
questo caso per ridurre eventuali imprecisioni i settori adiacenti sono separati da spazi vuoti
(vedi fig. 15.4).
Sector
S3
Intersector Gap
S4
Track
S3
S4
S5
S2
S2
S5
S1
S6
Intratrack Gap
S6
S1
S8
S8
S7
S7
Figura 15.4 Struttura del disco
I bit vicini al centro del disco ruotano più lentamente di quelli più esterni. E’ possibile
compensare la variazione di velocità, incrementando lo spazio tra i bit registrati sul disco man
15.3
mano che ci si allontana dal centro del disco, in modo tale che la testina possa leggere tutti i
bit alla stessa velocità (tecnica a velocita’ angolare costante).
Questa tecnica da’ come vantaggio che i singoli blocchi di dati possono essere indirizzati per
traccia e settore. Per spostare la testina verso un indirizzo specifico è sufficiente un suo
movimento verso la traccia desiderata e attendere che il settore specificato venga a trovarsi
sotto la testina. Come svantaggio si ha che la quantità di dati immagazzinabili deve essere la
stessa e per le tracce interne e per quelle esterne.
Essendo la densità, in bit per centimetro lineare, crescente man mano che ci si sposta dalla
traccia più esterna a quella più interna la capacità di memorizzazione dei dischi a velocità
angolare costante è limitata dalla massima densità ottenibile dalla traccia più interna. In
figura 15.5 è riportato l’andamento della crescita della densità per anno.
Year
Areal Density
1.7 100000
7.7
10000
63
3090
1000
17100
Areal Density
1973
1979
1989
1997
2000
100
10
1
1970
1980
1990
2000
Year
Figura 15.5 Andamento della densità.
Per incrementare tale densità si utilizza una tecnica nota come registrazione a più zone
(multiple zone recording) in cui la superficie è ripartita in un certo numero di aree (16
solitamente) all’interno delle quali il numero di bit per traccia è costante; le zone più lontane
dal centro contengono un numero maggiore di settori e quindi di bit rispetto a quelle più
centrali, ciò consente una capacità di memorizzazione più elevata a discapito di una logica
circuitale più complessa.
Poiché la testina si muove da una zona all’altra la lunghezza (lungo la traccia) dei singoli bit
varia, provocando così un cambiamento nei tempi di lettura e scrittura.
E’ logico che vi deve essere un criterio per localizzare la posizione del settore all’interno della
traccia, quindi un punto di partenza sulla traccia per poter identificare l’inizio e la fine di ogni
settore (Figura 15.6). Questi requisiti vengono rispettati tramite dati di controllo
memorizzati sul disco. Ossia il disco viene inizializzato con dei dati utilizzabili solo dal suo
sistema di controllo e non accessibili all’utente.
15.4
17
7
Gap1
Id
Sync
Byte
1
Track
#
2
41
515
20
17
Gap2 Data Gap3 Gap1
Head Sector CRC
#
#
1
1
2
7
Id
41
515
20
Bytes
Gap2 Data Gap3
Sync
Byte
Data
CRC
1
512
2
Bytes
Figura 15.6. Formato della traccia nel disco ‘ST506’.
15.2 CARATTERISTICHE FISICHE
La testina, rispetto alla direzione radiale del piatto, potrebbe essere fissa o mobile. Nei
dischi a testina fissa e’ presente una testina di lettura-scrittura per ciascuna traccia: tali
testine sono montate su un braccio rigido che si estende attraverso tutte le tracce (questi
sistemi attualmente sono rari). Nei dischi a testina mobile ve ne è una sola montata su un
braccio, quest’ultimo può estendersi o ritrarsi in quanto la testina deve essere in grado di
raggiungere una qualsiasi traccia.
Il disco è montato su un telaio comprendente il braccio, l’albero che fa ruotare il disco e i
circuiti per l’ingresso e l’uscita dei dati. I dischi non removibili restano montati nel proprio
telaio mentre quelli removibili possono essere asportati e sostituiti con altri dischi; il
vantaggio consiste nel poter disporre di quantità illimitate di dati pur avendo quantità limitate
di memorie disco, inoltre tali dischi possono essere spostati da un calcolatore ad un altro
come avviene con i floppy disk. I dischi magnetici removibili tendono oggi ad essere sostituite
da memoria a stato solido dette ‘memorie flash’.
Se la patina magnetizzabile è applicata ad entrambe le facce dei piatti i dischi sono detti a
doppia faccia (double-sided) altrimenti a singola faccia (single-sided). Nei modelli con più
piatti (multiple platter) disposti verticalmente a distanza di pochi centimetri sono presenti più
bracci; questi dischi utilizzano una testina mobile di lettura/scrittura per ogni superficie dei
piatti, le testine sono fissate meccanicamente in modo tale da trovarsi alla stessa distanza dal
centro del disco e muoversi contemporaneamente. Così, in ogni istante, tutte le testine sono
posizionate sulle tracce che presentano uguale distanza dal centro del disco.
A seconda del meccanismo della testina i dischi vengono classificati in tre tipi. Un un primo
tipo di disco, la testina di lettura/scrittura viene posizionata a distanza fissa sopra il piatto
lasciando un’intercapedine d’aria. All’altro estremo si trova un tipo di disco (floppy disk) in cui
il meccanismo che viene a contatto fisico con il supporto durante le operazioni di lettura o
scrittura. Per il terzo tipo va detto che la testina deve generare o leggere un campo
elettromagnetico di intensità sufficiente alla scrittura e alla lettura. Più è stretta la testina e
più è vicina alla superficie del piatto, quindi tracce più ravvicinate e pertanto maggiore densità
dei dati. Tuttavia più la testina è vicina al disco e maggiore è il rischio d’errore per impurità o
imperfezione. Per un miglioramento della tecnologia venne sviluppato il disco Winchester (esso
si trova comunemente all’interno di personal computer e workstation); le sue testine sono
utilizzate negli assemblaggi di dischi ermeticamente sigillati, praticamente privi di
contaminanti. Esse operano più vicino alla superficie rispetto a quelle convenzionali
consentendo così una maggiore densità dei dati.La testina è in realtà una foglia aerodinamica
15.5
che si appoggia leggermente alla superficie del piatto quando il disco è fermo. La pressione
dell’aria generata da un disco in rotazione è sufficiente per allontanare la foglia dalla
superficie. Il sistema risultante, privo di contatto, può quindi utilizzare testine più strette
che operano più vicino alla superficie del piattello.
15.3 CARATTERIZZAZIONE DELLE PRESTAZIONI DEL DISCO
Per accedere ai dati il sistema operativo deve pilotare i dischi attraverso un processo
composto da tre fasi.
Nella prima si posiziona la testina sopra la traccia più opportuna, operazione denominata seek
(letteralmente ricerca) ed il tempo necessario richiesto è denominato tempo di seek. I
produttori di dischi riportano nei loro manuali il tempo di seek minimo, massimo e medio. I
primi due sono facilmente misurabili, mentre la media si presta a diverse interpretazioni
poiché dipende dalla distanza di posizionamento. Le industrie si sono accordate nel calcolare il
tempo di seek medio come la somma di tutti i possibili tempi di seek divisa per il numero di
posizionamenti possibili. I tempi di seek medi che vengono dichiarati oscillano tra 8 ms e 20
ms, tuttavia a causa della località dei riferimenti al disco i valori medi reali possono anche
essere solo il 25-33% di quelli dichiarati; il fenomeno della località si manifesta sia a causa di
accessi consecutivi allo stesso file, sia perché il sistema operativo tenta di far eseguire
insieme tali accessi.
Una volta che la testina ha raggiunto la traccia corretta (seconda fase) è necessario
attendere che il settore desiderato si muova (ruotando) sotto la testina di lettura/scrittura;
questo tempo viene chiamato latenza di rotazione o ritardo di rotazione. La latenza media per
raggiungere l’informazione desiderata corrisponde a mezza rotazione del disco e poiché questi
ruotano ad una velocità compresa tra 3600 RPM e 7200 RPM la latenza di rotazione media
varia tra:
latenza di rotazione media = 0.5 rotazioni/3600 RPM = 8.3 ms
latenza di rotazione media = 0.5 rotazioni/7200 RPM = 4.2 ms
I dischi caratterizzati da diametri più piccoli sono più attraenti, poiché possono
ruotare a velocità maggiori senza un consumo eccessivo, riducendo così la latenza di
rotazione.
L’ultima fase che caratterizza un accesso ad un disco e’ denominata tempo di
trasferimento: è il tempo necessario per trasferire un blocco di bit, che corrisponde
solitamente ad un settore. Il tempo di trasferimento è una funzione della dimensione
del settore (oggi in realta’ molti trasferimenti da disco hanno una lunghezza pari a più
settori), della velocità di rotazione e della densità di memorizzazione di una traccia. Il
calcolo si complica di più se si considera che la maggior parte dei dischi possiedono una
cache integrata che memorizza i settori man mano che questi vengono acquisiti.
Il controllo del disco ed il trasferimento dei dati tra il disco e la memoria vengono
gestiti da un controllore di disco. Esso è responsabile dell’aggiunta di un’ulteriore
componente al tempo di accesso al disco, nota come tempo del controllore, che
corrisponde al ritardo che il controllore impone all’esecuzione degli accessi di I/O.
Quindi il tempo medio per eseguire un’operazione di I/O è determinato da queste
componenti oltre ad eventuali tempi di attesa dovuti al fatto che altri processi stanno
utilizzando il disco.
15.6
Esempio:
Calcolare il tempo medio per la scrittura o la lettura di un settore di 512 byte
per un disco che ruota a 5400 RPM ed in cui il tempo medio di seek e’ pari a 12
ms, il tempo di trasferimento e’ 5 MB/s, il tempo aggiuntivo causato dal
controllore risulta 2 ms.
Il tempo medio di accesso al disco è pari al tempo medio di seek + la latenza di
rotazione media + il tempo di trasferimento + il tempo del controllore.
Tempo medio accesso disco = 12 ms + 5.6 ms + (0.5 KB)/(5 MB/s) + 2 ms = 19.7
ms
Se il tempo medio di seek misurato fosse pari al 25% del tempo medio indicato il
risultato sarebbe:
Tempo medio accesso disco = 3 ms + 5.6 ms + (0.5 KB)/(5 MB/s) + 2 ms = 10.7
ms
Da notare è che quando si prende in considerazione il tempo medio di seek misurato,
anziché quello indicato dal costruttore, la latenza di rotazione può divenire la
componente principale del tempo di accesso.
Ciascuna traccia contiene lo stesso numero di bit, tuttavia quelle esterne sono più
lunghe. Queste, quindi, memorizzano le informazioni con una minore densità per
centimetro rispetto alle tracce più vicine al centro del disco. In alcuni tipi di hard
disk, quali tipicamente quelli basati sull’interfacctia SCSI (Small Computer Systems
Interface) si usa invece una tecnica detta densità di bit costante, basata sulla
memorizzazione di un numero maggiore di settori nelle tracce esterne rispetto a
quelle interne. La velocità alla quale si muove l’unità di lunghezza della singola traccia
sotto la testina è pertanto variabile ed è maggiore per le tracce esterne. Di
conseguenza, se il numero di bit per unità di lunghezza è costante, la frequenza alla
quale i bit devono essere letti/scritti è variabile, quindi, la parte elettronica del
dispositivo deve tener conto di questo fattore.
15.4 STANDARD DI COMUNICAZIONE
Le tecnologie utilizzate per gli hard disk sono oramai molto sofisticate, dovute direttamente
dalla maggiore richiesta di elevate prestazioni e capacità. L’evoluzione costante di processori
e memorie incrementa la quantità di dati da elaborare e gli hard disk sono costretti a ospitare
e spostare moli sempre maggiori di Mbyte. Le innovazioni tecniche riguardanti vari
componenti, come dischi e testine, condizionano la capacità degli hard disk oltre che le loro
prestazioni.
I vari sistemi d’interfacciamento sono importanti per evitare l’insorgere di limitazioni dal
punto di vista delle prestazioni. Attualmente (anno 2005) il sistema di interfacciamento più
diffuso utilizza il bus parallelo dello standard ATA (AT Attachment). Ad esempio, nello
standard ATA/100 e’ possibile ottenere una velocita’ di trasferimento dal controller all’hard
15.7
disk pari a 100. Una tale velocità è ragguardevole in quanto non ci sono, per ora, applicazioni
che richiedono tutta questa larghezza di banda.
Il trend degli hard disk per Personal Computer è orientato verso un nuovo standard
denominato S-ATA (Serial ATA), il quale usa un bus di comunicazione seriale anziché parallelo.
Il 29/08/2001 sono state rilasciate le specifiche della versione 1.0 dello standard Serial
ATA. Questa evoluzione fornisce prestazioni più elevate permettendo di superare alcune
limitazioni dovute ai precedenti protocolli di comunicazione, quali l’elevato numero di pin da
utilizzare per i collegamenti, la complessità (e quindi i costi) dei cavi usati e le tensioni in
gioco relativamente elevate. La prima evidente differenza è nei cavi, i quali utilizzano una
connessione punto-punto con lunghezza massima di un metro; inoltre, il Serial ATA, concepito
per collegare unità interne, usa due coppie di fili contro i 40/80 necessari con lo standard
ATA parallelo.
I sistemi operativi attuali sono compatibili poiché questo bus è trasparente rispetto alle
applicazioni; sarà, quindi, solamente necessario istallare i corrispondenti driver per il
controller così come accade per qualunque altro dispositivo. Un altro aspetto interessante è
che per il suo funzionamento necessita di una tensione di 500 millivolt picco-picco, inferiore a
quella di 5 V richiesta dagli standard ATA paralleli. Le conseguenze si ripercuotono sui
produttori di chipset; infatti, essendo il controller per gli hard disk ormai integrato nel
chipset principale del calcolatore, il non esser costretti a utilizzare una tensione di 5 V
permette di costruire chip con minori dimensioni e pertanto con un risparmio in termini di
costi di produzione. Questa riduzione di tensione torna anche utile per il mondo dei portatili
con conseguente prolungamento dell’autonomia delle batterie.
Per quanto riguarda la topologia di collegamento usata da Serial Ata ci si trova di fronte a una
configurazione a stella. Ossia da un connettore del controller parte un cavetto che va
direttamente all’hard disk, per collegare un secondo hard disk serve un secondo connettore e
un altro cavetto analogo al primo. Tra le conseguenze di questo tipo di collegamento c’è la
possibilità di evitare collisioni di segnali sullo stesso cavo, con diversi vantaggi per le
prestazioni.
A livello software non ci sono sconvolgimenti dato che il comportamento dei registri di
controllo e dei comandi, i trasferimenti dati, i reset e gli interrupt sono tutti emulati
dall’unità.
15.5 DIREZIONI FUTURE
I dischi magnetici hanno capacità che crescono rapidamente anche se il tempo di
accesso migliora molto più lentamente. Una delle spiegazioni sta nel fatto che i dischi
magnetici hanno un’evoluzione più veloce nella fascia bassa piuttosto che in quella alta
del mercato, ed è la fascia bassa a registrare una spinta maggiore verso un limitato
costo per megabyte. Questo mercato ha dato il suo contributo alla riduzione nella
dimensione dei dischi dai piatti da 14 pollici (35 cm) usati per i dischi dei mainframe ai
dischi di 1.3 pollici (3.5 cm) sviluppati per i calcolatori portatili e palmari. Questa
elevatissima richiesta di dischi piccoli ha portato ad un’accelerazione per il
miglioramento della densità dei dischi.
Oltre al miglioramento della densità sono cresciute anche le velocità di trasferimento,
man mano che i dischi aumentavano la propria velocità di rotazione e miglioravano le
interfacce. Inoltre, ogni disco ad alte prestazioni prodotto oggi contiene un buffer di
15.8
traccia o di settore che implementa un meccanismo di cache che memorizza i settori
quando la testina passa sopra di essi.
Un’importante novità per ciò che concerne l’organizzazione dei dischi è costituita dai
sistemi RAID (Redundant Array of Inexpensive Disks o letteralmente schiere di
dischi piccoli ed economici). La motivazione che spinge a ciò è che, poiché il prezzo per
megabyte è indipendente dalla dimensione del disco, il throughput potenziale può
essere migliorato utilizzando più unità disco e quindi più testine: la distribuzione dei
dati su più dischi fa sì che gli accessi avvengano su più dischi; mentre l’uso delle
schiere migliora il throughput , la latenza non viene però necessariamente ridotta.
L’aggiunta dei dischi ridondanti offre la possibilità di scoprire un disco guasto e
recuperare automaticamente l’informazione perduta. Le schiere possono quindi
migliorare l’affidabilità e le prestazioni dei sistemi di elaborazione. Ad esempio IBM
offre sia la soluzione basata su RAID sia un sottosistema di dischi che utilizza i dischi
più grandi prodotti dall’azienda.
Nonostante questo continuo sviluppo va sottolineato come il disco rimanga il vero collo
di bottiglia dei moderni Personal Computer: con tempi di accesso ai dati dell’ordine di
una decina di millisecondi, un moderno processore può sprecare qualche milione di cicli
utili in attesa del dato giusto da elaborare. Le piattaforme multiprocessore di
prossima introduzione non faranno che esasperare questo problema, necessitando di
maggiori quantità di dati da elaborare.
Le nuove generazioni di dischi rigidi hanno,tuttavia, tutte le carte in regola per stare
al passo con le future piattaforme. Infatti basti osservare le nuove tecnologie dei
protocolli di comunicazione. Ad esempio, la tecnologia NCQ (Native Command
Queueing) è la caratteristica più avanzata del futuro standard Serial Ata II. NCQ è
un protocollo che permette all’hardware di controllo del disco di mantenere in sospeso
più comandi allo stesso tempo. I dischi che lo supportano dispongono di un buffer dove
i comandi pervenuti possono essere accodati, riordinati e infine essere eseguiti in
modo dinamico per ottimizzare i tempi di accesso e scrittura dei dati sulla superficie
magnetica. Il protocollo Serial Ata II si basa su tre nuove funzioni integrate atte a
migliorare le prestazioni dei dischi: Race Free Status Return Mechanism, Interrupt
Aggregation e First Party Dma.
La prima funzione consiste nella possibilità di comunicare la risposta, o status, ad un
qualunque comando dell’host in ogni momento senza che questo resti in attesa per
riceverlo; in questo modo il drive può soddisfare comandi multipli e restituirne lo
stato consecutivamente o perfino nello stesso momento.
La seconda funzione, aggregazione degli interrupt, consente di superare il problema
che riguarda il drive per ciò che concerne la produzione di un segnale d’interruzione
per l’host ogni volta che completa un comando: se un drive con NCQ completa più
comandi in un tempo molto breve, i singoli segnali di interrupt vengono aggregati per
produrne uno solo affinché l’host sia interrotto una volta sola per più comandi.
Infine la funzione First Party Dma consiste in un sofisticato sistema che permette al
drive di eseguire un’operazione di accesso diretto alla memoria centrale per
trasferirvi dati senza l’intervento del software dell’host ed è alla base della
15.9
possibilità di riordinare la sequenza di esecuzione dei comandi. Infatti, il drive può
scegliere di propria iniziativa il buffer da trasferire e preparare il comando
appropriato per il sistema DMA, il controller dell’host si limita quindi a copiare nel
controller DMA la direttiva che arriva dal drive in modo che venga avviato il
trasferimento hardware dei dati.
E’ evidente che, con il sistema NCQ, l’host può trasmettere comandi mentre il disco
sta ancora eseguendo quelli precedenti, offrendo così un miglior supporto al software
multithread per Windows o Linux. In pratica la coda delle richieste di accesso al disco
viene riordinata e eseguita tenendo conto della posizione delle testine sui dischi, in
modo da ridurre al minimo il numero di rotazioni dei dischi e di spostamenti delle
testine, il che comporta migliori prestazioni e minore usura meccanica. La logica NCQ
del drive può decidere di eseguire subito una richiesta appena arrivata, facendole
saltare una lunga coda di attesa, se determina che la testina sia già nella giusta
posizione, magari integrando la richiesta a quelle che riguardano lo stesso settore su
disco. Le prove di laboratorio hanno evidenziato come, anche in mancanza di software
appositamente ottimizzato, l’incremento di prestazioni è di almeno il 15%.
Per ciò che concerne i futuri supporti ottici sono da menzionare due formati, che sono
il Blu-Ray e HD-DVD (entrambi basati su laser blu, cioe’ a lunghezza d’onda piu’ bassa
rispetto ai laser attualmente utilizzati nelle memorie ottiche) che sono in concorrenza
per succedere agli attuali formati per DVD. I supporti Blu-Ray possono contenere fino
a 50 GByte di dati, mentre gli HD-DVD possono registrare fino a 32 GByte di dati. Il
supporto fisico ha lo stesso spessore di un normale DVD (1.2 millimetri) e i
masterizzatori di DVD esistenti si potrebbero modificare con poca spesa. Blu Ray e
HD-DVD sono, quindi, una soluzione interessante per il backup dei dati. E’ interessante
notare come anche nella variante Double Layer (7.9 GByte) il DVD non riesca ad
eguagliare la capacità di archiviazione offerta da Blu Ray e HD-DVD.
15.6 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
15.10
LEZIONE 16
Introduzione al sottosistema di memoria
16.1 INTRODUZIONE
Il sottosistema di memoria rappresenta una parte fondamentale per il funzionamento di un
calcolatore ed influenza consistentemente le sue prestazioni.
Esistono varie tipologie di memorie e varie tecnologie sono utilizzate per implementarle.
Le memorie ad accesso casuale (RAM –Random Access Memory) consentono di leggere e
scrivere informazioni con un tempo di accesso che risulta indipendente dalla locazione di
memoria effettivamente indirizzata. Questo tipo di memorie possono essere volatili, cioè se
ne perde il contenuto quando viene spento il calcolatore, oppure non-volatili.
Le memorie ad accesso semicasuale possiedono come caratteristica il fatto che il tempo di
accesso è dipendente, sia dalla locazione fisica dell’informazione, sia dal particolare istante in
cui viene effettuato l’accesso. Alcuni esempi che utilizzano questo tipo di modalità sono i
dischi-fissi (Hard Disk) e i CD-ROM.
Nelle memorie ad accesso sequenziale, i dati vengono memorizzati in modo sequenziale nelle
locazioni di memoria; quindi il tempo di accesso dipende in maniera lineare dalla locazione
fisica. I nastri sono delle componenti hardware che utilizzano questo tipo di modalità.
16.2 RANDOM ACCESS MEMORY (RAM)
Esistono diverse tecnologie con cui vengono realizzate memorie ad accesso casuale, quali le
DRAM (Dynamic Random Access Memory), le SRAM (Static Random Access Memory) e le
memorie non volatili (es. ROM, EPROM e varianti, Flash). Questi tipi di memoria prevedono un
meccanismo fisico per l’accesso alla locazione di memoria che consente di ottenere un tempo
di accesso relativamente basso rispetto agli altri tipi di tecnologie di accesso ed inoltre tale
tempo risulta lo stesso qualunque sia la locazione di memoria alla quale sia necessario
accedere. I vari tipi di memoria RAM si differenziano principalmente per la diversa tecnologia
usata per l’implementazione fisica della locazione di memoria, detta anche “cella di memoria”.
A seconda della tecnologia usata, la cella risultera’ piu’ veloce, piu’ compatta, in grado di
mantenere o meno le informazioni in assenza di alimentazione.
Il tipo di cella piu’ compatto si realizza con la tecnologia DRAM: in questo caso la cella e’
realizzata con un semplice condensatore (come vedremo in dettaglio nel capitolo 16.4 ogni bit
puo’ essere memorizzato mediante un condensatore). Per questo motivo le memorie DRAM
sono spesso usate per realizzare la memoria principale, in modo da ottenere una grande
capienza a bassi costi. Il tipo di cella piu’ veloce si realizza con la tecnologia SRAM: in questo
caso la cella e’ realizzata utilizzando anche 6 transistor. Le memorie SRAM, che verranno
approfondite nel capitolo 16.3, sono normalmente usate per realizzare memorie veloci, come la
memoria cache dato che i tempi di accesso sono relativamente bassi (circa 5 ns con le
tecnologie attuali). Se non c’e’ bisogno di variare il contenuto della cella di memoria si fa’ uso
delle memorie non volatili, che hanno il vantaggio di mantenere i dati memorizzati anche in
assenza di alimentazione della cella. Esistono diversi tipi di memorie non volatili, quali le ROM
(Read Only Memory), le PROM (Programmable ROM), le EPROM (Erasable PROM), le EEPROM
(Electrical EPROM) e infine le memorie Flash.
Nelle ROM, che come indica il nome sono memorie di sola lettura, i dati vengono memorizzati
quando viene fabbricato il circuito e non possono più essere alterati. Le PROM sono ROM
programmabili, ovvero la memoria può essere scritta soltanto una volta e normalmente questo
viene fatto dal costruttore.
Invece le EPROM, le EEPROM, e le memorie flash sono riscrivibili, tipicamente attraverso due
passi: cancellazione e scrittura. Questi tre tipi di memoria usano procedimenti diversi per
16.1
eseguire la cancellazione dei dati. Le EPROM sono cancellabili a livello di chip attraverso
l’esposizione di raggi UV (Ultra Violetti). Le EEPROM sono cancellabili a livello di byte
elettricamente, applicando un determinato potenziale alla cella di memoria, mentre le memorie
Flash sono cancellabili sempre elettricamente, ma a livello di blocco. Queste ultime hanno una
struttura simile a quella delle DRAM, ovvero hanno un singolo transistor per cella di memoria,
e sono caratterizzate da un alta densità di celle. Inoltre, il tempo di lettura è paragonabile a
quello delle DRAM (circa 100 ns con le tecnologie del 2004).
Nella tabella 16.1 vengono evidenziate le differenze fra i vari tipi di memorie ad accesso
casuale (RAM).
Tabella 16.1. Riepilogo caratteristiche memorie ad accesso casuale
Tipo
SRAM/DRAM
ROM
PROM
EPROM
Flash
EEPROM
Categoria
read/write
read only
read only
read only
read mostly
read mostly
Cancellazione
elettricamente
impossibile
impossibile
luce UV
elettricamente
elettricamente
Scrittura
elettricamente
maschere
elettricamente
elettricamente
elettricamente
elettricamente
Volatilità
volatile
non volatile
non volatile
non volatile
non volatile
non volatile
16.3 CELLA DI RAM STATICA (SRAM)
La cella di RAM statica e’ costituita da circuiti capaci di rispondere rapidamente all’accesso e
al tempo stesso di mantenere l’informazione in essa contenuta per tutto il periodo in cui la
stessa e’ alimentata. La figura 16.1 rappresenta la tipica struttura di una cella di memoria di
una RAM statica (SRAM). Due buffer invertenti sono formano un latch. Il latch è collegato
alla linea su cui transita il dato (fisicamente composta da due linee bit e /bit, per ragioni di
efficienza di pilotaggio della cella) tramite i due transistor di passo. Questi ultimi hanno la
caratteristica di agire da interruttori controllati della linea di indirizzamento della cella
(word line). Quando, ad esempio, la word line è a massa i transistor di passo sono chiusi e di
conseguenza il latch mantiene il proprio stato.
word line
Cella a 6 Transistor
word line
0
0
bit
1
1
bit
bit
bit
Figura 16.1: Cella SRAM
Per poter eseguire un’operazione di lettura dalla cella SRAM, dopo aver precaricato entrambe
le bitline alla tensione di alimentazione VDD, la word line deve essere attivata aprendo i
transistor di passo. Se la cella si trova nello stato logico 1, il segnale sulla linea bit restera’
alto, mentre quello sulla linea /bit diventera’ basso. La situazione è opposta nel caso in cui la
16.2
cella sia nello stato logico 0. Le due linee bit e /bit pilotano un “sense amplifier” (un circuito in
grado di amplificare la differenza tra i due segnali) che fornisce in uscita il dato letto.
Per quanto riguarda le operazioni di scrittura, lo stato della cella viene aggiornato portando la
linea bit (e conseguentemente /bit) al valore da scrivere, e successivamente attivando la word
line. A questo punto il contenuto della cella viene forzato nello stato desiderato.
16.3.1 Organizzazione delle celle SRAM
Per formare una memoria di una certa dimensione, la cella viene replicata secondo uno schema
bidimensionale come mostrato in figura 16.2: tutti i latch di una certa colonna della memoria
siano collegati alla stessa linea in output (es. Dout 1) attraverso un Sense–Amplifier. Quando il
sense amplifier rileva la piccola variazione di tensione determinata dalla “apertura” della cella
in lettura, sbilancia completamente la sua tensione di uscita verso un valore alto o basso di
tensione, in base al segno della variazione rilevata.
Din 3
Din 2
Din 1
Din 0
Wr Driver &
- Precharger +
Wr Driver &
- Precharger +
Wr Driver &
- Precharger +
Wr Driver &
- Precharger +
SRAM
Cell
SRAM
Cell
SRAM
Cell
SRAM
Cell
SRAM
Cell
SRAM
Cell
SRAM
Cell
SRAM
Cell
WE_L
Precharge
‘_L’ Æ
attivo basso
Word 0
A1
A2
A3
Address Decoder
A0
Word 1
:
:
:
:
Word 15
SRAM
Cell
SRAM
Cell
SRAM
Cell
SRAM
Cell
- Sense Amp +
- Sense Amp +
- Sense Amp +
- Sense Amp +
Dout 3
Dout 2
Dout 1
Dout 0
Figura 16.2. Struttura celle SRAM (16 word x 4 bit)
La funzione dell’Address Decoder è quella di selezionare e abilitare in lettura o scrittura una
determinata word line della memoria (dalla word 0 alla word 15, nell’esempio). Per abilitare il
processo di scrittura o lettura viene usato il segnale WE_L3 (Write Enable).
Il segnale precharge serve ad attivare il circuito precharger che si occupa di caricare le linee bit e
/bit entrambe alla tensione VDD. Successivamente, il sense-amplifier rilevera’ la leggera differenza
di carica che si puo’ generare all’accesso della cella. Poiché la linea dati viene tipicamente mappata
su un unico segnale di ingresso/uscita DQ, completano lo schema coppie di buffer tri-state (uno per
ogni coppia Din e Dout) pilotati dal segnale OE_L (Output Enable), come in figura 16.3.
3
Con il simbolo “_L” si indica che il segnale è ATTIVO BASSO, ossia assume il significato di attivo se il valore sulla linea è basso.
16.3
OE_L
Din o D
DQ
Dout o Q
Figura16.3. SRAM Bus dati
Attivando o disattivando i segnali di controllo WE_L e OE_L si hanno varie combinazioni
possibili (Tabella 16.2).
Tabella 16.2: Controllo del bus dati SRAM
WE_L
Basso
Alto
OE_L
Alto
Basso
Significato
Scrittura con ingresso in DQ
Lettura con uscita in DQ
Attivando WE_L (basso) e disattivando OE_L (alto) si abilita la scrittura in memoria e si
assume DQ come ingresso. Disattivando WE_L (alto) e attivando OE_L (basso) si abilita la
lettura e si assume DQ come uscita. Deve essere evitato di attivare sia WE_L (Write Enable)
che OE_L (Output Enable) poiché si viene a verificare uno stato di indeterminazione.
Infine, si usa un ulteriore segnale di controllo, CE_L (Chip Enable) per disattivare (alta
impedenza), il bus dati in modo che non vada a costituire un inutile carico sul bus comune ad
altri chip.
Si osservi che la struttura esaminata in figura 16.2 ha soprattutto una valenza didattica in
quanto una memoria di maggiori dimensioni dovra’ invece essere suddivisa in modo da evitare
decoder di enormi dimensioni.
16.3.2 Temporizzazione delle SRAM
In figura 4, viene riportata la temporizzazione del ciclo di lettura e di quello di scrittura di
una SRAM.
Write Timing:
Read Timing:
High Z
DQ
Data In
A
Write Address
Data Out
Junk
Read Address
Data Out
Read Address
OE_L
WE_L
Write
Setup
Time
Write
Pulse
Time, tWP
Read
Access
Time
Write
Hold
Time
Write Cycle Time, tWC
Read
Access
Time
Read Cycle Time, tRC
Figura 2. Temporizzazione SRAM
Il tempo di accesso sull’operazione di lettura (Read Access Time) è definito come il tempo
necessario ad avere i dati stabili all’uscita, ed è calcolato a partire dall’istante in cui gli
indirizzi A ed i segnali di controllo (OE_L e WE_L) sono stabili agli ingressi. Dopo che il
16.4
segnale di controllo OE_L viene attivato, l’uscita esce dallo stato di alta impedenza (High Z), e
dopo un certo tempo (Read Access Time), i dati sono stabili sull’ uscita (Data Out). Trascorso
il tempo trc (Read Cycle Time), si può iniziare un nuovo ciclo di lettura.
Per quanto riguarda l’operazione di scrittura, è necessario che sia fornito l’indirizzo A,
abilitato il segnale di controllo chip enable (CE_L) e che siano presenti i dati in ingresso da
scrivere. Il tempo che si deve attendere prima di poter attivare WE_L per abilitare l’impulso
di scrittura (Write Pulse) è detto Write Setup Time. Il tempo che WE_L deve rimanere
attivo per completare la scrittura è detto Write Pulse Time (twp). Per completare l’operazione
si deve attendere un ulteriore tempo detto Write Hold Time. Con twc (Write Cycle Time) si
indica il tempo dopo il quale può cominciare un nuovo ciclo di scrittura.
16.4 CELLA DI RAM DINAMICA (DRAM)
Allo scopo di ottenere memorie particolarmente dense si usa la tecnologia DRAM (Dynamic
Random Access Memory) in cui la cella e’ costituita sostanzialmente da un solo condensatore
(figura 16.5). Quando il condensatore è carico la cella assume il valore logico 1; viceversa
quando è scarico il valore logico è 0. Tuttavia, tali celle non possono mantenere il loro stato
per un tempo indefinito, ma solo per pochi millesecondi, per questo motivo sono chiamate
memorie RAM dinamiche. Il loro contenuto deve essere quindi periodicamente “rinfrescato”,
rigenerando così la carica intera del condensatore.
Cella D RAM a 1 transistor
word
line
T ransistor di passo
bit
Condensatore
Figura 16.5. Struttura della cella di DRAM
16.4.1 Funzionamento della cella a 1 transistor DRAM
Per poter memorizzare le informazioni in questa cella, il transistor di passo deve essere
aperto (tramite il controllo sulla word line), e deve essere applicata una tensione adeguata alla
linea di bit. Questa operazione provoca il caricamento del condensatore fino ad un
determinato valore.
Purtroppo, anche quando il transistor di passo si trova nello stato OFF, il condensatore tende
a scaricarsi. Questo è dovuto alla resistenza parassita del condensatore stesso e al fatto che
il transistor continua a condurre e a far circolare un piccolo quantitativo di corrente
(dell’ordine di pA), anche dopo essere stato disattivato.
Le informazioni memorizzate nella cella possono essere lette in modo corretto solo se vi si
accede prima che la carica del condensatore sia scesa sotto un valore prefissato.
16.5
Durante l’operazione di lettura, la linea di bit viene pre-caricata ad un valore pari a Vdd/2 e
viene attivato il transistor di passo. Un sense amplifier, collegato alla linea di bit, determina
se la carica del condensatore sia sopra o sotto il valore prefissato.
L’operazione di lettura distrugge il valore contenuto nella cella a cui si accede. Per mantenere
le informazioni memorizzate, la memoria DRAM comprende circuiti appositi che riscrivono il
valore appena letto. Quindi, tutte le celle collegate ad una determinata word line vengono
rinfrescate ogni volta che la word line viene attivata.
16.4.2 Organizzazione logica di memoria DRAM a 4 Mbit
Nella figura 16.6 possiamo osservare lo schema dell’organizzazione logica di una memoria
DRAM a 4Mbit. Le celle di memoria sono organizzate in una griglia bidimensionale. Per
individuare una determinata cella si utilizzano due segnali: l’indirizzo di riga, RAS_L (Row
Address Strobe) e l’indirizzo di colonna CAS_L (Column Address Strobe). Come già visto, il
circuito di precarica accumula la carica per poi indirizzarla alle celle. I circuiti Row Decoder e
Column Decoder permettono di ridurre la complessità (e le dimensioni) di una DRAM. Infatti,
invece di usare un decoder con dimensione 22, (2²²=4194304), si usano due decoder con
dimensione 11, (2¹¹x2=4096). I Row Latches e Column Latches mantengono l’indirizzo.
A0…A10
e dopo…
A11…A21
R
o
w
11
L
a
t
c
h
e
s
Precharge circuit
R
o
w
11
11
D
e
c
o
d
e
r
C
o
l.
Storage Cell
Memory Array
(2048 x 2048)
DQ
Sense Amp. & I/O Circuits
Data
IN
L
a
t
c
h
e
s
word line
Data
OUT
C
T
R
L
bit line
RAS_L
CAS_L
WE_L
Column Decoder
11
Figura 16.6. Schema dell’organizzazione logica DRAM
16.4.3 Diagramma Logico della DRAM
Le memorie SRAM possiedono una interfaccia logica leggermente diversa da quella delle
DRAM, come si può vedere dalla figura 16.7. I pin A servono per specificare sia gli indirizzi di
riga che quelli di colonna. Se RAS_L va basso, i latch di riga memorizzano i pin A. Se invece
CAS_L va basso, i latch di colonna memorizzano i pin A.
16.6
RAS_L
WE_L
CAS_L
A
OE_L
256K x 8
DRAM
9
DQ
8
Figura 16.7. Interfaccia logica di un chip di DRAM
Analogamente alle SRAM, i segnali Din e Dout passano attraverso un multiplexer e vanno a
formare un unico segnale DQ. Come già visto per la SRAM, il segnale OE_L viene utilizzato
per controllare i buffer tri-state.
Se WE_L è attivo e OE_L è disattivo viene abilitato il processo di scrittura, e si assume il
segnale DQ come ingresso. Se invece WE_L è disattivo e OE_L è attivo viene abilitato il
processo di lettura, e DQ è un’uscita (Tabella 16.3).
Tabella 16.3: Controllo del bus dati DRAM
WE_L
Basso
Alto
OE_L
Alto
Basso
Significato
DQ è un ingresso
DQ è una uscita
16.4.4 Ciclo di scrittura DRAM
Nelle memorie DRAM il ciclo di scrittura viene controllato principalmente dai segnali CAS_L,
RAS_L e WE_L.
Write Cycle Time
RAS_L
CAS_L
A
Row Address
Col Address
Junk
Data In
Junk
Row Address
Col Address
Junk
OE_L
WE_L
DQ
Junk
Data In
Write Access Time
Junk
Write Access Time
Early Write Cycle: WE_L attivato prima di CAS_L
Late Write Cycle: WE_L attivato dopo CAS_L
Figura 16.8. Ciclo di scrittura DRAM
L’operazione di scrittura inizia asserendo RAS_L ed inviando sui pin A i bit piu’ significativi
dell’indirizzo (indirizzo di riga). Trascorso un certo lasso di tempo, si pongono sui pin A i bit
meno significativi dell’indirizzo (indirizzo di colonna) e si asserisce il segnale CAS_L, da
questo istante in poi ha inizio la scrittura (Write Access Time). Dopo poco, il ciclo di scrittura
è terminato e un altro ciclo può iniziare. Il tempo di accesso in scrittura (Write Access Time)
è definito come l’intervallo di tempo fra l’attivazione di RAS_L e la disattivazione di RAS_L (e
CAS_L).
16.7
Se il segnale WE_L viene attivato prima di CAS_L, si avrà un ciclo di scrittura anticipata
(Early Write Cycle), mentre se viene attivato dopo CAS_L si avrà un ciclo di scrittura
ritardata (Late Write Cycle). Una scrittura puo’ essere seguita da una nuova operazione
(lettura o scrittura) dopo che sia trascorso un tempo il tempo Write Cycle Time dall’inizio
della scrittura precedente.
16.4.5 Ciclo di lettura DRAM
La figura 16.9 mostra la temporizzazione di un ciclo di lettura di una memoria DRAM. La
procedura per il caricamento degli indirizzi è la medesima di quella usata per la scrittura.
Dopo il tempo Read Cycle Time si può effettuare un altro ciclo di lettura.
Come si può osservare dalla figura 16.9, il tempo di accesso al ciclo di lettura (Read Access
Time) termina quando sia RAS_L e CAS_L vengono disattivati. A seconda che OE_L sia
attivato prima o dopo CAS_L si avrà rispettivamente un Early Read Cycle o un Late Read
Cycle.
Read Cycle Time
RAS_L
CAS_L
A
Row Address
Col Address
Junk
Row Address
Col Address
Junk
WE_L
OE_L
DQ
High Z
Junk
Read Access
Time
Data Out
High Z
Output Enable
Delay
Early Read Cycle: OE_L attivato prima CAS_L
Data Out
Late Read Cycle: OE_L attivato dopo CAS_L
Figura 16.9. Ciclo di lettura DRAM
16.4.6 Tempi di accesso DRAM; trc,trac,tcac e tpc
Come già visto in precedenza, trc (Read Cycle Time) è il tempo minimo fra due accessi alle
righe. Questo dipende dal segnale RAS_L; infatti inizia con il primo fronte in discesa di
quest’ultimo e termina con il successivo fronte in discesa (figura 16.10). Per una DRAM a 4
Mbit, trc tipicamente ha un valore ad esempio di110 ns (considerando le tecnologie del 2004).
16.8
tRC = Read Cycle Time
RAS_L
CAS_L
A
Row Address
Col Address
Junk
Row Address
Col Address
Junk
WE_L
OE_L
DQ
High Z
Junk
Data Out
High Z
Output Enable
Delay
tCAC = Read
Access Time from CAS
Data Out
tRAC = Read
Access Tim e from RAS
Figura 16.10. DRAM: Temporizzazione
I tempi trac e tcac sono chiamati rispettivamente Access Time from RAS e Access Time from
CAS e dipendono rispettivamente dai segnali RAS_L e CAS_L. Il tempo trac ha inizio dal
fronte in discesa di RAS_L, e termina al fronte in discesa di DQ, cioè quando i dati sono
validi, mentre tcac inizia dal fronte in discesa di CAS_L, cioè quando anche l’indirizzo sono
validi. La velocità della DRAM normalmente è definita da trac . Con le attuali tecnologie si ha
ad esempio trc=110ns e trac=60ns.
La figura 16.11 illustra il “Page Cycle Time”. Il Page Cycle Time è il tempo che trascorre
dall’inizio dell’accesso ad una colonna e l’inizio dell’accesso alla colonna successiva.
tPC = Page Cycle time
RAS_L
CAS_L
A
Row Address
Col Address
Junk
Row Address
Col Address
Junk
WE_L
OE_L
DQ
High Z
Junk
Data Out
High Z
Output Enable
Delay
Figura 16.11. DRAM: Page Cycle Time
16.9
Data Out
Il tpc (Page Cycle Time) ha inizio dal primo fronte in discesa di CAS_L e termina al successivo
fronte in discesa di RAS_L. In una DRAM a 4Mbit con trac equivalente a 60 ns, si ha un un
“Page Cycle Time” equivalente tpc=60 ns (con le tecnologie attuali).
Una DRAM con trac=60 ns effettua un accesso a righe distinte ogni 110 ns (trc) ed accede ad
una colonna in 15 ns (tcac). E’ necessario però considerare anche il tempo tpc, cioè il tempo
minimo tra due accessi ad una colonna, che puo’ essere circa 35 ns; inoltre si deve tenere
conto anche dei ritardi di indirizzamento esterni, ovvero il tempo necessario per inviare
l’indirizzo all’esterno del microprocessore, e dei tempi di “turnaround” sul bus che fanno
aumentare tpc a 40-50 ns.
Nel calcolo del tempo di latenza, se si tiene conto del ritardo introdotto dal controller della
memoria, o di fattori come il pilotaggio dei chip di DRAM, del controller esterno,bus modulo
SIMM e numero dei pin da pilotare, per una DRAM con trac=60 ns si può arrivare ad un tempo
di 180-250 ns4.
16.4.7 Terminologia delle DRAM
Le DRAM possono essere catalogate in base alla loro capacità, al costo per bit, alla banda e al
tempo di latenza. Nella tabella 16.4 è illustrato un parallelo fra le metriche delle DRAM e
quelle dei microprocessori. Il trend di sviluppo (fino ad oggi) delle memorie DRAM è
caratterizzato da un aumento della capacità del 60% ogni anno, una diminuzione del costo per
bit del 25%, un incremento della banda del 20% ed una riduzione della latenza del 7%, sempre
per anno. Con maggiore dettaglio e’ riportata in tabella 16.5 la situazione di evoluzione
tecnologica delle DRAM nel corso degli anni.
Tabella 16.4. Terminologia DRAM
Standard
Parametri di merito
Tassi di variazione
per anno
4
DRAM
Pinout, package, refresh rate,
capacita’
1) capacita’
2) costo per bit
3) banda
4) latenza
1) +60%
2) -25%
3) +20%
4) -7%
I tempi considerati sono riferiti alle tecnologie del 2004
16.10
microprocessori
Compatibilita’ binaria,
IEEE 754,
bus di I/O
1) quanti SPEC
2) costo
1) +60%
2) circa costante
Tabella 16.5. Evoluzione delle DRAM nel tempo
Primi esemplari
‘84
‘87
‘90
‘93
‘96
‘99
Capacita’ (x 1bit)
1 Mb
4 Mb
16 Mb
64 Mb
256 Mb
1 Gb
Area chip (mm2)
55
85
130
200
300
450
Area Memoria (mm2)
30
47
72
110
165
250
22.84
11.1
4.26
1.64
0.61
0.23
Area Cella di memoria (µm2)
(da Kazuhiro Sakashita, Mitsubishi)
16.5 CONSIDERAZIONI DI RIEPILOGO SULLE RAM
Le memorie DRAM (Dynamic Random Access Memory) hanno prestazioni più basse, ma hanno
costi inferiori e capacità maggiori rispetto alle memorie SRAM. Per questi motivi le DRAM
vengono tipicamente utilizzate per realizzare la memoria principale di un calcolatore.
Le memorie SRAM (Static Random Access Memory) hanno prestazioni elevate, ma anche un
alto livello di mercato (più costose), inoltre hanno una capacità ridotta e dissipano 5 volte più
energia rispetto alle memorie DRAM. In conclusione le SRAM dovrebbero essere utilizzate
con attenzione, ovvero solo nel caso in cui risulta essere molto importante il tempo di accesso
ai dati.
16.6 TECNICHE
MEMORIA
PER
MIGLIORARE
LE
PRESTAZIONI
DELLA
16.6.1 Bus Semplice (Organizzazione diretta)
CPU
MEM
Figura 16.12. Schema a Bus semplice
La figura 16.12 mostra la più semplice organizzazione (Organizzazione diretta o “Bus
semplice”) del sottosistema di memoria. In questo schema, CPU, cache, memoria e BUS hanno
tutti lo stesso parallelismo ovvero il bus ha una larghezza fissa di 32 bit (cioè la dimensione di
una word).
16.11
16.6.2 Bus Largo (Organizzazione estesa)
CPU
MUX
MEM
Figura 16.13. Schema a Bus largo
La figura 16.13 mostra un’opzione per aumentare la larghezza di banda verso la memoria che si
basa sull’ampliamento della memoria e del bus tra il processore e la memoria. Tale situazione
permette l’accesso in parallelo a tutte le parole di un blocco. La logica tra la cache e la cpu
consta di un multiplexer (MUX) usato per le letture e di una logica di controllo per aggiornare
le parole opportune della cache durante le operazioni di scrittura. Il bus fra CPU e cache
conserva sempre una larghezza di banda di 1 word ma il parallelismo avviene tra gli altri
componenti dove è possibile lo scambio di N word in parallelo.
L’aumentare dell’ampiezza della memoria e del bus fa aumentare proporzionalmente la
larghezza di banda, riducendo la latenza associata in caso agli accessi. I principali costi
associati a questo miglioramento sono il bus più ampio ed il possibile aumento nel tempo di
accesso dovuto al multiplexer ed alla logica di controllo tra la cpu e memoria.
16.6.3 Interleaving
CPU
MEM
BANK
0
MEM
BANK
1
MEM
BANK
2
MEM
BANK
3
Figura 16.14: Schema Interleaving
La figura 16.14 mostra un bus che non presenta nessun tipo di parallelismo (larghezza pari ad 1
word), ma una memoria con interleaving. Questa soluzione consente di aumentare la larghezza
di banda ampliando la larghezza della memoria, ma non quella del bus di interconnessione. La
memoria e’ organizzata in banchi sui quali si possono distribuire in maniera opportuna gli
accessi. Per illustrare il funzionamento in caso di interleaving ricordiamo che in generale,
quando si fa accesso ad una memoria si possono individuare:
16.12
Cycle Time - Tempo di Ciclo della DRAM (Read/Write): rappresenta il periodo di
tempo tra un accesso e l’altro, ovvero indica la frequenza con cui si possono fare
accessi in memoria (figura 16.15).
•
Access Time - Il Tempo di Accesso della DRAM (Read/Write): rappresenta il
tempo necessario alla memoria per accedere alla cella indirizzata, ovvero quanto tempo
occorre per poter ottenere il dato richiesto rispetto alla fase iniziale dell’accesso.
Come si può notare il Cycle Time risulta essere notevolmente maggiore rispetto all’Access
Time, creando così problemi alle prestazioni della memoria.
•
Cycle Time
Access Time
Time
Figura 16.15. Tempo di Ciclo DRAM
In un modello senza interleaving (figura 16.16) l’inizio dell’accesso al dato D2 avviene solo alla
fine del tempo di ciclo della DRAM, attendendo un tempo inutile in cui il dato D1 è già pronto.
Senza interleaving:
CPU
Memory
Dato D1 disponibile
Inizio dell’accesso al dato D1
Inizio dell’accesso al dato D2
Figura 16.16. Metodo "Senza Interleaving"
Per rendere l’accesso in memoria più veloce, si adotta il modello con interleaving, in cui si inizia
un nuovo accesso, ma su un banco diverso, non appena e’ terminato il tempo di accesso.
Memory
Bank 0
4-way Interleaving:
Accesso al Banco 0
CPU
Accesso
al Banco 1
Memory
Bank 1
Memory
Bank 2
Memory
Bank 3
Accesso
al Banco 2 Accesso
al Banco 3 Posso di nuovo fare accesso al Banco 0
Figura 16.17. Metodo "4 - Way Interleaving”
Considerando il caso in cui la memoria sia divisa in 4 banchi (4-way Interleaving) si presenta
la situazione di figura 16.17: mentre la CPU finisce di usare il 1° banco di memoria, sta
simultaneamente usando gli altri tre banchi.
16.13
16.6.4 Confronto fra le tecniche
Confrontiamo le tre tecniche viste per valutare le differenza di prestazioni. Consideriamo
come blocco minimo di trasferimento 4 word ed il seguente modello dei tempi di accesso:
• 1 ciclo per inviare l’indirizzo,
• 6 cicli per accedere al data,
• 1 ciclo per ricevere il data.
I tempi per il trasferimento del blocco minimo considerato (4 word) sono:
• “Bus Semplice” : 32 cicli.
n° words x
(n° cicli per inviare l’indirizzo +
n° cicli per accedere al data +
n° cicli per ricevere il data) =
=4 x (1+6+1) = 32 cicli.
•
“Bus Largo” : 8 cicli.
(n° cicli per inviare l’indirizzo +
n° cicli per accedere al data +
n° cicli per ricevere il data) =
=(1 + 6 + 1) = 8
• 4-way interleaving: 11 cicli.
n° cicli per inviare l’indirizzo +
n° cicli per accedere al data +
n° cicli per ricevere il data x n° words =
=1 + 6 + 1 x 4 = 11.
Per ottimizzare la struttura del modello “interleaving” dovrebbe essere scelto un numero di
banchi di memoria pari al numero di periodi di clock per accedere ad una word di un banco; in
questo modo si otterrebbe una word ad ogni ciclo di clock. Il problema per cui cio’ non e’
sempre attuabile (ad es. nei PC) e’ il fatto che il numero di banchi di cui si dispone tende a
diminuire perche’ da un lato le capacita’ dei chip aumentano e dall’altro la memoria totale
necessaria tende ad essere relativamente minore (Tabella 16.6). Nelle attuali DRAM lo
sfruttamento dell’interleaving e’ trasferito all’interno dei chip di memoria (v. Cap. 16.7)
16.14
Tabella 16.6: Evoluzione DRAM
4 MB
‘86
1 Mb
‘89
4 Mb
32
8
Memoria tipica di un PC
8 MB
16 MB
16
‘92
‘96
‘99
‘02
16 Mb 64 Mb 256 Mb 1 Gb
4
8
2
32 MB
4
1
64 MB
8
2
128 MB
4
1
256 MB
8
2
(da Pete MacWilliams, Intel)
16.6.5 Prestazioni del sottosistema di memoria
Per analizzare le prestazioni del sottosistema di memoria dobbiamo innanzitutto stabilire un
parametro di misurazione. Una scelta possibile e’:
PMEM =
1
t RC
Se consideriamo il caso di una DRAM con tRAC = 50ns, e tRC = 84ns, si ottiene:
1
PMEM =
≅ 12MB / s .
84 ⋅ 10 −9
Nel caso invece della EDO DRAM, assumendo per il primo accesso 50ns, ma per quelli
1
successivi 30ns, si ottiene PMEM =
≅ 33MB / s .
30 ⋅ 10 −9
16.15
16.7 DRAM SINCRONE
16.7.1 SDRAM (Synchronous DRAM)
Le SDRAM sono la naturale evoluzione delle EDO DRAM. Hanno dei segnali in più rispetto alle
DRAM: CLK(clock), che da la temporizzazione, il CKE (clock enable) che decide se attivare o
meno il segnale di clock,il DQM (DQ mask) che agisce su DQ principalmente come un Output
Enable, il BA (bank address) che regola gli accessi ai banchi interni (per maggiori dettagli si
veda ad es. [5]) Inoltre il clock abilita il pipelining nel datapath fra gli array interni e DQ.
Nelle SDRAM sono presenti banchi interni che si possono selezionare dall’esterno. Il primo
accesso ha una latenza di 1-3 cicli, ma gli accessi sequenziali consentono di ottenere i dati ad
ogni ciclo di clock, sfruttando una sorta di interleaving interno al chip.
Considerando una latenza media di 2 cicli, per una SDRAM a 100MHz, si ottiene:
PMEM =
100MHz
≅ 50Mb / s .
2
16.7.2 DDR (Double Data Rate) SDRAM
Sono la naturale evoluzione delle SDRAM. L’output ha luogo su entrambi i fronti del clock,
consentendo quindi di raddoppiare la banda di uscita. Anche qui si hanno segnali in più rispetto
alle SDRAM: si ha il CLK#(clock complementare) che permette di ricevere i dati anche sul
fronte in discesa, BA[1…0] (indirizzo di banco su due bit), VDDx8, VSSx8. Sono presenti 4
banchi interni indipendenti. Per fornire più banda interna si usa un datapath interno più largo.
(per maggiori dettagli si veda ad es. [6]) Con un clock esterno a 100MHz si ha un clock interno
a 143MHz. Altri piccoli accorgimenti interni consentono di operare a frequenze così alte.
Per una DDR SDRAM a 100MHz si ottiene come prestazione massima:
PMEM = 286 MB / s (143 × 2) .
16.7.3 SLDRAM (Sync-Lynk DRAM)
Sono una combinazione tra le memorie SDRAM e quelle DDR SDRAM, in modo da ottenere
attraverso, l’aggiunta di un protocollo a pacchetti command/address/control, le prestazioni di
picco PMEM = 400 Mb / s a 400MHz. I segnali esterni hanno caratteristiche totalmente diverse
da quelle delle DRAM.
16.7.4 DRDRAM (Direct RDRAM Rambus)
Queste memorie presentano una configurazione simile alle SLDRAM per quanto riguarda il
protocollo a pacchetti, ma la sua struttura concettuale risulta essere molto diversa dalle
DRAM. Le prestazioni di picco PMEM = 400 Mb / s a 400MHz.
16.7.5 SIMM (Single In-line Memory Module)
Sono state il primo tentativo di fornire uno standard per raggruppare chip di memoria.
Inizialmente furono prodotte con 30 pin, in seguito fu estesa a 72 pin, in modo da consentire
un bus dati fino a 32 bit.
16.7.6 DIMM (Dual In-line Memory Module)
Hanno 168 pin disposti su una schedina su cui sono collocati i chip di memoria,
caratterizzata da un bus dati da 64 bit; i moduli DIMM stanno sostituendo i moduli SIMM
(Single In-Line Memory Module), nei quali il bus dati è da 32 bit. Nei moduli SIMM (72 pin), i
contatti opposti, sulle 2 facce della scheda, sono uniti e formano un contatto unico; nei DIMM
16.16
(168 pin), i contatti opposti sono elettricamente isolati e formano due contatti separati. Negli
standard successivi (2004) i moduli DIMM hanno 184 pin.
16.7.7 RIMM(marchio del consorzio Rambus)
Questo standard prevede una larghezza di bus di 32bit e ha lo stesso connettore delle DIMM
a 100MHz standard. Questo standard non ha avuto molto successo.
16.10 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] V. Hamacher, G. Vranesic, G. Zaky, “INTRODUZIONE ALL’ARCHITETTURA DEI CALCOLATORI”
McGraw-Hill 1997.
[3] G. Bucci, "Architettura dei Calcolatori Elettronici", McGraw-Hill, 2001, ISBN 88-386-0889-X.
[4] P. Corsini “DALLE PORTE AND OR NOT AL SISTEMA CALCOLATORE” Edizioni ETS 2000
[5] SITO JEDEC: http://www.jedec.org/download/search/4_20_02R13.PDF
[6] SITO JEDEC: http://www.jedec.org/download/search/JESD79E.pdf
16.17
LEZIONE 17
Introduzione alla memoria cache (parte prima)
17.1 INTRODUZIONE
Le cinque componenti classiche di un calcolatore sono l’unità d’ingresso, l’unità di uscita, la
memoria, l’unità di elaborazione e l’unità di controllo.
La figura 17.1 mostra l’organizzazione tipica di un calcolatore, la quale è indipendente dalla
tecnologia hardware utilizzata. In questo capitolo analizzeremo le unità di memoria.
Processore
Input
Parte di
Controllo
Memoria
Memoria
Parte
Datapath
Output
Figura 17.1 Struttura di un Calcolatore.
Esistono almeno due tipi di memoria: la memoria principale e la CACHE.
La memoria principale è tipicamente una memoria ampia ma lenta, mentre la memoria CACHE è
una memoria più piccola e più veloce. Come visto nel Capitolo 16, anche i costi sono diversi.
17.1.1 Principio di localita’ dei programmi
Questo principio sta alla base del modo in cui funzionano i programmi, e dice che in un dato
istante di tempo i programmi fanno accesso ad una porzione relativamente piccola del loro
spazio di indirizzamento. Ovvero un programma ad un certo istante non ha la necessità di
accedere a tutto il suo codice ed a tutti i suoi dati con uguale probabilità.
Il principio di località viene sfruttato implementando la memoria di un calcolatore sotto forma
di gerarchie di memoria.
I principali tipi di località individuabili in un programma sono:
- la Località Temporale
- la Località Spaziale
17.1.1.1 Localita’ temporale
E’ probabile che un oggetto a cui il programma ha fatto riferimento venga nuovamente
richiesto in tempi brevi.
Per esempio molti programmi contengono dei cicli, per cui le istruzioni ed i dati saranno
probabilmente richiesti ripetutamente, portando ad un buon grado di località temporale.
17.1.1.2 Localita’ spaziale
E’ probabile che gli oggetti che si trovano vicino ad un oggetto a cui il programma ha fatto
riferimento vengano richiesti in tempi brevi.
Per esempio, poiché durante l’esecuzione di un programma le istruzioni sono normalmente
richieste in modo sequenziale, i programmi hanno un’elevata località spaziale.
17.1
17.1.2 Gerarchia di memoria
Conseguenza del principio di località è che risulta vantaggioso organizzare la memoria secondo
una gerarchia.
La gerarchia di memoria trae vantaggio dalla località temporale mantenendo i dati a cui si è
fatto accesso di recente in posizioni più vicine alla CPU, e dalla località spaziale spostando
verso i livelli superiori della gerarchia blocchi composti da molte parole consecutive.
CPU
Livelli della
Gerarchia
di Memoria
Livello 1
Livello 2
Tempo di Accesso
(distanza dal
processore)
Livello N
Dimensione della Memoria a ciascun livello
Figura 17.2 Struttura generale della gerarchia di memoria.
Come mostrato in figura 17.2, una gerarchia di memoria usa memorie più piccole e di
tecnologia più veloce vicino al processore.
Se i dati richiesti dal processore compaiono in qualche blocco nel livello superiore, si dice che
si è verificato un successo nell’accesso (ovvero hit) e vengono quindi elaborati velocemente. Se
il dato non è presente ai livelli superiori, la richiesta viene classificata come fallimento
nell’accesso (ovvero miss) e si deve ricorrere ai livelli inferiori nella gerarchia per recuperare
il blocco contenente il dato richiesto, tali livelli sono più capienti ma più lenti.
Il caso migliore si ha quando la frequenza di hit (hit rate) è sufficientemente elevata, in tal
caso la gerarchia di memoria ha complessivamente un tempo di accesso effettivo vicino a
quello del livello più alto (quindi la più veloce) ed una capacità vicina a quella del livello più
basso (quindi la più capiente).
A partire da queste differenze è vantaggioso costruire il sottosistema di memoria come una
gerarchia di livelli, con la memoria più veloce vicina alla CPU e quella più lenta e meno costosa
ai livelli inferiori, come mostrato in figura 17.3.
17.2
Velocità
Più veloce
CPU
Memoria
CACHE
Capacità
Costo
Più piccola
Più alto
Più grande
Più basso
Memoria
RAM
Più lenta
Dischi Magnetici
Figura 17.3 Struttura fondamentale della gerarchia delle memorie.
La gerarchia di memoria può consistere di più livelli, ma in ogni istante di tempo i dati sono
copiati solamente tra ciascuna coppia di livelli adiacenti. Per cui per analizzare il
comportamento del passaggio dei dati tra i vari livelli è conveniente concentrarsi solo su due
di essi.
Attualmente vi sono tre tecnologie principali per la costruzione della gerarchia di memoria: la
prima è l’utilizzo delle DRAM (Dynamic Random Access Memory) nella realizzazione della
memoria principale, la seconda è l’utilizzo delle SRAM (Static Random Access Memory) nella
realizzazione delle CACHE e la terza tecnologia è utilizzata per implementare il livello più
capiente e più lento della gerarchia, ovvero i dischi magnetici.
Il tempo di accesso ed il costo per bit cambiano notevolmente tra queste tre tecnologie.
La DRAM ha un costo per bit inferiore alla SRAM, ma d’altra parte risulta essere più lenta.
I dischi magnetici, a loro volta, hanno un costo per bit inferiore alle prime due tecnologie, ma
il loro tempo d’accesso è di gran lunga più elevato.
Come si evince dalla figura 17.3, il livello superiore è più piccolo e veloce (poiché usa una
tecnologia più costosa) rispetto a quello inferiore. Ad un certo istante il nome dell’entità di
informazione considerata è chiamata blocco o pagina.
Il blocco è costituito da un insieme di indirizzi di memoria adiacenti, la cui dimensione è
dipendente dalla tecnologia scelta dal costruttore dell’hardware.
17.2 FONDAMENTI SULLE CACHE
Il termine CACHE è stato usato per la prima volta per indicare il livello della gerarchia tra la
CPU e la memoria principale nel primo calcolatore commerciale dotato di tale livello [1]. Esso
indica anche qualsiasi tipo di gestione della memoria che trae vantaggio dalla località degli
accessi.
17.3
17.2.1 Funzionamento di una cache
Esamineremo una CACHE semplificata in cui la CPU richiede una parola per volta e ciascun
blocco contiene una sola parola. In figura 17.4 è mostrata una CACHE di questo tipo prima e
dopo la richiesta di un dato, inizialmente non presente nella CACHE stessa.
Prima del riferimento a Xn
Dopo il riferimento a Xn
X4
X1
Xn-2
X4
X1
Xn-2
Xn-1
X2
Xn-1
X2
Xn
X3
X3
Figura 17.4 CACHE prima e dopo il riferimento ad una parola Xn inizialmente non presente.
Inizialmente la CACHE contiene un insieme di dati richiesti di recente (X1, X2, …, Xn-1).
Ad un certo punto il processore richiede la parola Xn non presente nella CACHE: questo è un
classico esempio di miss e la parola Xn viene quindi trasferita dal livello di gerarchia di
memoria sottostante alla CACHE, ovvero, basandoci sulla struttura della gerarchia vista nella
figura 17.3, si ha un trasferimento da un livello inferiore ad uno superiore.
17.2.1.1 Posizionamento dei dati nella cache
Il modo più semplice per associare una posizione della CACHE a ciascuna parola di memoria è
di determinare la posizione della CACHE sulla base dell’indirizzo della parola in memoria. Tali
CACHE sono dette a corrispondenza diretta, poiché a ciascun blocco di memoria corrisponde
esattamente una posizione nella CACHE.
Come esempio la figura 17.5 mostra una CACHE a corrispondenza diretta di otto elementi e
con blocchi di dimensione esattamente pari ad una parola.
Si possono notare le corrispondenze tra gli indirizzi di memoria compresi tra 00001due (1dieci) e
11101due (29dieci) e le posizioni 001due (1dieci) e 101due (5dieci) nella CACHE.
Poiché a ciascuna locazione nella CACHE corrispondono più indirizzi (ad esempio alla posizione
001 corrispondono i quattro indirizzi della memoria che terminano proprio con 001), per poter
riconoscere a quale degli indirizzi possibili di memoria corrisponde l’indirizzo di CACHE
cercato, si aggiunge un tag, che indicheremo notazionalmente con T, nell’indirizzo di CACHE,
contenente esattamente i bit dell’indirizzo di memoria non deducibili dall’indirizzo di CACHE.
17.4
000
001
010
011
100
101
110
111
Cache
Memoria
00001
00101
01001
01101
10001
10101
11001
11101
Figura 17.5 Cache a corrispondenza diretta di otto elementi.
17.2.2 Definizioni generali sulle cache
Esistono anche CACHE con più blocchi memorizzati allo stesso indirizzo di CACHE dette
CACHE associative (verrano esaminate nel capitolo successivo). In tal caso il numero di blocchi
appartenenti allo stesso indirizzo è detto:
A = “grado di Associatività” della CACHE o “numero di vie”, con A є {1,2, …, Amax}
Inoltre definiamo:
B = dimensione del blocco di CACHE in byte
C = capacità della CACHE in byte
Tipicamente, ma non necessariamente B, C sono una potenza di 2. In tal caso:
B = 2b con b є {1,2, …, bmax}
C = 2c con c є {1,2, …, cmax}
e sussistono le seguenti relazioni:
NC = C / B = 2c-b Æ Numero di blocchi della CACHE
NS = C / (A*B) = 2c-(a+b) Æ Numero di indirizzi disponibili nella CACHE (Numero di SET)
NM = M / B = 2m-b Æ Numero di blocchi della Memoria
dove M è la dimensione della memoria in byte e m = log2(M).
Nel caso di CACHE ad accesso diretto si ha A pari ad 1 e quindi NS = NC.
Bit di validita’
E’ necessario anche un meccanismo per il riconoscimento dei blocchi della CACHE che non
contengono informazioni valide, come ad esempio accade all’accensione del sistema. Il valore
del tag di tali blocchi deve essere ignorato, perché privo di significato. Il metodo più comune
è quello di aggiungere un bit di validità (bit V) per indicare se una cella della CACHE contiene
un indirizzo valido. Se il bit non vale 1, il blocco non viene considerato corrispondente ad alcun
indirizzo. Quando il blocco viene caricato in CACHE viene posto a 1 il bit di validità.
17.5
17.2.3 FORMALIZZAZIONE DEL FUNZIONAMENTO DELLA CACHE
Siano:
XM = l’indirizzo di un blocco di memoria
XS = l’indirizzo di un blocco di CACHE
Definiamo inoltre (usando una sintassi C-like) le varie informazioni che troveremo all’indirizzo
di cache XS:
D = contenuto del blocco memorizzato in CACHE = XS->D = YD
T = tag del blocco memorizzato in CACHE = XS->T= YT
V = bit di validità del blocco memorizzato in CACHE = XS->V= YV
Supponiamo per il momento, per semplicita’, di fare soltanto accessi in lettura per prelevare
informazioni. La cache, oltre a fornire la memoria i blocchi (D), i tag (T), e i bit ausiliari (quali
il bit V) dovra’ fornire un meccanismo per leggere il blocco in cache in maniera simile alla
funzione FM(XM) che consente leggere il blocco dalla memoria. Tale meccanismo dovra’ essere
quasi sempre piu’ veloce a fornire il dato rispetto ad un accesso in memoria; chiameremo tale
funzione:
• FC Funzione di prelievo del dato in Cache
La funzione FC permette di ottenere il contenuto del blocco richiesto (D) una volta specificato
un determinato indirizzo di memoria (XM). Mentre sappiamo che FM: MEM[.] ovvero per
ottenere D dall’array di memoria MEM basta indicizzare tale array con l’indirizzo XM, per
esplicitare FC occorre introdurre altre tre funzioni: FT per generare il tag a partire da XM, FS
per ricavare il potenziale indirizzo del blocco di cache a partire XM ed infine una funzione FH
che permetta di sapere se un blocco è presente in CACHE o meno, cioè che verifichi se sia
avvenuto un hit od un miss. Quindi:
• FT Funzione di generazione del tag
La funzione FT a partire dall’indirizzo del blocco di CACHE XM genera XT:
XT = FT(XM)
• FS Funzione di piazzamento del blocco
La funzione FS a partire dall’indirizzo del blocco di CACHE XM genera XS:
XS = FS(XM)
• FH Funzione di Hit
La funzione FH a partire da XT, T (ovvero XS->T) e V (ovvero XS->V) fornisce hit (1) o miss (0):
FH = (XT == T && V == 1 ? 1 : 0)
Una volta stabilito che il dato e’ in cache risulta possibile prelevarlo, quindi:
FC : (FH == 1 ? D : ‘----’)
Chiaramente FC = FC(XM) infatti, con le debite sostituzioni si ricava:
FC : ((FT (XM) == FS(XM)->T && FS(XM)->V == 1 ? 1 : 0) == 1 ? FS(XM)->D : ‘----’)
Nel caso delle cache fino a qui esaminate (ad accesso diretto) si ha:
FS: (mod NS) ovvero
FS (XM) = XM mod NS
17.6
FT: (div NS)
ovvero
FT (XM) = XM div NS
Nel capitolo successivo vedremo come generalizzare le funzioni FC ,FS, FT, FH al caso di cache
con piu’ vie.
17.2.2.1 Esempio1
Analizzando la CACHE di figura 17.6, ovvero una CACHE ad accesso diretto con A = 1, B = 4
byte, C = 4 Kbyte, M = 4 Gbyte e dove X è l’indirizzo al byte, si possono calcolare gli indirizzi
del blocco di memoria DRAM (XM ), del blocco di CACHE (XS) e del tag (XT) sfruttando le relazioni
ricavate nei paragrafi precedenti. Si ha infatti:
XM = X / B = X / 22
XT = XM / NC = XM / (C / B) = XM / 210
XS = XM mod NS = XM mod C / B = XM mod 210
XM
13 12 1 1
31 30
210
X
Byte
offset
Tag
10
20
=XT
Index =X
S
Index Valid Tag
Data
0
1
2
1021
1022
1023
V
T
FH
Hit
20
D
32
Data
Figura 17.6 Schema logico di una CACHE ad accesso diretto.
17.2.4 Esempio2: cache a cui e’ presentata una sequenza di indirizzi
Prendiamo in esame una CACHE ad accesso diretto con: A=1, B=4, C=32. Descriviamo
sinteticamente il funzionamento, in tabella 17.1, in seguito ad una serie di richieste da parte
del processore. Si sa inoltre che:
XT = XM / NS = XM / (C / (A * B)) = XM * (A * B) / C = XM * 4 / 32
17.7
Tabella 17.1 Azione intrapresa in seguito a ciascun accesso alla memoria.
(XM)dieci
(XM)due
XT
XS
FH
22
26
22
26
16
3
16
18
10110
11010
10110
11010
10000
00011
10000
10010
10
11
10
11
10
00
10
10
110
010
110
010
000
011
000
010
0 (MISS)
0 (MISS)
1 (HIT)
1 (HIT)
0 (MISS)
0 (MISS)
1 (HIT)
0 (MISS)
Quando la parola di indirizzo 18 (10010) è portata nel blocco 2 (010) della CACHE, la parola
dell’indirizzo 26 (11010), che si trovava nel blocco 2 (010), deve essere sostituita dai dati
appena richiesti. Questo comportamento permette alla CACHE di trarre beneficio dalla
località temporale, infatti le parole richieste più di recente sostituiscono quelle a cui si è
fatto riferimento meno di recente. In una CACHE ad accesso diretto esiste un solo posto in
cui porre il dato appena richiesto, perciò vi è una sola possibile scelta a riguardo di che cosa
debba essere sostituito. Nel caso di una CACHE associativa esistono A possibili posti liberi in
cui rimpiazzare il blocco richiesto (vedi capitolo 18).
Il numero totale dei bit richiesti per una CACHE è funzione della sua dimensione e della
lunghezza degli indirizzi della memoria.
Nell’architettura MIPS abbiamo indirizzi a 32 bit, quindi una CACHE ad accesso diretto di
capacità pari a 2n word e con blocchi di una word (4 byte) richiederà un tag di ampiezza pari a
32 – (n + 2) bit, poiché 2 bit sono usati per l’offset del byte ed n bit per l’indice.
Il numero totale di bit in una CACHE ad accesso diretto è quindi:
2n x (dimensione blocco + dimensione tag + dimensione bit di validità)
Nel caso di architetture MIPS il numero di bit è pari a:
2n x (32 + (32 – n – 2) + 1) = 2n x (63 – n )
17.2.3 Come trarre beneficio dalla localita’ spaziale
Per poter trarre beneficio dalla località spaziale è necessario che i blocchi della CACHE siano
più ampi di una sola parola, contrariamente al tipo di CACHE considerata finora.
Quando si verifica un miss, si leggeranno quindi diverse parole adiacenti, che hanno un’elevata
probabilità di essere richieste a breve termine. Confrontando la figura 17.7, che riporta una
CACHE con 64 KB di dati organizzata in blocchi di quattro parole (16 Byte) ciascuno, con la
figura 17.6, che riporta una CACHE delle stesse dimensioni ma con blocchi di una sola parola,
notiamo che nella CACHE di figura 17.7 l’indirizzo ha un campo aggiuntivo per indicizzare il
blocco, detto Offset nel Blocco. Tale campo viene utilizzato per controllare il multiplexer
(MUX) che seleziona la parola richiesta tra le quattro del blocco prescelto. Il numero totale
di campi tag e di bit di validità è inferiore nella CACHE con blocchi di più parole, perché
ciascuno di essi viene usato per quattro parole. Per ricavare il blocco di CACHE relativo ad un
17.8
dato indirizzo si può utilizzare lo stesso metodo che si era utilizzato nella CACHE con blocchi
di una parola, ovvero si utilizzano FS e FT come nel caso di CACHE con blocchi di una sola
parola. L’indirizzo del blocco è l’indirizzo stesso espresso in byte diviso la dimensione del
blocco espressa in byte.
31
16 15
16
4 32 10
12
2 Byte
offset
Tag
Index
V
Block offset
16 bits
128 bits
Tag
Data
•A = 1
•B = 16 byte
•C = 64 Kbyte
•M = 4 Gbyte
X indirizzo al byte
XM = X / B
= X / 24
4K
entries
16
32
32
32
32
XT = XM / NS
= XM / (C / B)
= XM / 212
XS = XM mod NS
= XM mod C / B
= XM mod 212
Mux
32
Data
Hit
Figura 17.7 CACHE di 64 KB che utilizza blocchi di quattro parole (16 byte).
17.2.5 Il miss rate
Le condizioni di miss nel caso delle letture sono trattate allo stesso modo sia per CACHE con
blocchi di più parole che per CACHE con blocchi di una sola parola, infatti quando avviene un
miss si preleva sempre l’intero blocco. Mentre, durante le scritture, gli hit ed i miss devono
essere gestiti diversamente nei due casi. Questo perché, nelle CACHE con blocchi di più
parole, non è possibile scrivere all’interno di ogni singolo blocco solamente il tag ed il dato.
Per capirne il motivo, si ipotizzi che vi siano due indirizzi di memoria, X1 ed X2, entrambi
corrispondenti al blocco XS della CACHE, il quale sia un blocco di quattro parole che al
momento contiene X2. Si consideri ora una scrittura all’indirizzo X1 che sovrascriva
semplicemente il dato ed il tag all’interno del blocco XS: dopo la scrittura, il blocco XS avrà il
tag corrispondente ad X1, mai dati contenuti in XS corrisponderanno ad una parola di X1 ed a
tre parole di X2.
In generale la frequenza di miss si riduce all’aumentare della dimensione dei blocchi come si
evince dal grafico in figura 17.8, tuttavia la località spaziale tra le parole dello stesso blocco
si riduce all’aumentare della sua dimensione, per cui il miglioramento della frequenza di miss si
ridurrà sempre di più fino a che essa non tenderà ad aumentare.
Un ulteriore problema associato all’aumento della dimensione dei blocchi è l’aumento del costo
dei miss, infatti la penalità dovuta ad un miss è determinata dal tempo necessario a caricare il
blocco dal livello immediatamente inferiore nella gerarchia di memoria e scriverlo nella
CACHE. Questo tempo è composto da: la latenza per leggere la prima parola ed il tempo di
17.9
trasferimento per il resto del blocco. Se non si modifica il sottosistema di memoria il tempo
di trasferimento (quindi il costo di una miss) aumenta al crescere della dimensione del blocco.
Perciò l’aumento della penalità introdotta da una miss supera la riduzione della frequenza di
miss stessi quando i blocchi sono troppo grandi, riducendo di conseguenza le prestazioni della
CACHE.
Capacità
della
CACHE
40%
35%
30%
1 KB
8 KB
16 KB
64 KB
25%
256 KB
M
i
s
s
r
a
t
e
20%
15%
10%
5%
0%
4
16
64
256
Dimensione del blocco (byte)
Figura 17.8 Dipendenza del Miss rate dalla dimensione del blocco.
17.3 VALUTAZIONE DELLE PRESTAZIONI DELLE CACHE
Possiamo definire un tempo medio di accesso alla memoria come:
AMAT (Average Memory Access Time) = tH * h + tM * m
dove:
tH = tempo di hit, h = probabilità di avere un hit (hit rate)
tM = tempo di miss, m = probabilità di avere un miss (miss rate)
Estendendo i risultati del capitolo 4, non considereremo più una memoria perfetta, cioè che
risponde in un tempo tH, ma considereremo un tempo di penalty (tPENALTY), dovuto all’attesa dei
dati dalla memoria principale, che influirà sulla risposta della CPU. Quindi poiché:
tM = tH + tPENALTY
si ha:
AMAT = tH * h + (tH + tPENALTY) * m = tH * (h + m ) + tPENALTY * m = tH + tPENALTY * m
L’equazione delle prestazioni di una CPU (vedi capitolo 4) è nel caso ideale:
TCPU = CCPU x TC = NCPU x /CPIideale x TC
dove:
TCPU = tempo di esecuzione della CPU
17.10
CCPU = cicli di esecuzione della CPU
TC = periodo di clock
NCPU = numero di istruzioni dinamicamente eseguite dal processore
/CPIideale = cicli per istruzione medi nel caso ideale
Considerando il caso non ideale in cui, come detto precedentemente, non vi sia un passaggio
immediato dei dati dalla memoria alla CPU (caso reale), otterremo un CPIeffettivo (medio).
Definendo:
NREF = numero totale di riferimenti alla memoria
/RINST = (NREF / NCPU) = riferimenti medi per istruzione
CPEN = cicli di penalty della CPU
si ha:
/CPIstallo = /RINST x m x CPEN
/CPIeffettivo = /CPIideale + /CPIstallo
TCPU = NCPU x /CPIeffettivo x TC =
= NCPU x (/CPIideale + /CPIstallo) x TC =
= NCPU x (/CPIideale + (/RINST ) x m x CPEN)) x TC
Per comprendere il significato di /RINST, consideriamo il seguente codice MIPS:
ADD R1, R2, R3
LW R1, 0(R2)
genera 1 riferimento (alla memoria istruzioni)
genera 2 riferimenti (1 alla memoria istruzioni + 1 alla memoria dati)
Quindi NREF = 3 e conseguentemente: /RINST = (NREF / NCPU) = 3 / 2 = 1,5
Ciascuna classe di istruzioni genera un certo numero di riferimenti.
RINST,K = numero di riferimenti generati dalla classe K
•
•
RINST, LOAD = 2
RINST, ADD = 1
Quindi nel nostro esempio:
type
RINST =
∑
k =1
RINST,K = 1+2 = 3
17.3.1 Esempio di impatto sulle prestazioni
Supponiamo di avere un processore a 200 MHz (5 ns per ciclo) con:
/CPIideale = 1,1
50% aritmetico/logiche
30% load/store
20% control
17.11
Supponiamo che il 10% delle operazioni in memoria subisca miss con CPEN = 50 cicli di clock.
Sappiamo che:
/CPIeffettivo = /CPIideale + (/RINST x m x CPEN) = 1,1 + (0,30 * 0,10 * 50) = 1,1 + 1,5 = 2,6 cicli
Per il 58% (1,5 / 2,6) del tempo il processore attende la memoria. Un 1% di miss rate per le
istruzioni aggiunge 0,5 cicli al CPI (medio)effettivo. In tal caso la situazione è quella mostrata in
figura 17.9.
Miss Istruzioni
(0,5)
CPI Ideale
(1,1)
16%
35%
Miss Dati
(1,5)
49%
Figura 17.9 Diagramma della composizione del CPI effettivo.
17.4 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
17.12
LEZIONE 18
Introduzione alla memoria cache (parte seconda)
18.1 COME ORGANIZZARE I BLOCCHI DELLA CACHE
La memoria cache può essere organizzata in vari modi mantenendo costante la dimensione
totale.
18.1.1 Cache direct-mapped (ad accesso diretto)
Con questo tipo di organizzazione, il tag (XT) ricavabile dall’indirizzo di memoria richiesto (X)
viene confrontato con quello memorizzato in cache (T), all’indirizzo di cache selezionato
dall’indice (XS), ricavato dall’indirizzo specificato
B lo c k
T ag
D a ta
0
1
2
3
4
5
6
7
Figura 18.1: Esempio, cache direct mapped (o direct access) ad 8 blocchi.
18.1.2 Cache Set-Associativa (Set-Associative)
Per ridurre i conflitti sullo stesso indirizzo di cache (XS), è possibile organizzare i blocchi in
modo diverso raggruppandoli in insiemi o set in corrispondenza dello stesso indirizzo di cache
(Xs). Il numero totale di blocchi appartenenti allo stesso set è detto numero di vie e indica il
grado di associatività della cache (v. figura 18.2).
Set
Ta g
D a ta
Ta g
D a ta
0
1
S et
2
T ag
D a ta
T ag
D a ta
Tag
D a ta
Tag
D a ta
0
3
1
(a)
(b)
Figura 18.2: Esempio, Cache set-associative a 8 blocchi: (a) cache a 2 vie (A=2); (b) cache a 4 vie (A=4).
Detti quindi:
A
B
C
X
XM
XT
XS
si ha che:
=
=
=
=
=
=
=
Associatività (numero di blocchi raggruppati nello stesso set)
Dimensione del blocco
Dimensione totale della cache
Indirizzo al byte
Indirizzo in memoria del blocco (=X/B)
Indirizzo del tag
Indirizzo del set
18.1
NS
XS
XT
dove :
NS
=
=
=
=
C/(B•A)
XM mod NS
XM div NS
Numero di set complessivi della cache
Per individuare il blocco all’interno dello set è necessario a questo punto confrontare in
parallelo i tag dei vari blocchi del set con quello (XT) ricavabile dall’indirizzo (X). La funzione
di hit per una cache set-associative risulta quindi essere:
FH : (∀k=1…A, XT == YT,k && YV,k == 1 ? 1 : 0)
dove:
YT,k:
YV,k:
Tag del k-esimo blocco del set
Bit di validità del k-esimo blocco del set
XM
31 30
12 11 10 9 8
3210
X
XT
Index
0
1
2
V
Tag
Data
8
XS
22
V
Tag
Data
V
Tag
Data
V
Tag
Data
253
254
255
YT,0
YT,1
YD,0
YT,2
YD,1
YT,3
YD,2
22
32
YD,3
YV,0
YV,1
YV,2
YV,3
4-to-1 multiplexer
Hit
Data
Figura 18.3: Cache set-associative a 4 vie di dimensione 4KB e dimensione del blocco 4 byte.
18.1.3 Cache Completamente Associativa (Fully-Associative)
E’ possibile organizzare i blocchi anche in modo tale da avere un unico grande set contenente
tutti i blocchi; si parla in questo caso di cache completamente associativa.
18.2
T ag
D a ta
Tag
D a ta
Tag
D a ta
Tag
D a ta
Tag
D a ta
Tag
D a ta
Tag
D a ta
Tag
D a ta
Figura 18.4: Esempio, cache full-associative a 8 blocchi.
Poiché in questo caso si ha solo un set, si ha che:
NS = 1 Æ
XT = XM
XS = 0 (non occorre indirizzare il set)
18.2 POLITICA DI RIMPIAZZAMENTO
Quando il dato non è in cache e il set in cui si dovrebbe collocare tale dato è pieno, è
necessario fare preventivamente posto per il nuovo dato. In tal caso, si parla di
rimpiazzamento con riferimento al blocco da scartare per far posto al nuovo blocco. In
dettaglio, i passi da seguire per effettuare questa operazione sono:
1. scelta del blocco da rimpiazzare ;
2. eventuale riscrittura in memoria principale (si parla di “eventuale” riscrittura perché
adottando alcuni particolari politiche è possibile, sotto certe condizioni, evitare la
riscrittura in memoria);
3. caricamento del nuovo blocco nella locazione liberata.
18.2.1 Scelta del blocco da rimpiazzare
La scelta del blocco da rimpiazzare può seguire varie politiche:
• Random: Si sceglie blocco a caso;
questa politica è veloce ed economica ma ha delle scarse prestazioni.
• LRU (Least Recently Used): Si sceglie il blocco usato meno recentemente;
questo approccio garantisce ottime prestazioni ma tuttavia può essere costoso da
implementare in quanto sono necessari dei circuiti aggiuntivi per individuare il blocco
meno recentemente usato. In dettaglio, per tenere traccia dell’utilizzo dei blocchi è
necessario inserire dei bit addizionali per ogni blocco e dell’hardware aggiuntivo che ne
gestisca l’evoluzione dell’uso dei blocchi. Spesso vengono usate delle versioni
semplificate (es. Intel usa pseudo-LRU) che hanno prestazioni leggermente inferiori.
• FIFO: Si sceglie il blocco caricato meno recentemente;
la politica ha prestazioni intermedie fra random e LRU, ma non è molto usata. A livello
di implementazione è necessario gestire la lista FIFO.
Analizziamo piu’ in dettaglio il funzionamento della politica LRU. Supponiamo di avere A
blocchi all’interno di un set. Per ordinare i blocchi in funzione del loro uso si rendono necessari
ceil(log2(A)) bit (es. A=4 occorrono 2 bit di LRU). Ad ogni accesso occorrerà aggiornare tutti i
bit associati ad ogni blocco del set, in modo da mantenere sempre aggiornata una lista dei
blocchi dal meno recentemente usato al più recentemente usato. Nel momento in cui si sceglie
il blocco la logica selezionerà il primo blocco della lista (es. il blocco con i bit LRU tutti a
zero).
Da esperimenti su molte applicazioni (es: benchmark SPEC) si rileva che questa politica è la più
vantaggiosa tra le tre qui analizzate poiché si avvantaggia maggiormente della località degli
accessi. Come svantaggio abbiamo però una complicazione dell’hardware richiesto soprattutto
18.3
all’aumentare dell’associatività ed inoltre un lieve rallentamento nell’accesso alla cache
rispetto al caso di accesso diretto.
18.3 GESTIONE DELLE SCRITTURE SU HIT
E’ possibile ridurre il costo delle operazioni di rimpiazzamento osservando che se un blocco
non viene modificato, non e’ necessario riscriverlo in memoria. Ciò si può fare introducendo un
bit, detto ‘bit M’ o anche ‘bit D’ (rispettivamente Modified o Dirty) che tiene traccia della
necessità o meno di effettuare la riscrittura in memoria. Cio’ comporta che durante
l’operazione di scrittura (nel caso in cui si abbia hit) si preveda di adottare una politica ben
precisa. Nel caso si faccia uso del bit M, si parlera’ di politica di scrittura su hit di tipo Write
Back (WB), mentre nel caso in cui non si faccia ricorso a questo bit si parla di politica di
scrittura su hit di tipo Write Through (WT).
Ricapitando sono possibili due approcci per gestire le politiche di scrittura su hit (v. figura
18.5):
• Write Back (WB): nel momento in cui si debba rimpiazzare un blocco, si verifica se il
bit M è attivo. Se lo è si procede al rimpiazzamento come si sarebbe fatto
normalmente, se non lo è si può evitare l’operazione di riscrittura in memoria in quanto
il blocco in cache contiene già lo stesso valore presente in memoria (questo tipicamente
accade quando il processore fa operazioni si sola lettura o fetch su quel blocco). Si
risparmia così del traffico fra cache e memoria. Come già detto, per implementare
questa soluzione è necessario aggiungere un ulteriore bit per ogni blocco (M=Bit di
modifica) per indicare se sia o meno necessario riscrivere il dato in memoria al
momento del rimpiazzamento.
• Write Through (WT): se il dato del blocco in cache viene scritto, si effettua
immediatamente la scrittura anche in memoria. Notare che l’impegno del bus in questo
caso non e’ per un intero blocco (o piu’ blocchi) ma solo per la parola (o dato) coinvolta
nell’operazione di scrittura. Questo approccio risulta essere più semplice da
implementare, ed inoltre puo’ comportare vantaggi se le operazioni di scrittura non
sono molto frequenti.
CPU
CPU
W
CACHE
W
(1) hit
CACHE
(1) hit e
rimpiazzamento
(2) rimpiazzamento
MEM
MEM
(a)
(b)
Figura 18.5: Gestione delle scritture su hit: (a) Politica Write Back, (b) Politica Write Through
18.4 GESTIONE DELLE SCRITTURE SU MISS
Anche nel caso di una operazione di scrittura, se si presenta un miss e’ possibile adottare
diversi tipi di politiche (v. figura 18.6):
• Write Allocate (WA): Il blocco viene caricato nella cache e poi viene effettuata la
scrittura.
18.4
•
Write Not-Allocate (WNA): Il blocco viene modificato direttamente nel livello
inferiore di memoria e poi viene caricato in cache.
CPU
CPU
W
CACHE
MEM
W
(1) miss e scrittura in
memoria
(1) miss
(3) scrittura in cache
CACHE
(2) lettura blocco
(2) lettura blocco
MEM
(a)
(b)
Figura 18.6: Gestione delle scritture su miss: (a) Politica Write Allocate, (b) Politica Write Not-Allocate
Anche in questo caso, vantaggi e svantaggi dei due approcci dipendono fortemente dal tipo di
applicazione che gira sulla macchina.
18.3 CACHE A PIÙ LIVELLI
Nel progetto delle memorie cache, così come in quello di tutto il sottosistema di memoria di
un calcolatore, ci sono esigenze contrapposte. In particolare a parità di capacità, le memorie
più veloci sono più costose di quelle lente, perciò sono in genere più piccole.
In termini architetturali, e’ possibile utilizzare piu’ livelli di cache per generare una memoria
cache al tempo stesso grande e con un buon tempo di accesso medio, in maniera (gerarchica)
del tutto simile a come si e’ strutturata la memoria in termini di cache e memoria principale.
Si usano così più livelli di memoria cache, ordinati in modo tale che man mano che si scende di
livello, le cache siano sempre più capienti anche se più lente. Solitamente viene adottato uno
schema che prevede almeno due livelli di cache (figura 18.6).
L1
L1 hit
time
L2
L2 hit
time
DRAM
Proc
L2 Miss Rate
L2 Miss Penalty
L1 Miss Rate
L1 Miss Penalty
Figura 18.6: Architettura a due livelli di cache
Ovviamente ogni singola latenza dovrà essere considerata separatamente, con il relativo miss
rate, nel calcolo del tempo medio di accesso alla memoria. Nel caso di due livelli di cache si
ottiene, ad esempio:
AMAT
=
L1 Hit Time + L1 Miss Rate * L1 Miss Penalty
L1 Miss Penalty
=
L2 Hit Time + L2 Miss Rate * L2 Miss Penalty
da cui:
AMAT
=
L1 Hit Time + L1 Miss Rate *
(L2 Hit Time + L2 Miss Rate * L2 Miss Penalty)
18.5
Valori tipici per architetture di questo tipo sono:
• L1 Æ Capacità: decine di KB
Hit-time: 1 (o 2) cicli di clock
Miss-rate (tipici): 1-5%
• L2 Æ Capacità: centinai di KB
Hit-time: qualche ciclo di clock
Miss-rate (tipici): 10-20%
In una struttura a più livelli, il tempo medio di hit si assesterà ad un valore intermedio fra
quelli delle due cache prese singolarmente.
18.6
LEZIONE 19
Memoria virtuale
19.1 INTRODUZIONE
I processori attuali sono in grado di indirizzare una memoria (logica) spesso molto più estesa
di quella (fisica) effettivamente presente.
Fino a che la quantità di memoria necessaria per eseguire un programma non supera quella
della memoria fisica è possibile fare in modo che gli indirizzi generati dal processore siano
compresi entro i limiti della memoria disponibile.
Il caso in cui sia necessaria più memoria rispetto a quella disponibile potrebbe venire trattato,
come succedeva prima che esistessero i sistemi di memoria virtuale, lasciando al
programmatore il problema di individuare quelle parti di programma che non hanno bisogno di
essere contemporaneamente presenti e prevedere esplicitamente il caricamento nella
memoria di quelle parti che di volta in volta servono (tecnica di overlay).
I sistemi di memoria virtuale forniscono una soluzione generale al problema di consentire
l’esecuzione di programmi che richiedono più memoria di quella effettivamente disponibile,
evitando particolari accorgimenti nella programmazione e permettendo una condivisione
efficiente e sicura della memoria fra molti programmi.
19.2 MEMORIA VIRTUALE
La memoria virtuale è basata, per quanto riguarda i trasferimenti tra memoria centrale e
memoria secondaria, su un meccanismo simile a quello che c’è tra memoria cache e memoria
centrale: cioè in modo tale da assicurare che nella prima (di estensione inferiore alla seconda)
siano copiate dalla seconda solo le informazioni che servono e solo negli intervalli di tempo in
cui servono.
Il programma viene caricato in uno spazio di memoria completamente vuoto dato che ogni
programma ha a disposizione tutti gli indirizzi di uno proprio spazio virtuale privato. In questo
modo si ottiene “gratuitamente” anche una esecuzione protetta dei programmi dato che un
programma non puo’ accedere allo spazio di indirizzamento privato di un altro programma.
19.2.1 Funzionamento.
In presenza della memoria virtuale, la CPU genera un indirizzo virtuale che viene tradotto da
un insieme di componenti hardware e software in un indirizzo fisico da utilizzare per accedere
alla memoria principale. Questa associazione prende il nome di MEMORY MAPPING (figura
19.1)
19.1
I n d iriz z i F isic i
I n d iriz z i V irtu al i
M em o ria
P rin cip al e
s w ap-i n
M eccani s mo di
t r aduzi one degli i ndir i zzi
DIS K
s w ap-out
s w ap-i n: por tar e
pagi na
i n m em or i a
s w op-out : por tar e
pagi na s ul
di s co
Figura 19.1. L’unità di trasferimento fra memoria e disco è la pagina che ha una dimensione
tipica di 4-16 KB. La Pagina Virtuale può essere sul disco anziché nella memoria fisica.
Nella memoria virtuale i blocchi di memoria (detti pagine) sono posti in corrispondenza tra un
insieme di indirizzi (indirizzi virtuali) e un altro (indirizzi fisici); questo perché una pagina può
trovarsi sulla memoria fisica oppure sul disco.
Nel caso in cui una pagina virtuale non sia presente nella memoria principale ma sul disco si
parla di PAGE FAULT.
Una delle funzioni più importanti della memoria virtuale, è di permettere a più processi la
condivisione di un’unica memoria principale, fornendo contemporaneamente la protezione della
memoria tra questi processi, e tra i processi e il sistema operativo. Le pagine fisiche sono
condivisibili facendo in modo che a due indirizzi virtuali corrisponda lo stesso indirizzo fisico:
questo permette a programmi diversi di condividere dati o codice. Questo meccanismo deve
anche garantire che un singolo processo non possa scrivere nello spazio di indirizzamento di un
altro processo utente, sia intenzionalmente che accidentalmente.
È necessario anche evitare che un processo possa leggere i dati di un altro processo. Non
appena si permette la condivisione della memoria principale, è necessario garantire la
protezione dei dati da parte di un processo dalle letture e scritture di altri processi; infatti
un processo potrebbe accidentalmente danneggiare i dati di un altro processo. Quando i
processi devono condividere informazioni secondo modalità controllate, il sistema operativo li
deve assistere, per evitare di accedere alle informazioni di un altro processo. Per limitare la
condivisione della memoria in lettura, possiamo utilizzare il bit di accesso in scrittura e
lettura, poiché questo può essere modificato soltanto dal sistema operativo.
19.2.2 Memoria virtuale: schema logico.
La Tabella delle Pagine (PAGE TABLE) realizza il meccanismo di traduzione delle pagine da
indirizzi virtuali a fisici.
La Tabella delle Pagine, che risiede in memoria, è una sorta di schedario dove accanto al nome
del libro c’è la sua collocazione fisica (che può essere nella stessa biblioteca o in altre
biblioteche).
19.2
Le pagine vengono caricate a blocchi di dimensione fissa; sia l’indirizzo virtuale che quello
fisico hanno due componenti:
• un numero di pagina virtuale;
• un offset interno alla pagina.
Il meccanismo di mapping non interviene per cambiare l’offset, e quindi p-bit meno
significativi degli indirizzi, relativi all’offset, non vengono modificati dall’operazione di
mapping e restano invariati (figura 19.2).
n + p = 32
I n d ir iz z o V ir t u a le
I d e n t if ic a t o r e d i
P a g in a V ir t u a le (V P N )
n
F u n z io n e d i M a p p in g
V ir t u a le Æ F is ic o
I d e n t if ic a t o r e d i
P a g in a F is ic a (P P N )
p
O f f s e t d i p a g in a
m
I n d ir iz z o F is ic o
m + p
Figura 19.2. Processo di traduzione del numero di pagina virtuale in un numero di pagina fisico.
Il numero di pagina fisica rappresenta la parte più significativa dell’indirizzo fisico mentre l’offset interno
alla pagina, che non viene modificato, ne costituisce la parte meno significativa; il numero di bit nel campo di
offset nella pagina determina quindi la dimensione delle pagine.
19.3
GESTIONE DEL PAGE FAULT
Per poter leggere una locazione della memoria principale si deve presentare un indirizzo
virtuale al sistema di memoria, dopodiché si accede alla Tabella delle Pagine per vedere se la
pagina è presente nella memoria.
Se la pagina è presente in memoria, accedo nuovamente alla RAM per prendere il dato.
Viceversa, se il processo tenta di accedere ad una pagina che non era stata caricata nella
memoria si ha un accesso ad una pagina contrassegnata come non valida e questa causa
un’eccezione di pagina mancante (PAGE FAULT TRAP). L’architettura di paginazione, Tabella
delle Pagine, una volta constatato attraverso un apposito bit di validità che la pagina non è
valida attiva bit di eccezione (PAGE FAULT) del processore ed avvia un’apposita routine del
sistema operativo denominata PAGE-FAULT HANDLER. Tale routine si occupa di effettuare
l’operazione di PAGE-SWAP, ossia di trasferire la pagina richiesta in memoria principale e
conseguentemente riscrivere su disco di una pagina che si presume non serva piu’ (pagina
“vittima”). A questo punto viene ripetuto l’accesso e questa volta la pagina si troverà nella
RAM.
L’indirizzo virtuale non è sufficiente per determinare dove la pagina si trova nel disco. Nei
sistemi con memoria virtuale occorre tener traccia della collocazione su disco di tutte le
pagine dello spazio di indirizzamento virtuale.
Dato che non possiamo conoscere in anticipo quando una certa pagina in memoria verrà
sostituita, il sistema operativo tipicamente genera uno spazio su disco per tutte le pagine di
un processo all’atto stesso della creazione di questo; tale struttura dati può far parte della
19.3
Tabella delle Pagine oppure può essere una struttura dati ausiliaria indicizzata nello stesso
modo in cui si indicizza la Tabella delle Pagine.
19.3.1 Collocazione e ritrovamento delle pagine
Ciascun processo ha la propria Tabella delle Pagine; per indicare la posizione della Tabella
delle Pagine in memoria, l’hardware comprende un registro che punta alla sua locazione iniziale:
tale registro viene detto registro della Tabella delle Pagine.
Poiché la Tabella delle Pagine contiene un elemento per ciascuna possibile pagina virtuale non
vi è necessità di un campo tag. Nella terminologia della memoria cache l’indice, che è utilizzato
per accedere alla Tabella delle Pagine, corrisponde all’intero indirizzo di blocco, ossia del
numero della pagina virtuale (figura 19.2).
La Tabella delle Pagine può essere molto grossa, e quindi devono essere trovate tecniche per
ridurla. Queste tecniche mirano a ridurre la quantità totale dello spazio richiesto e a
minimizzare la parte della memoria principale dedicata alla Tabella delle Pagine.
• Registro di limite - La tecnica più semplice dispone di un registro di limite che delimiti
la dimensione della tabella per un dato processo: se il numero di una pagina virtuale
diviene più grande del contenuto del registro di limite occorre aggiungere degli
elementi alla Tabella delle Pagine. Questa tecnica permette alla Tabella delle Pagine di
crescere man mano che il processo richiede più spazio. In questo caso la Tabella delle
Pagine sarà grande solo se il processo sta usando molte pagine dello spazio di
indirizzamento virtuale e lo spazio di indirizzamento si espande in una sola direzione.
• Due tabelle e due limiti - Molti linguaggi richiedono due aree di dimensioni espandibili;
una contenente lo STACK e l’altra detta HEAP contiene i dati allocati dinamicamente. A
causa di questa dualità risulta conveniente dividere la Tabella delle Pagine e lasciarla
crescere dall’indirizzo più alto a quello più basso e viceversa, in questo modo avremo
due tabelle e due limiti. L’uso di due tabelle divide lo spazio di indirizzamento in due
zone, di solito il bit più significativo dell’indirizzo determina quale zona e quale tabella
si debbano usare. Ciascun segmento può crescere fino a metà dello spazio di
indirizzamento. Questo tipo di suddivisione è usato in molte architetture tra cui quella
dei MIPS ed è invisibile al programma applicativo anche se non lo è al sistema
operativo.
• Tabella delle Pagine inversa - Un altro approccio alla riduzione della dimensione della
Tabella delle Pagine prevede di applicare una funzione di hash all’indirizzo virtuale in
modo che la struttura dati della Tabella delle Pagine abbia una dimensione determinata
solo dal numero delle pagine fisiche nella memoria virtuale; ovviamente il processo di
ricerca in una Tabella delle Pagine inverse è più complesso perché non è sufficiente
indicizzare la tabella delle pagine. In pratica nella Tabella delle Pagine c’è una entry
per ogni pagina fisica. I vantaggi di questo approccio sono che la dimensione scala con
la dimensione della memoria fisica anziché con quella della memoria virtuale e che la
dimensione è fissa a prescindere dal numero di processi. La tecnica è chiamata
“inversa” perche’ le entry sono indicizzate per pagina fisica (PPN) anziche’ per pagina
virtuale (VPN); in ogni caso la ricerca avviene fornendo un VPN e ottenendo un PPN che
è dedotto dalla posizione della VPN trovata.
19.4
VPN
V
D
V irtual Address
Virtual Page 10
PPN 0
1 0
PPN 1
PPN 2
0 0
1 1
Virtual Page 16
PPN 3
1 0
Virtual Page 520
PPN K
0 0
K=Numero di elem enti della tabella
V = bit di validità
D = dirty bit: bit che indica
se la pagina è stata
m odificata
C
O
M
P
A
R
E
Physical
Page
N um ber
PPN = physical page number
VPN = virtual page number
Figura 19.3. Tabella Inversa delle Pagine.
•
•
Paginazione della Tabella delle Pagine - La maggior parte degli elaboratori permette
alle tabelle delle pagine di venire a loro volta paginate. E’ sufficiente applicare lo
stesso principio fondamentale della memoria virtuale, permettendo alla tabella della
pagine di risiedere nello spazio di indirizzamento virtuale. Cio’ comporta pero’ la
generazione di un maggior numero di page-fault. Questo puo’ essere evitato ponendo
tutte le tabelle delle pagine nello spazio di indirizzamento del sistema operativo e
ponendo alcune tabelle delle pagine di sistema in una parte della memoria principale che
sia indirizzata fisicamente e che sia sempre presente e mai su disco.
Paginazione su piu’ livelli - Possiamo anche utilizzare più livelli di tabelle delle pagine in
modo da ridurre la quantità totale dello spazio. In questo schema la Tabella delle
Pagine e’ organizzata ad albero: il primo livello fornisce la corrispondenza per grossi
blocchi di dimensione fissa dello spazio di indirizzamento virtuale da 64 a 256 pagine
ciascuno; questa tabella viene detta tabella radice anche se questi sono invisibili agli
utenti. Ciascun elemento della tabella radice, indica se una o più pagine delle foglie
siano allocate; in caso affermativo punta ad una Tabella delle Pagine per quel segmento.
Per tradurre gli indirizzi si ricerca il segmento nella tabella radice mediante i bit più
significativi dell’indirizzo; se l’indirizzo del segmento è valido i successivi bit più
significativi servono ad indicizzare la Tabella delle Pagine foglia indicata dall’elemento
dalla tabella dei segmenti. Questo metodo permette di usare lo spazio di
indirizzamento in modo sparso senza dover allocare l’intera. Ciò risulta particolarmente
utile con spazi di indirizzamento molto ampi ed in sistemi software che richiedono
l’allocazione non contigua. Uno svantaggio del modello di corrispondenza a più livelli è la
maggiore complessità del processo di traduzione degli indirizzi (figura 19.4 e 19.5).
19.5
1K
foglie
Indirizzo a 32 bit
10
P1 index
10
P2 index
4KB
1 radice
12
page offest
4 bytes
P1 index = puntatore all’interno della
radice
P2 index = puntatore all’interno delle foglie
Radice = tabella di primo livello
Foglie = tabelle di secondo livello (non
tutte saranno presenti all’interno
della memoria)
4 bytes
Figura 19.4. Tabella delle Pagine a due Livelli.
Indirizzo VIRTUALE a 64-bit
selector
Level 1 Level 2 Level 3 page offest
PAGE TABLE
BASE REGISTER
+
+
+
L1
page table
L2
page table
L3
page table
PHYSICAL
PAGE NUMBER
page offest
64-bit PHYSICAL address
Figura 19.5. Tabella delle Pagine a Tre Livelli:
specializzazione dello schema a due livelli
19.6
19.4
TLB
Dato che le tabelle delle pagine risiedono nella memoria principale, ciascun accesso in memoria
potrebbe richiedere il doppio del tempo (a causa della presenza della Tabella delle Pagine);
infatti servirebbe un primo accesso per ottenere l’indirizzo fisico ed un secondo per caricare
il dato.
Per velocizzare il meccanismo di traduzione i calcolatori contengono una cache speciale per
tener traccia delle traduzioni effettuate di recente. Questa cache prende il nome di TLB
(Translation Look aside Buffer) (figura 19.6).
Virtual Address
N
Virtual to Physical
Page Map
TLB
P
M
Physical Address
L1 Cache
Data
Figura 19.6. Schema TLB.
19.4.1 Caratteristiche del TLB
Il TLB è una cache che contiene solo le corrispondenze della Tabella delle Pagine più recenti.
Il campo tag nel TLB contiene il numero di pagina virtuale, mentre il campo dati contiene il
numero di pagina fisica (figura 19.7)
Dato che non sarà più necessario accedere alla Tabella delle Pagine, facendo accesso in
memoria al TLB ogni accesso in memoria dovrà includere dei bit aggiuntivi, come il bit di uso
ed il bit di modifica.
Ad ogni riferimento si consulta il numero di pagina virtuale nel TLB: se il valore è presente il
numero di pagina fisica viene usato per formare l’indirizzo ed il bit di uso corrispondente
viene posto ad 1; se il processore sta effettuando una scrittura si pone ad 1 anche il bit di
modifica.
Se invece si verifica un miss nel TLB occorre determinare se siamo di fronte ad un page fault
o più semplicemente ad un miss del TLB. Per determinarlo basta vedere se il bit V della
Tabella delle Pagine; se esso è a 1, allora la situazione indica un miss nel TLB. In questo caso la
traduzione va comunque avanti e la CPU può risolvere il riferimento caricando nel TLB la
traduzione presa dalla Tabella delle Pagine, e ripetere successivamente l’accesso.
Se invece la pagina non è presente in memoria indica un reale page fault e la CPU invoca il
sistema operativo per mezzo di un’eccezione.
19.7
Il TLB ha un numero di elementi molto minore del numero di pagine della memoria principale
(128-256 elementi) ed i page fault saranno molto più frequenti dei miss in TLB.
I miss nel TLB possono essere gestiti via hardware o software; in pratica vi è poca differenza
di prestazioni tra i due approcci poiché le operazioni principali da compiere sono le stesse in
entrambi i casi.
Non appena si verifica un miss nel TLB, e si è letta la traduzione corrispondente dalla Tabella
delle Pagine, occorre scegliere un elemento del TLB da sostituire. Dato che i bit di uso e di
modifica sono contenuti nel TLB si devono ricopiare anche questi nell’elemento corrispondente
della Tabella delle Pagine nel momento in cui si sostituisce tale elemento.
Questi bit sono l’unica parte del TLB che può venir modificata durante l’uso di quella pagina.
L’uso di una strategia write-back (ossia ricopiando tali bit solo quando si verifica un miss
anziché ad ogni scrittura) si rivela molto efficiente poiché la frequenza del miss che ci si
aspetta dal TLB è molto bassa.
T L B : O r g a n iz z a z io n e L o g ic a
VPN
V
D
1
0
1
1
0
0
1
0
V
D
1
1
0
0
1
0
0
0
U
U
Tag
P h y s ic a l A d d r e s s
M e m o r ia
F is ic a
P h y s ic a l P a g e o r D is k A d d r e s s
1 1
0 0
1 0
D IS C O
U = b it c h e in d ic a s e la p a g in a è s t a t a u s a t a
Figura 19.7. Organizzazione del TLB.
Nelle implementazioni reali si usa un ampio spettro di possibili valori di associatività nei TLB.
Alcuni sistemi utilizzano dei piccoli TLB completamente associativi, poiché la completa
associatività permette una frequenza di miss minore. Altri elaboratori utilizzano dei TLB più
capienti, non associativi o con bassa associatività.
Con una corrispondenza completamente associativa diviene più complesso sostituire un
elemento, i miss devono essere gestiti più velocemente, e non si può fare affidamento su di un
algoritmo software.
19.4.3 Interazione del TLB con la cache.
I sistemi di memoria virtuale ed il sistema di cache collaborano : il sistema operativo elimina il
contenuto di una pagina dalla cache quando e’ necessario trasferire tale pagina su disco (swap-
19.8
out). Allo stesso tempo il sistema operativo aggiorna la Tabella delle Pagine ed il TLB in modo
da riflettere tale cambiamento.
In uno schema di base, se il TLB genera un hit, si può accedere alla cache con l’indirizzo fisico
corrispondente. Se l’operazione è una scrittura l’elemento della cache viene sovrascritto ed il
dato è inviato verso il buffer delle scritture. Nel caso di lettura la cache può generare un hit
o un miss e rispettivamente fornire il dato oppure causare uno stallo. E’ da notare che un hit
nel TLB ed un hit nella cache sono eventi indipendenti e che l’accesso alla cache non è neppure
tentato quando avviene un miss nel TLB, infine si noti che un hit nelle cache si può verificare
solo dopo un hit nel TLB (figure 19.8 e 19.9).
VA
h it
PA
TLB
Lookup
CPU
m is s
M a in
M em ory
Cache
m is s
h it
T ransla t io n
d ata
tC
1/2 tC
20 tC
V A = V i r t u a l A d d re s s
P A = P h y s ic a l A d d re s s
Figura19.8. Schema e Interazione con la Cache.
V irt u a l a d d r e s s
T L B a cc e ss
T L B m is s
e x c e p tio n
No
Yes
T L B h it?
P h y s ic a l a d d re s s
No
T ry to r e a d d a ta
f ro m c a c h e
C a c h e m is s s ta ll
No
C a c h e h it?
Yes
W r ite ?
No
Yes
W r ite p ro te c tio n
e x c e p tio n
W rite a c c e s s
b it o n ?
Y es
W r it e d a t a in t o c a c h e ,
u p d a te th e t a g , a n d p u t
th e d a t a a n d th e a d d re s s
in to th e w rite b u ffe r
D e liv e r d a ta
to th e C P U
Figura 19.9 Diagramma di funzionamento del TLB + Cache.
19.9
Per evitare di generare cammini critici in cui siano presenti possibili stalli dovuti al tempo
necessario alla traduzione da indirizzo virtuale a fisico una possibile soluzione e’ di utilizzare
cache di dimensioni non superiori a quelle di una pagina (figura 19.10).
32
index
assoc
lookup
TLB
10
1 K
Cache
4 bytes
2
00
PA
Hit/
M iss
Data
PA
12
disp
20
page #
Hit/
M iss
=
Figura 19.10. Sovrapposizione dell’accesso alla Cache e al TLB
Alternativamente la CPU può indicizzare la cache con un indirizzo che sia completamente o
parzialmente virtuale; nel momento in cui si verifica un miss nella cache, il processore deve
tradurre l’indirizzo in un indirizzo fisico, in modo da poter prelevare il blocco della cache dalla
memoria principale (figura 19.11).
Quando si accede alla cache con indirizzi virtuali e le pagine sono condivise con altri
programmi, è possibile che si presenti il fenomeno di aliasing, il quale si verifica quando uno
stesso oggetto ha due nomi; in questo caso si hanno due indirizzi virtuali per la stessa pagina:
un programma potrebbe scrivere un dato, senza che il secondo programma si renda conto che
il dato è cambiato.
Virtual Address
N
TLB
Virtual to Physical
Page Map
P
L1 Cache
M
Physical Address
Figura 19.11 Cache con indirizzamento virtuale.
19.10
Data
Le cache completamente indicizzate virtualmente, introducono dei vincoli progettuali sulla
cache e sul TLB per non introdurre il problema dell’aliasing, o impongono al sistema operativo
di adottare dei provvedimenti per garantire che non possano esistere degli alias. Ad esempio,
per risolvere il problema, si effettua l’accesso in parallelo alla cache al TLB, confrontando i
campi tag dell’indirizzo fisico provenienti dalla cache con l’indirizzo fisico ottenuto dal TLB
(figura 19.12). Il segnale reale di hit della cache sara’ allora il risultato del confronto fra il
Phisical Page Number (PPN) ottenuto dal TLB e quello ricavato dalla cache (purche’ ovviamente
la cache abbia fornito un hit in corrispondenza dell’indirizzo Virtuale fornitole.
Virtual Address
N
TLB
P
Virtual to Physical
Page Map
L1 Cache
M
Physical Address
PPN
=
Data
PPN (tag)
L1 HIT
Figura 19.12. Cache con indirizzamento virtule ma tag fisici.
I processi indicizzati virtualmente ma con tag fisici, tendono ad ottenere vantaggi in termini
di prestazioni delle cache indicizzate in modo del tutto virtuale, mantenendo la semplicità
strutturale di una cache indirizzata fisicamente.
19.4.6 TLB: valori tipici
Alcuni valori tipici del TLB possono essere i seguenti:
¾ Dimensioni del blocco: 8 byte
¾ Hit-time: 0.5 – 1 ciclo
¾ Miss-penalty: 10 – 50 cicli
¾ Hit-rate: 99% - 99.99%
¾ Dimensione della TLB: 32 – 1024 elementi
19.5 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145-1.
[2] Abraham Silberschatz, Peter Baer Galvin, Greg Gagne,“Sistemi Operativi, concetti ed esempi”
19.11
LEZIONE 20
Pipeline
20.1 INTRODUZIONE
Il concetto di pipeline fu introdotto da Henry Ford (il famoso costruttore di automobili) agli
inizi del 1900. L’idea era di creare una catena di montaggio nelle sue industrie allo scopo di
produrre il maggior numero di autovetture per unità di tempo. Per chiarire il concetto si può
pensare ad una lavanderia dove il processo di lavaggio di un capo si suppone composto di 4 fasi
(fig. 20.1).
A
• Ann, Brian, Cathy, Dave
devono:
B
C
D
• Lavare
• Asciugare
• Stirare
• Mettere a posto
Figura 20.1 Fasi del processo di lavaggio.
Ogni fase richiede trenta minuti per essere eseguita, quindi l’intero processo richiederebbe
due ore per completarsi. Supponiamo che nella nostra lavanderia entrino quattro clienti,
utilizzando una classica tecnica sequenziale (fig. 20.2) il primo cliente terminerebbe dopo due
ore, il secondo dopo quattro e cosi via, questo perché si considerano le quattro fasi come un
unico blocco della durata di due ore. Quindi in totale la lavanderia dovrebbe restare aperta
otto ore per servire i quattro clienti.
6 PM
A
7
8
10
9
11
12
1
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
Tempo
B
C
D
Figura 20.2 Lavanderia sequenziale.
Si può notare che ogni fase del processo è indipendente dalle altre, questo ci suggerisce un
modo per minimizzare l’attesa dei nostri clienti: una volta che un cliente completa una fase, la
lascia disponibile per il cliente successivo. Come si può vedere dalla figura 20.3 dopo solo tre
ore e mezza avremmo servito tutti e quattro i nostri clienti. Suddividere un ciclo operativo in
fasi distinte ed indipendenti sta alla base delle tecniche di pipelining.
20.1
6 PM
7
8
9
30 30 30 30 30 30 30
10
11
12
1
Tempo
A
B
C
D
Figura 20.3 Lavanderia con pipeline.
20.2 VANTAGGI E SVANTAGGI DELLA PIPELINE
Da quanto vedremo nei prossimi paragrafi la tecnica della pipeline apporta sicuramente
notevoli vantaggi all’interno di un processore, consente di aumentare il numero di istruzioni
eseguite al secondo rallentando però il tempo di esecuzione di ogni singola istruzione
dovendosi adattare allo stadio più lento. Esistono in realtà altri limiti ai benefici che una
pipeline può apportare all’architettura di un sistema:
• I costi salgono in quanto la pipeline necessita di alcune risorse hardware in più.
• Una ulteriore suddivisione in fasi ancora più elementari diventa più difficile.
• La parallelizzazione crea conflitti che spesso riducono l’efficienza della pipeline.
20.3 LA PIPELINE NEL PROCESSORE MIPS
Gli stessi concetti visti nel caso della lavanderia sono applicabili al processore ma questa volta
in ingresso alla pipeline troviamo l’esecuzione delle istruzioni. La tecnica del pipelining
consente di sovrapporre l’esecuzione delle istruzioni senza ridurre effettivamente il tempo di
esecuzione di ogni singola istruzione, ma piuttosto aumentando il throughput ovvero il numero
di operazioni eseguite nell’unità di tempo. Il throughput è sicuramente la metrica più
importante nella valutazione di un processore.
In altri termini questa tecnica permette di disaccoppiare momenti logicamente a sé stanti
dell’elaborazione eseguendoli in parallelo con altri. Il lavoro che deve essere svolto, in una
pipeline, da un’istruzione è spezzato in piccole parti ciascuna delle quali richiede una frazione
del tempo necessario al completamento dell’intera istruzione, ciascun passo prende il nome di
stadio di pipeline.
La velocità della pipeline è limitata dallo stadio più lento proprio perché tutti gli stadi sono in
successione e lavorano in maniera sincrona quindi la frequenza con cui le istruzioni escono non
può superare quella con cui entrano nella pipeline. Ogni stadio viene quindi calibrato in modo
tale che le operazioni da esso svolte si compiano entro un ciclo di clock del processore. Un
ciclo di clock corrisponde al tempo necessario all’avanzamento dell’istruzione di un passo lungo
la pipeline cioè, ad ogni clock ci si sposta nello stadio successivo.
Se gli stadi fossero perfettamente bilanciati, condizione ottimale, l’accelerazione dovuta alla
pipeline sarebbe direttamente proporzionale al numero di stadi che compongono la pipeline
stessa, ma questa situazione di ottimalità è difficilmente raggiungibile.
Esaminiamo con un esempio le differenze che esistono tra l’esecuzione sequenziale di
istruzioni e quella in pipeline. Nella tabella 1 abbiamo un’insieme di istruzioni con i rispettivi
20.2
tempi di esecuzione per ogni stadio.
Tabella 1. Durata di singoli stadi per istruzione.
Tipo di
istruzione
Lw
Sw
add,sub,and,or
Beq
Memoria
istruzioni
2ns
2ns
2ns
2ns
Lettura
registro
1ns
1ns
1ns
1ns
Operazione
ALU
2ns
2ns
2ns
2ns
Memoria
dati
2ns
2ns
Scrittura
registro
1ns
Tempo
totale
8ns
7ns
6ns
5ns
1ns
Prendiamo come esempio un programma costituito da tre istruzioni lw. La figura 20.4 illustra
l’esecuzione delle tre istruzioni in maniera sequenziale, ovvero senza pipeline, il tempo
impiegato per svolgere le tre lw è di 24 ns.
2
T ime
lw $1, 100( $0)
In structi on
R eg
fe tch
lw $2, 200( $0)
4
6
8
AL U
D ata
ac cess
10
12
14
AL U
D ata
ac cess
16
18
Re g
In structi on
R eg
fe tch
8 ns
lw $3, 300( $0)
Re g
In structi on
fetch
8 ns
8 ns
Figura 20.4 Stadi sequenziali.
Adesso rivediamo lo stesso esempio utilizzando una pipeline a cinque stadi (figura 20.5).
Program
execution
Time
order
(in instructions)
lw $1, 100($0)
Instruction
fetch
lw $2, 200($0)
2 ns
lw $3, 300($0)
2
4
Reg
Instruction
fetch
2 ns
6
ALU
Reg
Instruction
fetch
2 ns
8
Data
access
ALU
Reg
2 ns
10
14
12
Reg
Data
access
Reg
ALU
Data
access
2 ns
2 ns
Reg
2 ns
Figura 20.5 Pipeline a cinque stadi.
Da questo esempio si può notare che la pipeline riesce ad eseguire le cinque istruzioni in 13ns
(senza pipeline ne servivano 24) nonostante la necessità di adattamento allo stadio più lento.
Questo adattamento viene messo in evidenza dai 9ns necessari per eseguire una singola
istruzione mentre ne servivano soltanto 8 nell’esempio senza pipeline: si è dovuto adottare uno
stadio di ciclo di clock da 2ns anche se alcuni stadi ne richiedevano soltanto 1, una scelta
tecnica impiegata per impedire che gli stadi che vanno poi a sovrapporsi nell’esecuzione non
20.3
finiscano i tempi diversi.
Questo esempio mostra anche come lo sbilanciamento degli stadi non consenta, in questo caso,
un miglioramento teorico di cinque volte che ci si aspetterebbe da una pipeline a cinque stadi.
Osservando le figure 20.4 e 20.5 quello che si può notare è il passaggio da un ciclo di clock
pari a 8ns ad uno pari a 2ns, il miglioramento ottenuto è quindi di quattro volte. Il
miglioramento apportato dalla pipeline diventa più evidente aumentando il numero di istruzioni.
20.3.1 Processore basato su pipeline
La figura 20.6 rappresenta un processore a singolo ciclo. Volendo implementare lo stesso
processore ricorrendo ad una pipeline a cinque stadi, occorrerà suddividere la rete logica
rappresentata in fig. 20.6 in cinque sottoreti più semplici.
Una suddivisione possibile risulta la seguente (fig. 20.6 e 20.7):
1. F: (Istruction Fetch) prelievo dell’istruzione
2. D: (Istruction Decode) decodifica dell’istruzione e prelievo operandi dai registri
3. X: (Execute) esecuzione o calcolo di un indirizzo effettivo specificato nell’istruzione
4. M: (Memory access) accesso alla memoria
5. W: (Write back) scrittura del risultato
F: prelievo istruzione
D: Decodifica istruzione
X: esecuzione
M: accesso in Memoria W: Write-back
0
M
u
x
1
Add
Add
4
Add
result
Shift
left 2
PC
Read
register 1
Address
Instruction
Instruction
memory
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Write
data
0
M
u
x
1
Zero
ALU ALU
result
Address
Data
memory
Write
data
16
Sign
extend
Read
data
1
M
u
x
0
32
Figura 20.6 Processore a singolo ciclo.
In figura 20.7:
• IM rappresenta la memoria da cui vengono prelevate le istruzioni (stadio F);
• REG rappresenta i registri (stadio D in lettura e decodifica, stadio W nella fase di
scrittura dei risultati);
• ALU è l’unita in cui vengono effettuate le operazioni logiche e aritmetiche (stadio X);
• DM e’ la memoria dati (stadio M).
20.4
Time (in clock cycles)
Program
execution
order
(in instructions)
CC 1
CC 2
CC 3
IM
Reg
ALU
lw $10, 20($1)
sub $11, $2, $3
IM
Reg
CC 4
CC 5
DM
Reg
ALU
DM
CC 6
Reg
Figura 20.7 Schema delle unità di calcolo.
La figura 20.8 rappresenta uno schema più dettagliato, in cui sono mostrate le risorse fisiche
di ogni stadio. Affinché ogni stadio operi autonomamente rispetto agli altri stadi è necessario
introdurre dei registri intermedi (denominati in funzione degli stadi che essi separano: es.
F/D e’ il registro che separa gli stadi F e D). Tali registri intermedi dovranno essere in grado
di bufferizzare tutti i segnali che e’ necessario passare da uno stadio al successivo. Ad ogni
ciclo di clock, tutte le istruzioni avanzano da un registro di pipeline al successivo. Tali registri
intermedi vengono sincronizzati (ovvero ricaricati) ad ogni ciclo di clock.
F: prelievo istruzione
D: Decodifica istruzione e
lettura dei registri
M: accesso
in Memoria
X: esecuzione
W: Write-back
0
M
u
x
1
Add
4
Add
Add
result
Shift
left 2
PC
Read
register 1
Address
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
0
M
u
x
1
Write
data
16
F/D
F/D
Sign
extend
Zero
ALU ALU
result
Address
Read
data
1
M
u
x
0
Data
memory
Write
data
32
D/X
X/M
X/M
M/W
M/W
Figura 20.8 Unità di calcolo con registri di pipeline.
20.3.1.1 Esempio: istruzione Load Word (lw)
I passi svolti possono essere diversi in base all’istruzione eseguita: ad es. la lw comporta i
seguenti cinque passi:
1. Prelievo dell’istruzione : viene letta l’istruzione nella memoria usando l’indirizzo del
20.5
Program Counter PC, dopodiché il PC viene incrementato di quattro, rendendolo pronto al
nuovo ciclo di clock (è necessario che l’incremento del PC venga memorizzato anche nel
registro F/D perché nel caso di un salto incondizionato il calcolatore non sa a priori quale
tipo di istruzione verrà caricata). Nel registro di pipeline F/D viene scritta l’istruzione
letta (comprensiva del numero del registro che specifica l’indirizzo base della lw e del
valore immediato che specifica l’offset su 16 bit).
2. Decodifica dell’istruzione e lettura dei registri: si preleva il contenuto del registro base
mentre l’offset viene esteso a 32 bit. Questi valori e l’indirizzo incrementato del PC
vengono salvati nel registro di pipeline D/X (il numero del registro in cui si memorizzerà il
risultato continua ad essere “propagato” di stadio in stadio).
3. Esecuzione (calcolo dell’indirizzo effettivo del dato): in questa fase l'istruzione load
riceve dal registro D/X il contenuto del registro base e l’offset, che vengono sommati
usando l’ALU . Il risultato della somma viene memorizzato in X/M.
4. Accesso in memoria.: si legge dalla memoria il dato usando l’indirizzo effettivo del dato
calcolato allo stadio precedente e lo si carica nel registro di pipeline M/W.
5. Scrittura del risultato: il dato letto al passo precedente viene scritto nel registro dove si
vuole inserire il risultato.
Quello che si nota da questi cinque passi è come i registri intermedi consentano di trasferire
tutta l’informazione che potrebbe risultare utile in cicli di clock successivi.
20.3.1.2 Esempio: istruzione Store Word (sw)
Di seguito vengono illustrati i passi necessari ad effettuare l’operazione di memorizzazione:
1. Prelievo istruzione: questo passo e’ del tutto analogo al caso dell’istruzione load word. Nel
registro di pipeline F/D viene scritta l’istruzione letta (comprensiva del numero del
registro che specifica l’indirizzo base della lw e del valore immediato che specifica l’offset
su 16 bit).
2. Decodifica istruzione e lettura registri: oltre alle operazioni analoghe all’istruzione load
word, viene anche letto il contenuto del registro dell’operando da scrivere in memoria. Tale
valore viene bufferizzato nel registro di pipeline D/X.
3. Esecuzione (calcolo dell’indirizzo effettivo del dato): anche questo passo e’ del tutto
analogo al caso dell’istruzione load word (il valore da memorizzare viene “propagato” di
stadio in stadio).
6. Accesso in memoria.: in questo passo si scrive in memoria il dato usando l’indirizzo
effettivo del dato calcolato allo stadio.
4. Scrittura risultato: in questo stadio l’istruzione store word non deve fare niente.
20.3.1.3 Esempio: sequenza di istruzioni lw+sub
Qui di seguito si propone un esempio che prende in considerazione la sequenza di istruzioni:
lw $10, 20($1)
sub $11, $2, $3
20.6
Time (in clock cycles)
Program
execution
order
(in instructions)
lw $10, 20($1)
CC 1
CC 2
CC 3
IM
Reg
ALU
sub $11, $2, $3
IM
Reg
CC 4
CC 5
DM
Reg
ALU
DM
CC 6
Reg
Figura 20.9 Pipeline a più cicli di clock.
La figura 20.9 mostra una rappresentazione schematica della pipeline a più cicli di clock, il
colore grigio indica attività delle risorse corrispondenti. Inoltre, il colore grigio nella prima
metà della risorsa indica che la risorsa corrispondente e’ occupata solo nella prima metà del
ciclo di clock; analogamente, il colore grigio nella seconda metà della risorsa indica che la
risorsa corrispondente e’ occupata solo nella seconda metà del ciclo di clock. Tale
informazione e’ fondamentale per consentire un uso allo stesso ciclo di clock di alcune risorse
come i registri del processore: tali registri risultano quindi disponibili anche nello stesso ciclo
in due stadi diversi (stadio D e stadio W). In Figura 20.10 viene mostrato in dettaglio la fase
di prelievo (Fetch) dell’istruzione lw con la convenzione di colorare in grigio le risorse
impegnate. Notare, in particolare, che il registro di pipeline F/D risulta impegnato nella prima
[meta’] metà del ciclo di clock. Notare inoltre che, rispetto alla fig. 20.8 e’ stato introdotto un
filo (in basso) che consente di propagare l’indice del registro in cui si andrà ad effettuare la
memorizzazione del risultato (write-back). La figura 20.10 illustra il prelievo dell’istruzione
lw, l’incremento del PC e la scrittura dei valori necessari agli altri quattro stadi nel registro di
pipeline F/D.
F: prelievo istruzione
lw $10, 20($1)
D: Decodifica istruzione
X: esecuzione
M: accesso in Memoria W: Write-back
0
M
u
x
1
Add
Add
4
Add
result
Shift
left 2
PC
Read
register 1
Address
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
0
M
u
x
1
Write
data
Zero
ALU ALU
result
Address
Read
data
1
M
u
x
0
Data
memory
Write
data
16
F/D
Sign
extend
32
D/X
X/M
Figura 20.10 Fetch dell’istruzione lw.
20.7
M/W
Nella figura 20.11 la lw si trova ormai allo stadio di decodifica dell’istruzione e
contemporaneamente si ha il prelievo dell’istruzione sub.
F: sub $11, $2, $3
D: lw $10, 20($1)
X: esecuzione
M: accesso in Memoria W: Write-back
0
M
u
x
1
Add
4
Add
Add
result
Shift
left 2
Read
register 1
Address
PC
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
0
M
u
x
1
Write
data
Zero
ALU ALU
result
Read
data
Address
1
M
u
x
0
Data
memory
Write
data
16
Sign
extend
32
D/X
F/D
M/W
X/M
Figura 20.11 Decode lw, Fetch sub.
F: prelievo istruzione
X: lw $10, 20($1)
D: sub $11, $2, $3
M: accesso in Memoria W: Write-back
0
M
u
x
1
Add
Add
4
Add
result
Shift
left 2
PC
Read
register 1
Address
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
0
M
u
x
1
Write
data
Zero
ALU ALU
result
Address
Read
data
1
M
u
x
0
Data
memory
Write
data
16
F/D
Sign
extend
32
D/X
X/M
M/W
Figura 20.12 Execute lw, Decode sub.
Nella figura 20.12 la lw legge dal registro di pipeline D/X i contenuti del registro 1 e il valore
immediato esteso in segno che vengono sommati usando l’ALU e salvando il risultato nel
registro X/M. Nel frattempo l’istruzione sub è ancora in fase di decodifica.
20.8
F: prelievo istruzione
D: Decodifica istruzione
M: lw $10, 20($1)
X:sub $11, $2, $3
W: Write-back
0
M
u
x
1
Add
4
Add
Add
result
Shift
left 2
PC
Read
register 1
Address
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
Zero
ALU ALU
result
0
M
u
x
1
Write
data
Address
Read
data
1
M
u
x
0
Data
memory
Write
data
16
Sign
extend
32
D/X
F/D
M/W
X/M
Figura 20.13 Memory lw, Execute sub.
Nella figura 20.13, per quanto riguarda la lw, il dato viene letto dalla memoria grazie
all’indirizzo contenuto in X/M e caricato nel registro M/W. L’istruzione sub si trova in fase di
esecuzione.
F: prelievo istruzione
D: Decodifica istruzione e
lettura dei registri
M:sub $11, $2, $3
X: esecuzione
W:lw $10, 20($1)
0
M
u
x
1
Add
Add
4
Add
result
Shift
left 2
PC
Read
register 1
Address
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
0
M
u
x
1
Write
data
Zero
ALU ALU
result
Address
Read
data
1
M
u
x
0
Data
memory
Write
data
16
F/D
Sign
extend
32
D/X
X/M
Figura 20.14 Write-Back lw, Memory sub.
M/W
La lw effettua la scrittura nei registri centrali come si nota nella figura 20.14 mentre
l’istruzione sub termina nella figura 20.15.
20.9
D: Decodifica istruzione e
lettura dei registri
F: prelievo istruzione
M: accesso
in Memoria
X: esecuzione
W:sub $11, $2, $3
0
M
u
x
1
Add
Add
4
Add
result
Shift
left 2
PC
Read
register 1
Address
Read
data 1
Read
register 2
Registers Read
Write
data 2
register
Instruction
Instruction
memory
0
M
u
x
1
Write
data
Zero
ALU ALU
result
Address
Read
data
1
M
u
x
0
Data
memory
Write
data
16
F/D
Sign
extend
32
D/X
X/M
Figura 20.15 Write-Back sub.
M/W
20.4 CRITICITA’ SUI DATI E PROPAGAZIONE
La suddivisione di un’istruzione in cinque fasi comporta che in ciascun periodo di clock vi
potranno essere fino a cinque istruzioni in esecuzione. D’altra parte ogni componente logico
dell'unità di elaborazione viene usato in un solo stadio della pipeline per evitare conflitti di
accesso alla stessa risorsa (anche chiamati criticità strutturali). Fino ad ora, si è visto il
comportamento ideale ed ottimale della pipeline, avendo considerato solo istruzioni
indipendenti tra di loro. Nei programmi reali la situazione è ben diversa: spesso le istruzioni
sono dipendenti l'una dall'altra. Si consideri, ad esempio, il seguente programma in assembly (i
registri sono indicati con ‘rX’ anziché con ‘$X’):
add
sub
and
or
xor
r1,r2,r3
r4,r1,r3
r6,r1,r7
r8,r1,r9
r10,r1,r11
Le ultime quattro istruzioni dipendono tutte dal risultato scritto in r1, perché bisogna
considerare che ad un certo istante le cinque istruzioni sono tutte in esecuzione in stati
diversi della pipeline e tutte hanno bisogno del valore del registro r1 (che ancora non e’ stato
memorizzato). Se ciò può non sembrare ovvio proviamo a vederlo graficamente in figura 20.16.
20.10
Tempo (cicli di clock)
F
Dm
Im
Reg
Dm
Im
Reg
Dm
Im
Reg
Dm
Im
Reg
ALU
Reg
Reg
ALU
and r6,r1,r7
W
ALU
sub r4,r1,r3
Im
M
ALU
O
r
d
e
r
add r1,r2,r3
X
ALU
I
n
s
t
r.
D
or r8,r1,r9
xor r10,r1,r11
Reg
Reg
Reg
Dm
Reg
Figura 20.16 Criticità sui dati.
Come si vede in figura i valori che si tentano di leggere da r1 nelle istruzioni sub, and, or, non
possono essere il risultato corretto della add a. Un possibile modo per evitare questo
problema potrebbe essere di ritardare tutte le istruzioni successive alla add di tre cicli,
perdendo pero’ un po’ di vantaggi della pipeline. Questi tipi di dipendenze prendono il nome di
"criticita’ sui dati" e sono uno dei motivi per cui è difficile progettare pipeline ad elevate
prestazioni.
L’approccio tipico per la risoluzione di questo tipo di criticità consiste nel propagare il valore
corretto agli altri stadi della pipeline, dove questo si rende necessario. Analizzando
attentamente ci si accorge che l’istruzione sub cerca di leggere r1, nello stadio di X, ma tale
registro non è stato ancora scritto dall’istruzione precedente, è quindi necessario disporre di
quei valori all’ingresso della ALU utilizzando appositi circuiti di propagazione (forwarding)
connessi tra le varie unità logiche. In questo caso il segnale deve essere propagato dall’uscita
della ALU verso l’ingresso della stessa tramite un collegamento “all’indietro” che giunga su un
registro di flip-flop, come illustrato in figura 20.18.
20.11
D/ X.MemRead
Hazard
detection
unit
D/ X
F / D W rit e
WB
Control
0
M
u
x
PC
Instruction
memory
In s tr u c tio n
P C W r ite
F/ D
X/M
M
WB
EX
M
M/W
WB
M
u
x
Registers
ALU
Data
memory
M
u
x
M
u
x
F/ D.RegisterRs
F/ D.RegisterRt
F/ D.RegisterRt
Rt
F/ D.RegisterRd
Rd
D/ X.RegisterRt
Rs
Rt
M
u
x
X/M.RegisterRd
Forwarding
unit
M/W .RegisterRd
Figura 20.18 Logica necessaria per il forwarding
Come mostrato in figura 20.19, e’ possibile ad ogni ciclo prelevare il dato giusto (se e’ stato
gia’ prodotto) da qualche parte all’interno della pipeline, perlomeno per le istruzioni
considerate. Inoltre, ricordando che il register file viene scritto nella prima meta’ del ciclo di
clock e letto nella seconda meta’ del ciclo, e’ possibile risolvere la criticita’ dell’istruzione ori
di questo esempio senza far ricorso al circuito di propogazione ora introdotto.
20.12
Tempo (cicli di clock)
F
Dm
Im
Reg
Dm
Im
Reg
Dm
Im
Reg
Dm
Im
Reg
ALU
Reg
Reg
ALU
sub r4,r1,r3
W
ALU
Im
M
ALU
O
r
d
e
r
add r1,r2,r3
X
ALU
I
n
s
t
r.
D
and r6,r1,r7
or r8,r1,r9
xor r10,r1,r11
Reg
Reg
Reg
Dm
Reg
Figura 20.19 Propagazione dei dati
20.4.1 Classificazioni dei conflitti di dati
I conflitti che possono insorgere in una pipeline si schematizzano in tre categorie:
RAW (Read After Write)
Un’istruzione successiva cerca di LEGGERE un registro prima che l’istruzione
precedente lo abbia scritto
WAR (Write After Read)
Un’istruzione successiva cerca di SCRIVERE un registro prima che l’istruzione
precedente lo abbia letto
WAW (Write After Write)
Un’istruzione cerca di SCRIVERE un registro prima che l’istruzione precedente lo abbia
scritto (se questo avviene, il valore non sarà più associato all’istruzione più recente)
20.4 CRITICITA’ SUI DATI E SITUAZIONI DI STALLO
Come è possibile immaginare la propagazione non può risolvere tutti i problemi possibili. Un
esempio semplice può essere un istruzione che tenta di leggere un registro subito dopo un
istruzione di load.
Time (clock cycles)
F
M
W
Reg
Reg
Dm
Im
Reg
ALU
sub r4,r1,r3
Im
X
ALU
lw r1,0(r2)
D
Dm
Figura 20.20 Criticità sui dati
20.13
Reg
Come si vede in figura 20.20 non è possibile propagare il dato non essendo ancora disponibile.
L’unico modo per ovviare a questo tipo di problema consiste nel ritardare di un ciclo
l’istruzione sub e far intervenire quindi la propagazione per non perdere un ulteriore ciclo,
come mostrato in figura 20.21.
Time (clock cycles)
F
X
Reg
Bub
ble
Im
M
W
Dm
Reg
Reg
ALU
sub r4,r1,r3
D
ALU
lw r1,0(r2)
Im
Dm
Reg
Figura 20.21 Soluzione con bolla
20.5 CRITICITA’ SUI SALTI DI TIPO “BRANCH”
Fino ad ora abbiamo considerato criticità che coinvolgono le operazioni aritmetiche ed i
trasferimenti di dati. Esistono anche criticità che coinvolgono i salti condizionati. La prima
soluzione che ci viene in mente potrebbe essere simile a quella utilizzata per le situazione di
stallo, cioè fare attendere l’istruzione di salto fino a quando non sia stata completata
l’istruzione precedente. Se la logica di decisione del salto si trova nello stadio di ALU,
avremmo al momento del salto due istruzioni già all’interno della pipeline. Questa prima
soluzione fa sì che si generi in pipeline un ritardo di due cicli di clock.
Dato che la logica di decisione del salto e’ data da un comparatore e da una somma per
calcolare l’indirizzo effettivo del salto (target del salto), un’idea per salvare un ciclo di clock
potrebbe essere di spostare il comparatore e il calcolo del branch target nello stadio di
decodifica. Non appena l’istruzione viene decodificata si puo’ cosi’ decidere se saltare o meno.
Quindi, il ritardo in pipeline associato ad salto di tipo branch diventerà di un ciclo, come
mostrato nelle prossime due figure (20.21 e 20.22) dove viene anche illustrato il circuito
logico che anticipa la decisione dei salti nello stadio di decode.
Beq
Load
I$
Reg
I$
D$
Reg
Reg
D$
Reg
bub
ble
I$
Reg
ALU
Add
ALU
O
r
d
Time (clock cycles)
ALU
I
n
s
t
r.
Figura 20.21 Criticità sui salti
20.14
D$
Reg
F.Flush
Hazard
detection
unit
M
u
x
D/ X
WB
Control
0
M
u
x
F/ D
4
M
WB
EX
M
M/W
WB
Shift
left 2
Registers
PC
X/M
M
u
x
=
Instruction
memory
ALU
Data
memory
M
u
x
M
u
x
Sign
extend
M
u
x
Forwarding
unit
Figura 20.22 Schema logico per l’anticipo del salto durante lo stadio di decodifica
Dato che il processore MIPS non fa niente per bloccare l’istruzione di salto il compito di
ritardare di un ciclo le istruzioni successive ad un salto di tipo “branch” e’ lasciato al
programmatore, il quale dovra’ inserire dopo una istruzione beq (ma anche j e jal per motivi
diversi) una istruzione di tipo nop (no operation) o nei casi piu’ fortunati una istruzione
precedente il cui spostamento non provochi effetti collaterali. Questa tecnica non apporta
nessun rischio nell’esecuzione corretta del codice e sfrutta questo ciclo di attesa associato
alle istruzioni di branch (chiamato Branch Delay Slot), in quanto inserendo una bolla otteniamo
lo stesso comportamento visto nella figura 20.22. I compilatori moderni riordinano il codice
proprio per approfittare di questo slot inserendo istruzioni che andrebbero eseguite a
prescindere dall’esecuzione del salto e tipicamente riescono ad utilizzare questa tecnica nel
50% dei casi. Un esempio è mostrato nella figura 20.23.
20.15
Nondelayed Branch
or
$8, $9 ,$10
Delayed Branch
add $1 ,$2,$3
add $1 ,$2,$3
sub $4, $5,$6
sub $4, $5,$6
beq $1, $4, Exit
beq $1, $4, Exit
or
xor $10, $1,$11
xor $10, $1,$11
Exit:
$8, $9 ,$10
Exit:
Figura 20.23 Situazioni di stallo
Se i salti non sono eseguiti nella metà dei casi e se costa poco scartare le istruzioni, questa
ottimizzazione dimezza il costo delle criticità sul controllo.
20.6 GESTIONE ECCEZIONI IN PRESENZA DI PIPELINE
Durante l’esecuzione in pipeline possono esser generate eccezioni di vario tipo come visto nel
capitolo 9: indirizzi errati, istruzioni sbagliate, overflow e indirizzi sbagliati dei dati. Quando
si genera un’eccezione, bisogna gestirla eseguendo una procedura idonea al controllo della
stessa, in modo tale da non avere dati non validi nei registri. Generalmente nella pipeline
abbiamo in esecuzione cinque istruzioni nei cinque diversi stadi, stando così le cose se viene
generata un’eccezione sorgono alcuni problemi aggiuntivi: i) come interrompere l’esecuzione
della pipeline e ii) come riprendere l’esecuzione. In tabella 2 e fig. 20.25 sono mostrati gli
stadi in cui i vari tipi di eccezione possono essere generati.
Tabella 2. Stadi ed eccezioni.
Stadio
IF
ID
EX
MEM
Eccezioni
page fault durante il fetch dell’istruzione; disallineamento nell’accesso alla
memoria istruzioni; violazione della protezione della memoria istruzioni
codice d’istruzione (opcode) non definito
eccezione aritmetica
page fault durante il fetch del dato; disallineamento nell’accesso alla memoria
dati; violazione della protezione della memoria dati; errore di memoria dati
20.16
IAU
npc
I mem
Regs
lw $2,20($5)
rivelare indirizzo sbagliato dell’ istruzione
PC
rivelare istruzione sbagliata
B
A
im
n op
rd
rivelare overflow
alu
S
rivelare indirizzo sbagliato dei dati
D mem
M
Regs
Permettere l’eccezione
Figura 20.24 Gestione delle eccezioni
Se ci troviamo nella condizione di dover eseguire una routine di gestione dell’errore, è chiaro
che dalla pipeline va eliminata l’istruzione successiva a quella che ha generato l’errore e al suo
posto va inserita l’istruzione del nuovo indirizzo (quella che deve gestire l’eccezione), mentre
l’indirizzo dell’istruzione che ha generato l’eccezione viene salvato nell’EPC (Exception
Program Counter), inoltre la pipeline viene svuotata in modo da riportarsi all’ultimo stato
sicuro dell’esecuzione del programma. In pratica viene congelata la parte iniziale della pipeline
e nella parte di pipeline coinvolta nell’eccezione (dallo stadio in cui si è verificata fino in
fondo) vengono inserite delle bolle come si vede in figura 20.25.
IAU
npc
I mem
Regs
op rw rs rt
freeze
PC
bubble
B
A
im
n op rw
alu
S
n op rw
D mem
m
n op rw
Regs
Figura 20.25 Soluzione alla gestione delle eccezioni
In realtà potrebbero anche verificarsi eccezioni simultanee. Normalmente si associa una
priorità alle eccezioni in modo da sapere quale deve essere gestita per prima, questo tipo di
strategia funziona anche per i calcolatori con pipeline, in particolare nelle implementazioni
20.17
MIPS dove i circuiti ordinano le eccezioni in modo che venga interrotta l’istruzione eseguita
per prima.
20.7 CENNI A PIPELINE PIU’ LUNGHE E SUPERSCALARI
Idealmente il massimo incremento di velocità su una pipeline è legato al numero degli stadi che
possiede come già detto in precedenza (vedi figura 20.26), è per questo che sono state
progettate le superpipeline ovvero pipeline più lunghe. Concettualmente. tornando all’esempio
della lavanderia, è possibile dividere il processo di lavaggio su tre macchine differenti, una
che lava, una che risciacqua e una che centrifuga, a questo punto è possibile cercare di
suddividere qualcuna delle fasi sopradescritte in due o più microfasi, allo stesso modo la
pipeline passa da cinque a più stadi.
Prestazioni Relativi
Too
Deep!
2.5
2.0
1.5
Superpipelined
1.0
0.5
1
2
4
8
16
Profondita’ della pipeline
Come si vede dalla figura 20.26 questo è vero solo in parte, perché se la pipeline è troppo
profonda allora le criticità sono più frequenti e conseguentemente le prestazioni scendono,
inoltre più la pipeline è profonda più diventa difficile eseguire istruzioni che utilizzano i
risultati di altre che con pipeline piu’ brevi sarebbero gia’ pronti.
Un’altra direzione in cui si sono evoluti i processori durante gli anni 90 sono le pipeline
superscalari: esempio molto noto di processore superscalare e’ l’Intel Pentium. Realizzare
copie dei componenti interni del calcolatore in modo che sia possibile lanciare l’esecuzione di
un’istruzione da parte di più stadi della pipeline sta alla base del concetto di pipeline
superscalare, un po’ come se nella nostra solita lavanderia avessimo tre lavatrici e tre
asciugatrici. Lo svantaggio sta nel lavoro addizionale che serve per tenere occupate tutte le
macchine e per trasferire il lavoro da uno stadio all’altro. Inoltre per programmi sequenziali
diventa veramente difficile riuscire a parallelizzare le istruzioni. Se cio’ riesce sarebbe
effettivamente possibile eseguire più istruzioni contemporaneamente ad ogni clock.
Alcuni studiosi hanno cercato di capire se nei programmi sia effettivamente possibile lanciare
piu’ istruzioni in parallelo (Istruction Level Parallelism o ILP). Nel corso degli anni, molti hanno
dato la propria valutazione del numero di istruzioni che si puo’ mandare in parallelo
mediamente (figura 20.27).
20.18
Weiss and Smith [1984]
1.58
Sohi and Vajapeyam [1987]
1.81
Tjaden and Flynn [1970]
1.86 (Flynn’s bottleneck)
Tjaden and Flynn [1973]
1.96
Uht [1986]
2.00
Smith et al. [1989]
2.00
Jouppi and Wall [1988]
2.40
Johnson [1991]
2.50
Acosta et al. [1986]
2.79
Wedig [1982]
3.00
Butler et al. [1991]
5.8
Melvin and Patt [1991]
6
Wall [1991]
7 (Jouppi disagreed)
Kuck et al. [1972]
8
Riseman and Foster [1972]
51 (no control dependences)
Nicolau and Fisher [1984]
90 (Fisher’s optimism)
Figura 20.27 Istruction Level Parallelism
In fig. 20.28 e’ mostrato un confronto fra le tecniche di superpipelining e superscalare.
SUPERSCALAR
Key:
IFetch
SUPERPIPELINED
0
1
2
3
4
5
6
7
Time in Cycles (of Base Machine)
8
9
Dcode
Execute
Writeback
10
11
12
13
Figura 20.28 Confronto tra Superscalari e Superpipeline
Una pipeline superscalare, per poter funzionare correttamente ha necessita’ di ricorrere a
tecniche quali la schedulazione dinamica della istruzioni : utilizzando circuiti addizionali si può
permettere alle istruzioni pronte di essere eseguite in parallelo. Le istruzioni possono cosi’
essere eseguite negli stadi di esecuzione anche in ordine diverso da quello del programma
(completamento fuori ordine): uno stadio finale fara’ in modo di riordinare le istruzioni che
abbiano via via completato la lor esecuzione. Il processore Alpha 21264 con l’uso di pipeline
più lunghe, superscalari e della schedulazione dinamica, esegue sei istruzioni per clock e nel
1997 raggiungeva i 600 MHz di frequenza.
Altri tipi di processori sono il VLIW (Very Long Istruction Word) nel quale le istruzioni
formate da pacchetti preschedulati dal compilatore in modo tale che le istruzioni di ogni
pacchetto possano essere eseguite in parallelo; in questo caso si hanno miglioramenti di
efficienza e maggior velocita’ di elaborazione rispetto ad un processore supercalare.
20.19
Altri tipi di parallelilismo (figura 20.29) che possono essere sfruttati riguardano i
comportamenti vettoriali dei dati: i sistemi SIMD (Single Istruction Multiple Data) realizzano
operazioni vettoriali come nel caso i processori grafici, o nelle istruzioni di supporto alla
multimedialita’, come negli standard MMX, SSE, SSE2, SSE3 di Intel.
Infine, spesso vengono abbinati tecniche predittive e speculative per cercare di prevedere il
flusso delle istruzioni e ridurre l’impatto di eventuali svuotamenti della pipeline dovuti a cause
varie.
Tipologia
Struttura
Pipelining
IF D Ex M
IF D Ex
IF D
IF
Super-pipeline
W
M W
Ex M W
D Ex M W
IF D Ex M W
IF D Ex M W
IF D Ex M W
IF D Ex M W
-Issue one instruction per
(fast) cycle
- ALU takes multiple cycles
Super-scalar
IF D Ex M W
IF D Ex M W
- Issue multiple scalar
instructions per cycle
Limitazioni
Issue rate
FU stalls
FU depth
Clock skew
FU stalls
FU depth
Hazard resolution
IF D Ex M W
IF D Ex M W
Packing
VLIW
-Each instruction specifies
multiple scalar operations
-Compiler determines
parallelism
IF D Ex
Ex
Ex
Ex
M
M
M
M
W
W
W
W
Applicability
SIMD
- Each instruction specifies
series of identical operations
IF D Ex M W
Ex M W
Ex M W
Ex M W
Figura 20.29 Pipelining nei processori
Invece, la tendenza della prima decade degli anni 2000 è quella di sfruttare lo spazio sul chip
non per aumentare la frequenza, ma per inserirvi altri processori in modo da poter sfruttare il
parallelismo di due o più processori.
20.9 RIFERIMENTI BIBLIOGRAFICI
[1] D.A. Patterson, J.L. Hennessy, "Struttura e Progetto dei Calcolatori" 2a edizione ITALIANA
(traduzione della 3a edizione inglese), Zanichelli, Luglio 2006, ISBN 978-88-08-09145
20.20
Scarica

dispense di calcolatori elettronici 1