Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Introduzione
Giovanni Iacca
giovanni.iacca@unitn.it
Luigi Palopoli
luigi.palopoli@unitn.it
Descrizione del corso
• Il corso (che si chiama anche «Architettura
degli elaboratori») si compone essenzialmente
di lezioni teoriche
• In aggiunta, avremo qualche esercitazione
sugli aspetti più pratici del corso (ad es.
aritmetica dei calcolatori e Assembly)
• In totale il corso è coperto da 48 ore di
didattica frontale (12 settimane x 2 lezioni x
2h)
Struttura del corso
Lez. Argomento
• Libro di riferimento:
David A. Patterson, John L. Hennessy,
«Struttura e progetto dei calcolatori
Progettare con RISC-V»,
Zanichelli 2019
• Ricevimento (via Zoom/Meet) su appuntamento
• Contatti
§ Giovanni Iacca
giovanni.iacca@unitn.it
§ Leonardo Custode, Andrea Ferigo (assistenti)
leonardo.custode@unitn.it
andrea.ferigo@unitn.it
Dove studiare cosa
Indicativamente:
Giovanni Iacca
giovanni.iacca@unitn.it
… x 18000!
https://it.wikipedia.org/wiki/ENIAC
Apollo Guidance Computer (IBM, 1969)
• 61 x 32 x 17 cm, 32 kg
• 2800 circuiti integrati (nota: i primi risalivano al '59!)
• Processore @0.043-2 MHz (frequenze
diverse nei vari sottosistemi)
• 152 kByte complessivi ROM/RAM
• Interfaccia DSKY (display&keyboard)
§ tastiera numerica
(istruzioni: verbo + nome)
§ piccolo display
§ vari indicatori luminosi
Margaret Hamilton, 1969
Per fare un confronto: iPhone X
• 143.6 x 70.9 x 7.7 mm, 174 g
• 4.3 miliardi di transistor
• Processore @2.39 GHz (A11 Bionic 6-core ARMv8-A)
• 3GB RAM, fino a 64GB di memoria storage
• Interfaccia: touch-screen + video/voice
Una profezia pessimistica
?
…e poi: la rivoluzione dei PC
LGP-30 (1956)
E oggi?
La rivoluzione dei calcolatori
• I calcolatori hanno creato la terza rivoluzione della
nostra società portandoci nel mondo post industriale
• Solo pochi anni fa le seguenti applicazioni erano
considerate fantascienza
§ Calcolatori negli automobili
§ Telefoni cellulari
§ Mappatura del genoma umano
§ World Wide Web
§ Motori di ricerca
§ Robot di servizio
§ Auto a guida automatica
Calcolatore o calcolatori?
• I calcolatori che operano nelle applicazioni che
abbiamo introdotto condividono la stessa idea
di base…
• Ma le soluzioni usate per ciascuna tipologia di
applicazione possono essere piuttosto diverse
• Per questo parliamo di vari tipi di calcolatori
Vari tipi di calcolatori
• Calcolatori personali (desktop o laptop)
§ buone prestazione a costo ridotto
§ eseguono software di terze parti (architetture aperte)
• Server
§ Pensati per eseguire grandi carichi di lavoro
ü poche applicazioni molto complesse (calcolatori scientifici)
ü tantissime applicazioni molto semplici (web-server)
• Embedded
§ Coprono un vasto spettro di applicazioni (mobile, automotive, avionica, gaming)
§ Le applicazioni sono spesso “dedicate” e operano a stretto contatto con l’hardware
§ Requisiti non funzionali essenziali
ü consumi
ü rispetto di vincoli temporali
ü costo
Perché è importante studiarli?
• Le prestazioni del software sono importanti nel
decretarne il successo commerciale
• Un programma che viene eseguito più velocemente
o che ha minori requisiti hardware ha maggiori
probabilità di soddisfare le aspettative del cliente
• Fino a qualche tempo fa le prestazioni erano
dominate dalla disponibilità di memoria
• Oggi, questo è un problema solo per alcune
applicazioni embedded piuttosto specifiche
Perché è importante studiarli?
• Tuttavia, per scrivere un programma con buone
prestazioni un programmatore moderno deve
§ comprendere la gerarchia di memoria
§ fare un uso efficiente del parallelismo (multi-
threading, GPU, calcolo distribuito)
• In altre parole, deve conoscere (e comprendere)
l’organizzazione del calcolatore
Obiettivi del corso
• Alla fine del corso, sapremo:
§ Quali sono i componenti di base che permettono ad
un calcolatore di operare?
§ Come vengono tradotti i programmi in modo che il
calcolatore possa eseguirli?
§ Qual è l’interfaccia HW/SW tramite la quale il
programmatore può far fare all’HW ciò che richiede?
§ Cosa influenza le prestazioni di un programma e come
può un programmatore migliorarle?
§ Cosa può fare il progettista HW per migliorare le
prestazioni, e perché oggi si ricorre sempre di più alle
architetture multi-core?
Capire le presentazioni
Componente HW/SW Cosa influenza Dove è trattato?
Determina il numero di
Algoritmi istruzioni di alto livello e di Altri corsi
operazioni di IO
Determina il numero di
Linguaggi di programmazione,
istruzioni macchina per ogni Cap. 2, 3
compilatori e architetture
istruzione di basso livello
Determinano quanto
Processore e sistema di
velocemente è possibile Cap. 4, 5, 6
memoria
eseguire ciascuna istruzione
Determina quanto
Sistema operativo,
velocemente possono essere Cap. 4, 5, 6
gestione HW e I/O
eseguite le istruzioni
add A, B 00000000000010001000100001000000
Il programmatore L’assemblatore traduce l’istruzione
scrive questa in una sequenza di bit
istruzione
(somma A a B)
Il flusso completo
Molti compilatori
saltano questa fase
Componenti del Calcolatore
Le elaborazioni dei
dati sono effettuate
dal Processore (diviso in
una parte operativa e
una di controllo)
I dispositivi di input
(tastiera, mouse, ecc.)
e quelli di output
(video, stampanti, ecc.)
permettono di scambiare
informazioni con l’esterno
Esempi di IO: il mouse
• Il mouse è stato inventato nel 1967 da Doug
Engelbart nei famosi laboratori della Xerox
• La versione più moderna usa una tecnologia ottica
§ Alcuni led illuminano il piano ed elaborano l’immagine
§ Facendo la differenza tra immagini successive si scopre
in che direzione si è spostato il mouse
La memoria permanente
è costituita da Hard Disk/SSD
e CD/DVD ROM
Il processore
• Il processore è la parte attiva di ogni calcolatore.
Si compone di:
§ Datapath: esegue le operazioni aritmetiche sui dati
§ Parte di controllo: indica al datapath, alla memoria, e
alle componenti di IO cosa fare sulla base di quanto
stabilito nel programma Una memoria RAM
aggiuntiva (cache) all’interno
della CPU migliora
sostanzialmente
le prestazioni
https://www.youtube.com/watch?v=4BYrKhejb88
Dischi ottici
Interferenza
dovuta agli altri
task Tempo di CPU
Tempo di Risposta Utente
Tempo di
esecuzione
della CPU Tempo di CPU
Sistema
Capire le prestazioni
Determina il numero di
Algoritmi istruzioni di alto livello e di Altri corsi
operazioni di IO
Determina il numero di
Linguaggi di programmazione,
istruzioni macchina per ogni Cap. 2, 3
compilatori e architetture
istruzione di basso livello
Determinano quanto
Processore e sistema di
velocemente è possibile Cap. 4, 5, 6
memoria
eseguire ciascuna istruzione
Determina quanto
Sistema operativo,
velocemente possono essere Cap. 4, 5, 6
gestione HW e I/O
eseguite le istruzioni
https://www.technologyreview.com/s/601441/moores-law-is-dead-now-what/
https://www.technologyreview.com/s/601962/chip-makers-admit-transistors-are-about-to-stop-shrinking/
https://gizmodo.com/how-chip-makers-are-circumventing-moores-law-to-build-s-1831268322
La barriera dell’energia
• Gli attuali processori sono costituiti di moltissimi
interruttori che dissipano energia quando sono in fase
di conduzione (commutazioni tra zero e uno)
• C’è un limite alla capacità di estrarre la potenza
prodotta dai processori (e il conseguente calore)
tramite ventole o radiatori (diciamo intorno ai 100W)
• Superato questo limite la refrigerazione diventa molto
costosa e non è attuabile in un normale desktop (per
non parlare dei laptop)
• La potenza è data da:
La barriera dell’energia
• La frequenza di commutazione è legata alla
frequenza di clock…
• Eppure dagli anni ’80 a oggi si è riusciti in un
‘‘miracolo”: aumentare la frequenza di +1000 volte
a spese di un aumento di consumi di un fattore 30
Come è stato possibile?
2 / 45
Informazione nei computer
3 / 45
Codifica delle informazioni - 1
• Per far sı́ che una sequenza di bit (cifre 0 o 1) possa essere
interpretata come informazione utile, deve essere stabilita una codifica
(rappresentazione digitale)
• Tutto deve essere codificato opportunamente per rappresentarlo in
questo modo
• Numeri (interi con e senza segno, razionali, reali, ...)
• Caratteri / testo
• Programmi (sequenze di istruzioni macchina)
• Immagini e suoni
• Sı́, ma come codifichiamo tutto ció?
4 / 45
Codifica delle informazioni - 2
5 / 45
Codifica dei numeri
6 / 45
Basi di numerazione
7 / 45
Esempi con basi di numerazione diverse da 10
8 / 45
Hai detto... binario?
9 / 45
Altre basi di numerazioni utili
10 / 45
Conversioni fra basi
• Base 2 ↔ 16: come visto (si convertono 4 bit in una cifra esadecimale
e viceversa)
• Base B → 10: già visto anche questo (si moltiplica ogni cifra ci per B i ,
dipendentemente dalla sua posizione)
• Base 10 → B: si divide iterativamente il numero per B
1. Per convertire x10 in base B:
2. Divisione intera fra x e B
3. Il resto e’ la cifra da inserire a sinistra nel numero convertito
4. Il quoziente viene assegnato ad x
5. Ritorna al punto 2
11 / 45
Esempio di conversione
12 / 45
Somme e sottrazioni in base 2
• Un’introduzione, prima:
A B Carry C Carry
0 0 0 0 0
1 0 0 1 0
0 1 0 1 0
1 1 0 0 1
0 0 1 1 0
1 0 1 0 1
0 1 1 0 1
1 1 1 1 1
A + B + Carry = C + Carry
13 / 45
Somma di naturali
• Esempio
11710 + 9710 = 11101012 + 11000012 = ?
1 1 1 0 1 0 1 +
1 1 0 0 0 0 1 =
1 1 0 1 0 1 1 0
110101102 = 21410
14 / 45
Sottrazione di naturali
A 0 1 1 0
B 0 0 1 1
A-B 0 1 0 1
10..0 → 01..1
15 / 45
Sottrazione di naturali
• Esempio
21410 − 11710 = 110101102 − 11101012 = ?
1 1 0 1 0 1 1 0 -
1 1 1 0 1 0 1 =
1 1 0 0 0 0 1
11000012 = 9710
16 / 45
Moltiplicazione per potenze di 2
17 / 45
Moltiplicazione di due naturali
1 1 0 1 x
1 0 0 0 1 =
1 1 0 1 +
- +
- +
- +
1 1 0 1
1 1 0 1 1 1 0 1
110111012 = 22110
18 / 45
Codifica dei numeri interi
19 / 45
Codifica con modulo e segno
20 / 45
Codifica in complemento a 1
21 / 45
Somma e sottrazione di numeri in complemento a 1
22 / 45
Codifica in complemento a 2
23 / 45
Codifica in complemento a 2
25 / 45
Somma e sottrazione di numeri in complemento a 2
26 / 45
Esempi senza overflow
• k = 7, 5 − 8
• 5 = 0000101; −8 = 0001000 + 1 = 1110111 + 1 = 1111000
0 0 0 0 1 0 1
1 1 1 1 0 0 0
1 1 1 1 1 0 1
• 1111101 =... −(1111101 + 1) = −0000011 = −3
• k = 7, −5 + 8
• −5 = 0000101 + 1 = 1111010 + 1 = 1111011; 8 = 0001000
(1)
1 1 1 1 0 1 1
0 0 0 1 0 0 0
0 0 0 0 0 1 1
• 0000011 = 3
27 / 45
Esempi con overflow
• k = 7, −64 − 8
• 64 = 1000000; −64 = 1000000 + 1 = 0111111 + 1 = 1000000
• 8 = 0001000; −8 = 0001000 + 1 = 1110111 + 1 = 1111000
(1)
1 0 0 0 0 0 0
1 1 1 1 0 0 0
0 1 1 1 0 0 0
• 0111000 = 56...
Sommando 2 numeri negativi si ottiene un numero positivo?
• Numeri rappresentabili (k = 7): da −26 = −64 a 26 − 1 = 63
• −64 − 8 = −72 non è rappresentabile!
28 / 45
Esempi con overflow
• k = 7, 63 + 1
• 63 = 0111111
• 1 = 00000001
0 1 1 1 1 1 1
0 0 0 0 0 0 1
1 0 0 0 0 0 0
• 1000000 = −(1000000 + 1) = −(0111111 + 1) = −1000000 = −64...
Sommando 2 numeri positivi si ottiene un numero negativo?
• Ancora, il risultato corretto (63 + 1 = 64) non è rappresentabile!
29 / 45
Esempi ed esercizi
30 / 45
Ancora su overflow
31 / 45
La matematica dell’orologio
32 / 45
Cosa succede in caso di overflow?
33 / 45
Rappresentazione dei numeri reali
34 / 45
Virgola fissa
35 / 45
Virgola fissa: esempio di conversione
36 / 45
Virgola mobile
38 / 45
Tipi di floating point in C
39 / 45
Precisione singola
! s E ! −127
E > 0 ⇒ x = (−1) · 1.M · 2
E ! = 0 ⇒ x = (−1)s · 0.M · 2−126
40 / 45
Precisione singola: valori massimo e minimo
41 / 45
Precisione singola: valori massimo e minimo
42 / 45
Precisione singola: riepilogo - 1
Categoria E! M s
Numeri normalizzati 1-254 qualunque 0/1
Numeri denormalizzati 0 non zero 0/1
± Zero 0 0 0/1
± Infinito 255 0 0/1
NaN (Not a Number) 255 non zero 0/1
44 / 45
Precisione singola: esempio di conversione
45 / 45
CALCOLATORI
Codifica del testo
Giovanni Iacca
giovanni.iacca@unitn.it
2/8
Lo standard ASCII
3/8
Lo standard ASCII
Decimal Hex Char Decimal Hex Char Decimal Hex Char Decimal Hex Char
4/8
Esempio
5/8
ASCII Esteso
• Un byte con valore < 128 (bit più significativo a 0) si interpreta in modo
univoco come carattere
• Esempio: 01000001 è sempre “A”
• L’interpretazione di byte col bit più significativo ad 1 non è univoca
• ISO 8859-1 (Latin1): caratteri dell’Europa Occidentale (lettere
accentate, ecc.)
• ISO 8859-2: caratteri dell’Europa Orientale
• ISO 8859-5: per i caratteri cirillici
• ...
• Esempio: il valore 224 è “à” per ISO 8859-1, “ŕ” per ISO 8859-2, ecc..
6/8
Problemi con ASCII Esteso
7/8
Altri standard di codifica dei caratteri
8/8
CALCOLATORI
Cenni alle reti logiche
Giovanni Iacca
giovanni.iacca@unitn.it
1 1 1 1 0 1
Algebra di Boole
• Una maniera più compatta è di specificare le funzioni
logiche combinatorie tramite espressioni algebriche
definite con l’algebra di Boole
• Esistono tre operatori di base
§ AND
ü Rappresentato tramite il simbolo di prodotto (•), es. A•B
ü Produce 1 se entrambi gli operandi sono 1, 0 negli altri casi
§ OR
ü Rappresentato tramite il simbolo della somma (+), es. A+B
ü Produce 0 se entrambi gli operandi sono 0, 1 negli altri casi
§ NOT
ü Rappresentato da una barra, es. Ā
ü Ha l’effetto di invertire il valore logico
Algebra di Boole
• Alcune semplici regole ci permettono di manipolare (e
semplificare) facilmente le espressioni logiche
§ Identità: A+0=A, A•1=A
§ Regola «zero e uno»: A + 1 = 1, A•0=0
§ Regola dell’inversa: A + Ā=1, A•Ā=0
§ Regola commutativa: A+B=B+A, A•B=B•A
§ Regola associativa: A+(B+C)=(A+B)+C,
A•(B•C)=(A•B)•C
§ Regola distributiva: A•(B+C)=(A•B)+(A•C),
A+(B•C)=(A+B)•(A+C)
Algebra di Boole
• In più esistono due regole molto importanti,
dette di De Morgan:
A·B =A+B
A+B =A·B
1 1 1 1 0 1
E = (A · B · C) + (A · C · B) + (B · C · A)
• O usando De Morgan
E = (A + B + C) · (A + C + B) · (B + C + A)
Porte logiche
• In effetti, esistono dei circuiti elettronici (cosiddette
«porte logiche») che implementano proprio gli
operatori Booleani fondamentali
AND OR NOT
Porte logiche
• Le porte si possono combinare tra di loro (con il NOT
che viene indicato di solito tramite un cerchio
sull’input corrispondente)
A+B
Alcuni circuiti
• Decoder
Alcuni circuiti
• Multiplexer
••••
••••
Decoder
Forme canonica SP
• Abbiamo visto calcolare l’espressione logica
equivalente ad una tabella di verità è semplice
• Basta prendere ciascuna riga uguale a 1 e
scrivere un termine di prodotto logico (AND)
dettato dalla configurazione degli ingressi
• A quel punto si può fare la somma (OR) di tutti
i prodotti individuati
Altro esempio
• Consideriamo come ulteriore esempio:
INPUT OUTPUT
A B C D
0 0 0 0
0 0 1 1
0 1 0 1
0 1 1 0
1 0 0 1
1 0 1 0
1 1 0 0
1 1 1 1
D = (A · B · C) + (A · B · C) + (A · B · C) + (A · B · C)
Programmable Logic Array (PLA)
• La struttura che abbiamo visto si compone di due stadi:
la prima è una barriera di AND (cosiddetti anche
«mintermini»), la seconda è una barriera di OR
1 1 1 1 0 1
Esempio
• Implementazione tramite porte logiche:
D =A+B+C
F =A·B·C
E = (A · B · C) + (A · C · B) + (B · C · A)
Esempio
• Una diversa rappresentazione
D =A+B+C
F =A·B·C
E = (A · B · C) + (A · C · B) + (B · C · A)
Costo
• Le funzioni logiche possono essere implementate in
maniera diversa (più o meno efficiente)
• Per COSTO di una rete logica si intende normalmente la
somma del numero di porte e del numero di ingressi
della rete (indipendentemente dal fatto che siano
positivi o negati)
• E’ possibile trovare delle implementazioni di una rete
che hanno costi diversi
Minimizzazione di funzioni logiche
!q Q– Q–
S
q = 0 or 1
0 0 1 1 1 0 0 q !q
Q– Q– Q–
S S S
Elemento
base di
memoria
(latch)
realizzazione con
due porte NOR
e schema di
“temporizzazione”
della tavola di
verità
Stati indecidibili e temporizzazione
• Si può rimediare?
§ latch-D (data)
§ flip-flop
Latch tipo “D”
• Gli ingressi al circuito
base sono ottenuti da
un’unica variabile (di
cui se ne fa il negato)
• Non vi può essere
ambiguità, per
definizione
• Il circuito è abilitato
durante tutta la fase
positiva del clock
Flip-flop
Master-Slave
D
Master
D Q
Qm
Slave
D Q
Qs
Q
• Configurazioni più
Clock Clk Q Clk Q Q
complesse (come
questa) consentono
ad esempio di
(a)
Circuit
ottenere che l’uscita
Clock del circuito commuti
D
esattamente al
Qm
Q = Qs
termine dell’impulso
(b) Timing diagram
di clock
D Q
(c) Graphical
symbol
Struttura
Registri
i7 D
Q+ o7
C
i6 D
Q+ o6
C I O
i5 D
Q+ o5
C
i4 D
Q+ o4
C
i3 D
Q+ o3 Clock
C
i2 D
Q+ o2
C
i1 D
Q+ o1
C
i0 D
Q+ o0
C
Cloc
k
§ Memorizzano bit
§ La maggior parte delle volte operano come una
barriera tra input e output
§ Sul fronte in salita del clock memorizzano l’input
Vantaggi dell’edge
triggered
In 1
carica l’input
Load e lo accumula
Clock
Clock
Load
In x0 x1 x2 x3 x4 x5
Giovanni Iacca
giovanni.iacca@unitn.it
2 / 21
Programmare in Assembly
3 / 21
Istruzioni Assembly
4 / 21
Funzionamento di una CPU
5 / 21
Programmi Assembly
6 / 21
Registri
7 / 21
Istruzioni Assembly
8 / 21
Esecuzione condizionale
9 / 21
Predicati in Assembly
10 / 21
Tipi di istruzioni Assembly
11 / 21
Operandi e destinazioni
12 / 21
ISA CISC e RISC
13 / 21
Reduced Instruction Set Computer
14 / 21
Complex Instruction Set Computer
15 / 21
Meglio CISC o RISC?
• Domanda “filosofica”
• Due tipi di ISA differenti, con obiettivi differenti
• Come in ogni cosa, gli estremismi non sono mai una buona idea...
• Soluzioni “di successo” spesso mescolano i due approcci
• Intel: tipico esempio di ISA CISC, ma ha “rubato” alcune idee a
RISC (numero di registri, cmov, ecc.)
• ARM: ISA RISC “pragmatica” con modalità di indirizzamento più
complesse, molte istruzioni (anche “complesse” e potenti), meno
registri (16)
• Durante il corso vedremo tre ISA: MIPS (RISC “duro e puro”), Intel
(CISC) ed ARM (RISC “pragmatico”)
16 / 21
Accessi alla memoria
17 / 21
Modalità di indirizzamento
18 / 21
Indirizzamento vs. CISC / RISC
19 / 21
ISA e ABI
20 / 21
Convenzioni di chiamata
21 / 21
CALCOLATORI
Assembl RISC-V
Giovanni Iacca
giovanni iacca unitn it
Luigi Palopoli
luigi palopoli unitn it
In c ion Se
• Per impartire istruzioni al computer bisogna
«parlare» il suo linguaggio
• Il linguaggio si compone di un vocabolario di
istruzioni detto instruction set IS
• I vari tipi di processore hanno ciascuno il
proprio IS.
• Tuttavia le differenze non sono eccessive
Un utile esempio è quello delle inflessioni
regionali di un’unica radice linguistica
In c ion Se
• Come osservato da von Neumann:
“certi insiemi di istruzioni in linea di principio si
prestano a controllare l hardware
• Egli stesso osservava che le considerazioni
davvero decisive sono di natura pratica:
semplicità dei dispositivi richiesti
chiarezza delle applicazioni
velocità di esecuzione
• Queste considerazioni scritte nel sono
straordinariamente valide anche oggi
In e e le ioni
• Nelle prossime lezioni, mostreremo diversi
instruction sets
• Studieremo inoltre il concetto di programma
memorizzato:
Istruzioni e dati sono
memorizzati come numeri
• Considereremo tre IS:
• RISC-V
• Intel
• ARM
Pe ch il RISC-V
• Il motivo per cui mostreremo e faremo esercizi con Intel dovrebbe
essere abbastanza chiaro…
Milioni di PC sono basati su architettura Intel o Intel compatibile
E’ un esempio paradigmatico di architettura CISC
• Vedremo inoltre che ARM è una sorta di via di mezzo tra CISC e RISC
(RISC «pragmatico»
Usata in moltissimi sistemi embedded
P inci i di P ge a i ne n
La em lici à fa i ce la eg la i à
I ioni a i me iche
• Il modo più semplice di immaginare una
istruzione aritmetica è a tre operandi:
a = b + c
add a, b, c
I ioni i com le e
• Istruzioni più complesse si ottengono a partire dalla
combinazione di istruzioni semplici.
• Esempio (in C :
a = b + c;
d = a ;
• Diventa:
add a, b, c
b d, a,
I ioni i com le e
• Un altro esempio (in C :
a = b + c + d + ;
• Diventa:
add a, b, c
add a, a, d
add a, a,
E em io
• Nei linguaggi ad alto livello possiamo scrivere
espressioni complesse a piacimento:
= ( + ) ( + );
add 0, , # a a ab .
# 0 = +
add 1, , # a a ab .
# 1 = +
b , 0, 1 # = 0 1
Alc ne con ide a ioni
• Nell’assemblatore (assembler RISC V
I commenti iniziano con e continuano fino alla fine
della linea
L’operando “destinatario” dell’operazione è sempre il
primo
• Questo non vale per tutti gli assemblatori
• Ad esempio se si usa gcc come assemblatore
questo segue la sintassi AT T per la quale:
I commenti si fanno a la C: / commento /
L’operando destinatario dell’operazione è messo in
fondo
O e andi
• Fino ad ora abbiamo usato gli operandi come se
fossero “normali” variabili di un linguaggio ad alto
livello…
• … in realtà, nel RISC-V gli operandi di operazioni
aritmetiche sono vincolati ad essere (contenuti
nei registri
• Un registro è una particolare locazione di memoria
che è interna al processore e quindi può essere
reperita in maniera velocissima (un ciclo di clock
Regi i del RISC-V
• Il RISC-V contiene registri a bit
Gruppi di bit detti «parola doppia» (double word
Gruppi di bit detti «parola» (word
• Il vincolo di operare solo tra registri semplifica di molto il
progetto dell’hardware
• Ma perché solo registri?
P inci i di P ge a i ne n
min i n le dimen i ni maggi e la el ci à
• Avere molti registri obbligherebbe i segnali a «spostarsi»
su distanze più lunghe all’interno del processore
• Quindi per effettuare uno spostamento all’interno di un
ciclo di clock saremmo costretti a rallentare il clock
E em io ( i e o)
• Torniamo al nostro esempio:
= ( + ) ( + );
• Il codice con i registri diventa:
add 5, 20, 21 # I a
# 5 a a a
# a d :
# 20+ 21 ( + )
add 6, 22, 23 # Id , 6 c a
# a 22+ 23 ( + )
b 19, 5, 6 # = 5 6
O e a ioni
• Come abbiamo detto, le operazioni logiche e
aritmetiche e si effettuano solo tra registri
• Il problema è che i registri non bastano!
• Occorrono istruzioni di trasferimento che:
prelevino dei dati da locazioni di memoria, e li
carichino nei registri (load
salvino il contenuto dei registri in memoria (store
La memo ia
• La memoria è una sequenza di bit organizzati in gruppi
di ( bit byte
Vincolo di allineamento:
è possibile accedere
solo a parole poste a
indirizzi multipli
dell’ampiezza della
parola (vincolo presente
ad es. in Intel x , ma
non in RISC-V
T a fe imen o
d 9, I dC 4( 3)
# a c a 4 a d 3 + I dC 4
add 22, 22, 9
O e andi immedia i
• La soluzione che abbiamo visto è piuttosto
inefficiente
• Ciò segue l’idea generale di ende e el ci le
i a i ni i c m ni
• Per questo motivo esistono istruzioni che
permettono di operare con costanti
= + 4; add 22, 22, 4
Immediate
La co an e 0 ( e o)
• Esistono alcune costanti che possono essere di
grande utilità per semplificare alcune
operazioni
Es. per spostare un registro in un altro posso
renderlo destinatario della somma del sorgente
con
• Per questo motivo il RISC-V dedica un registro
ad hoc ( 0 alla costante
N me i
• Prima di parlare del modo in cui le istruzioni sono
codificate attraverso numeri ricordiamo:
Nei calcolatori l unità base di informazione è il bit
Un gruppo di bit può essere associato ad un numero
fino a che rappresenta una cifra nella notazione
esadecimale
Quindi un byte viene rappresentato da due cifre
esadecimali (ciascuna corrispondente a bit
Ad esempio
1001 1101 9D
Cif e e adecimali
Li le e Big Endian
• Quando si memorizza una parola di quattro byte
(o una doppia parola di in una sequenza di
byte posti a indirizzi progressivi, va capito dove va
il byte più significativo e quello meno significativo
• Esempio (esadecimale
0 EA01BD1C
Byte meno
Byte più
significativo
significativo
Li le e Big Endian
• Little Endian (utilizzato ad es. in architetture Intel e
RISC-V
Addr EA
Addr
EA01BD1C Addr BD
Addr C
( ( ( ( ( (
• Ad esempio:
( ( ( ( (
d 9, 64( 22)
rs
rd
E em io
• Supponiamo di voler tradurre in linguaggio
macchina la seguente istruzione:
A[30] = + A[30] + 1;
• La traduzione è la seguente:
d 9, 240( 10)
add 9, 21, 9
add 9, 9, 1
d 9, 240( 10)
in codice macchina
• Cominciamo con il guardare i codici decimali:
in codice macchina
• In binario:
Ria mendo
• Ciascuna istruzione viene espressa come un
numero binario di bit
• Un programma consiste dunque in una
sequenza di numeri binari
• Tale sequenza viene scritta in locazioni
consecutive di RAM
• In momenti diversi, nella stessa RAM,
possiamo rappresentare programmi diversi
Codici delle i ioni i e fino ad
oa
Elenco (quasi completo)
delle istruzioni RISC-V
Elenco (quasi completo)
delle istruzioni RISC-V
Operazioni logiche
• I primi calcolatori operavano solo su parole intere
• Molto presto si manifestò la necessità di operare su
porzioni di parole o addirittura sul singolo bit
• Il RISC-V fornisce alcune istruzioni che permettono
di fare questo in maniera semplificata (rispetto ad
altre architetture) ma efficace
Shift logico
• Consideriamo per prima cosa lo shift logico a sinistra
• L’idea è di inserire degli zeri nella posizione meno significativa
e traslare tutto a sinistra perdendo (nel caso di overflow) i bit
più significativi
• Ad esempio, supponiamo che x19 contenga il valore 9:
Il numero di
bit di cui
effettuare lo
shift
Esempi
• Come abbiamo visto, l’effetto di questa istruzione è moltiplicare
l’operando per 2^k, dove k è il numero di elementi di cui si fa lo
shift
• Funziona sempre? Dipende…
• Esempio, se x19 contiene: (num. negativo, in complemento a 2)
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
• Se effettuiamo:
slli x11, x19, 4
• Otteniamo un num. positivo (16):
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00010000
• Se effettuiamo:
slli x11, x19, 1
• Se effettuiamo:
srli x11, x19, 4
• Otteniamo:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
Esempio
• Come abbiamo visto nelle lezioni sull’aritmetica dei calcolatori, lo
shift a destra di k unità corrisponde a dividere per 2^k
• E’ corretto?
• Nell’esempio fatto prima facendo lo shift a destra di 4 bit sul
numero 24 abbiamo ottenuto 1 (che è il risultato della divisione
intera di 24 per 2^4)
• Consideriamo un altro esempio (decimale -2):
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111110
• Risultato:
00000000 00000000 00000000 00000000 00000000 00000000 00010011 00000000
• Secondo passo
11010000 00000000 00000000 00000000 11010000 00000000 00000000 00000000
• Quarto passo
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001101
• Risultato:
00000000 00000000 00000000 00000000 00000000 11111000 00000000 11110000
Esempio
• Cosa succede se facciamo?
xor x9, x9, x9
• Risultato:
11111111 11111111 11111111 11111111 11111111 10011100 10011100 00110010
Istruzioni per prendere
decisioni
• Una delle caratteristiche fondamentali dei
calcolatori (che li distinguono dalle calcolatrici) è
la possibilità di alterare il flusso del programma al
verificarsi di certe condizioni
§ costrutto if nelle varie forme
• Il linguaggio macchina delle varie architetture
supporta questa possibilità fornendo istruzioni di
«salto condizionato»
• Tipicamente i salti avvengono sulla base di certe
condizioni sui registri
Salto su condizioni
• Le due principali istruzioni di salto condizionato
sono:
beq rs1, rs2, L1
Se il registro rs1 è
uguale al registro
rs2 effettua un
bne rs1, rs2, L1 salto all’istruzione
con etichetta L1
Se il registro rs1 è
diverso dal registro
rs2 effettua un
salto all’istruzione
con etichetta L1
Il costrutto if
• Supponiamo di avere il seguente codice C. Come
viene tradotto?
if (i == j) f = g + h; else f = g – h;
Il costrutto if
• Traduzione costrutto if precedente:
bne x22, x23, ELSE # Salta a ELSE se x22 div. da x23
add x19, x20, x21 # f = g + h
beq x0, x0, ESCI # Salto incondizionato a ESCI
ELSE: sub x19, x20, x21 # f = g - h
ESCI: …
• Alcune osservazioni:
• Le label vengono alla fine tradotte in indirizzi
• Per fortuna questo lavoro noioso è opera del
compilatore!
Cicli
• La struttura che abbiamo presentato può essere usata
anche per realizzare dei cicli
• Consideriamo l’esempio:
while (salva[i] == k)
i += 1;
• Traduzione:
Ciclo: slli x10, x22, 3 # Registro temp. x10 = 8*i
add x10, x10, x25 # Ind. di salva[i] in x10
ld x9, 0(x10) # Carica salva[i] in x9
bne x9, x24, Esci # Esci se raggiunto limite
addi x22, x22, 1 # i = i+1
beq x0, x0, Ciclo
Esci: …
Alcune considerazioni
• Le sequenze di istruzioni tra due salti condizionati
(conditional branch) sono così importanti che
viene dato loro un nome: blocchi di base
§ Blocco di base: seq. di istruzioni che non contiene né
istruzioni di salto (con l’eccezione dell’ultima) né
etichette di destinazione (con l’eccezione della prima)
• Una delle prime fasi della compilazione è di
individuare i blocchi base
• Tutti i cicli in linguaggio ad alto livello vengono
implementati con blocchi di base
Altri confronti
• Oltre al salto su operandi uguali o diversi, è utile avere un salto
anche su operandi minori/maggiori o minori o uguali
• Il RISC-V mette a disposizione diversi tipi di confronti, in
particolare:
Esempio
• Consideriamo il codice
if (i < j) f = g + h; else f = g – h;
• Traduzione
bge x22, x23, ELSE # Salta a ELSE se x22 >= x23
add x19, x20, x21 # f = g + h
beq x0, x0, ESCI # Salto incondizionato a ESCI
ELSE: sub x19, x20, x21 # f = g - h
ESCI: …
Signed e unsigned
• Cosa produce?
blt x9, x10, L1 salta a L1
• E cosa produce?
bltu x9, x10, L1 non salta a L1
Un piccolo trucco
• Supponiamo di volere controllare se un indice (x20) è fuori
dal limite di un array ([0, x11])
• Ce la possiamo cavare con un solo confronto!
bgeu x20, x11, FuoriLimite
• Perché?
• Se x20 >= x11, avviene il salto a FuoriLimite
• Ma ciò avviene anche se x20 è negativo: interpretando x20
come unsigned, necessariamente sarà x20 >= x11 (si noti
che x11 è necessariamente un num. positivo, in quanto
uguale alla dimensione dell’array-1)
Il costrutto case/switch
• Una possibilità è quella di effettuare una
sequenza in cascata di if-then-else
• In realtà esiste una tecnica diversa:
§ Memorizzare i vari indirizzi del codice da eseguire
in una tabella (una sequenza di word)
§ Caricare in un registro l’indirizzo a cui saltare
§ Fare un salto all’indirizzo puntato dal registro
tramite l’istruzione jalr («jump and link register»,
che vedremo più avanti)
Il costrutto case/switch
• Esempio: Tabella che
memorizza gli indirizzi
dei codici da eseguire
switch(a) {
case 1: <code 1>;
slli x9, x10, 3 # moltiplica per 8
case 2: <code 2>;
add x9, x9, x11 # x11 ind. base TABLE
…
ld x12, 0(x9) # carica indirizzo
}
jalr x1, x12
Salvataggio di x5 in
esempio_foglia: addi sp, sp, -24 sp+16
sd x5, 16(sp)
sd x6, 8(sp)
sd x20, 0(sp) Salvataggio di x6 in
sp+8
Salvataggio di x20 in
sp+0
Esecuzione della procedura
• Dopo il prologo, avviene l’esecuzione della procedura:
In x5 salviamo g+h
In x20: (g+h)-(i+j)
Epilogo
• Dopo aver eseguito la procedura, si ripristinano i
registri usati e si setta il valore di ritorno
Trasferisco x20 in x10
(valore di ritorno)
Lo stack procede
per indirizzi
decrescenti
L’ heap procede
per indirizzi
crescenti
Convenzioni sui registi
• In base alle convenzioni definite dal RISC-V, l’uso dei registri
può essere riassunto come segue:
Elaborazione
• Per quanto le ricorsioni siano eleganti, spesso
sono causa di varie inefficienze
Never hire a programmer who solves the factorial with a recursion!
2+1
Sum (2)
Il valore di ritorno viene costruito
tornando su per la catena di chiamate.
n=2 Ogni frame di attivazione deve essere
2+sum(1)
1+0 Sum (1) preservato per produrre il risultato
corretto.
n=1
1+sum(0)
Sum (0)
0
n=0
Elaborazione
• Consideriamo ora il codice così modificato
long long int sum(long long int n, long long int acc) {
if (n > 0)
return sum(n-1, acc+n);
else
return acc;
}
3
Sum (2,0)
In questo caso lungo i rami di ritorno
non faccio che restituire 3 (valore di
ritorno della chiamata).
n = 2, acc=0 Conservare i dati nel frame di
Sum(1,2)
3 Sum (1,2) attivazione è inutile. Posso usare
un unico frame di attivazione e svolgere
la ricorsione in iterazione.
n = 1, acc= 2
Sum(0, 3)
Sum (0,3)
3
n = 0, acc= 3
Codice
• La procedura ricorsiva viene compilata come segue segue:
sum:
add x5, x0, x11 # Sposta x11 in x5
blt x10, x0, L5 # Se x10 < 0, salta a L5
sum:
add x5, x0, x11 # Sposta x11 in x5
blt x10, x0, L5 # Se x10 < 0, salta a L5
L5:
add x10, x0, x5 # sposta x5 in x10 (valore ritorno)
jalr x0, 0(x1)
Esempi di programmi
assembly RISC-V
Giovanni Iacca
f = (g + h) − (i + j);
Traduzione RISC-V
• Supponendo che g, h, i, j siano in x19, x20, x21, e x22,
e che si voglia mettere il risultato in x23, la traduzione
è semplicemente
f = (g+h)-(i+j);
f = (g+h)-(i+j);
a[12] = h + a[8];
Traduzione RISC-V
• Supponiamo che h sia in x21 e che il registro base del
vettore a sia in x22
a[12]= h + a[8];
if (i == j)
f = g + h;
else
f = g – h;
if (i < j)
f = g + h;
else
f = g – h;
Traduzione RISC-V
• Supponendo di avere f, g, h, i, j nei registri da x19 a x23 avremo
if (i < j)
f = g + h;
else
f = g – h;
if (i < j)
f = g + h;
else
f = g – h;
i = 0;
while (a[i] == k)
i += 1;
Traduzione RISC-V
• Supponiamo di avere i in x22, k in x24 e che l’indirizzo base di a sia in x25
i = 0;
while (a[i] == k)
i += 1;
esempio_foglia:
addi sp, sp, -24 // aggiornamento stack per fare posto a tre elementi
sd x5, 16(sp) // salvataggio x5 per usarlo dopo
sd x6, 8(sp) // salvataggio x6 per usarlo dopo
sd x20, 0(sp) // salvataggio x20 per usarlo dopo
add x5, x10, x11 // x5 = g + h
add x6, x12, x13 // x6 = i + j
sub x20, x5, x6 // f = (g+h)- (i+j)
addi x10, x20, 0 // restituzione di f (x10 = x20 + 0)
ld x20, 0(sp) // ripristino x20 per il chiamante
ld x6, 8(sp) // ripristino x5 per il chiamante
ld x5, 16(sp) // ripristino x5 per il chiamante
addi sp, sp, 24 // aggiornamento sp con eliminazione tre elementi
jalr x0, 0(x1) // ritorno al programma chiamante
Traduzione RISC-V (ottimizzata)
• Traduzione tenendo conto che g, h, i, j e che i registri
temporanei non debbano essere salvaguardati
long long int esempio foglia(long long int g, long long int h,
long long int i , long long int j) {
long long int f;
f = (g + h) − (i + j);
return f ;
}
esempio_foglia:
add x5, x10, x11 // x5 = g + h
add x6, x12, x13 // x6 = i + j
sub x7, x5, x6 // f = (g+h)- (i+j)
addi x10, x7, 0 // restituzione di f (x10 = x7 + 0)
jalr x0, 0(x1) // ritorno al programma chiamante
RISC-V Nomi dei registri ed uso
Registro Nome Uso Chi salva
x9 s1 Salvato Chiamato
int64 inc(int64 n)
{
return n + 1;
}
int64 f(int64 x)
{
return inc(x) − 4;
}
Traduzione RISC-V (gcc)
• La traduzione di inc() è simile alla precedente traduzione,
supponendo che n sia in x10 e il risultato vada in x10
inc:
addi sp, sp, -32 // spazio nello stack
sd x1, 24(sp) // salvo indirizzo di ritorno
sd x8, 16(sp) // salvo frame pointer
addi x8, sp, 32 // frame pointer = inizio frame attivazione
sd x10, -24(x8) // salvo x10
ld x10, -24(x8) // ripristino x10
addi x10, x10, 1 // incremento x10 per predisporre il ritorno
ld x8, 16(sp) // ripristino frame pointer
ld x1, 24(sp) // ripristino return address
addi sp, sp, 32 // ripulisco stack
ret
Traduzione RISC-V
• La traduzione di inc() con ottimizzazioni è la seguente
inc:
addi x10, x10, 1
jalr x0, 0(x1) // ritorno al programma chiamante
Traduzione RISC-V
• Vediamo ora come il gcc traduce la funzione non
foglia (senza ottimizzazioni)
long long int f(long long int n) {
return inc(n) – 4;
}
f:
addi sp,sp,-32 // estendiamo lo stack
sd ra,24(sp) // salviamo ra
sd s0,16(sp) // salviamo contenuto di s0 (alias per x8)
addi s0,sp,32 // nuovo x8 = sp + 32
sd a0,-24(s0) // salvo in s0 – 24 contenuto di a0 (aka x10, n)
ld a0,-24(s0) // carico in a0 (X10) il contenuto di s0 – 24
call inc // equivale a jal x1, inc
mv a5,a0 // copia risultato a0 della call in a5
addi a5,a5,-4 // decrementa risultato di 4
mv a0,a5 // copia risultato nel registro di ritorno a0
ld ra,24(sp) // recuperiamo ra
ld s0,16(sp) // recuperiamo s0
addi sp,sp,32 // rilasciamo lo stack
ret // ritorniamo a ra
Traduzione RISC-V
• Abilitando le ottimizzazioni di gcc (con O1), le cose
cambiano
long long int f(long long int n) {
return inc(n) – 4;
}
f:
addi sp, sp, -16 // spazio nello stack
sd ra, 8(sp) // memorizzo return address
call inc // jal x1, inc
addi a0, a0, -4 // decremento a0 (x10)
ld ra, 8(sp) // ripristino ra (x1)
addi sp, sp, 16 // dealloco stack
ret
Ordinamento di array
• Passiamo a qualcosa di più complesso. Un algoritmo
noto come «insertion sort»
void sposta(int v[], size_t i) { void ordina(int v[], size_t n) {
size_t j; size_t i;
int appoggio; i = 1;
while (i < n) {
appoggio = v[i]; sposta(v, i);
j = i - 1; i = i+1;
while ((j >= 0) && (v[j] > appoggio)) { }
v[j+1] = v[j]; }
j = j-1;
}
v[j+1] = appoggio;
}
Traduzione RISC-V
• Cominciamo da sposta. Questa volta le cose sono più
complesse. Assumiamo che i parametri siano memorizzati
in a0, a1 rispettivamente.
appoggio = v[i];
j = i - 1;
sposta:
slli a4,a1,2 //a4 = i*4
add a5,a0,a4 //a5 = &v[i]
lw a3,0(a5) //a3 = v[i]
addiw a1,a1,-1 //a1 = a1-1 (i-1)
Traduzione RISC-V (continua)
• Ciclo while ((j >= 0) && (v[j] > appoggio)) {
v[j+1] = v[j];
j = j-1;
}
.L4:
li a1,-1
.L2:
addi a1,a1,1
slli a1,a1,2
add a1,a0,a1
sw a3,0(a1)
ret
Traduzione RISC-V (continua)
• Passiamo ora alla funzione ordina. Assumiamo che i
parametri siano memorizzati in a0, a1 rispettivamente.
void ordina(int v[], size_t n) {
size_t i;
i = 1;
li a5,1
ble a1,a5,.L11
addi sp,sp,-32
sd ra,24(sp)
sd s0,16(sp)
sd s1,8(sp)
sd s2,0(sp)
mv s1,a1
mv s2,a0
li s0,1
Traduzione RISC-V (continua)
• Passiamo al loop
while (i < n) {
sposta(v, i);
i = i+1;
}
.L8:
mv a1,s0
mv a0,s2
call sposta
addiw s0,s0,1
bne s1,s0,.L8
Traduzione RISC-V (continua)
• Epilogo ordina
ld ra,24(sp)
ld s0,16(sp)
ld s1,8(sp)
ld s2,0(sp)
addi sp,sp,32
jr ra
.L11:
ret
Copia stringhe
• Consideriamo ora
void copia_stringa(char d[], const char s[]) {
size_t i = 0;
copia_stringa:
addi sp, sp, -8 // aggiorna stack per inserire un elemento
sd x19, 0(sp) // salva x19
add x19, x0, x0 // i = 0
Traduzione RISC-V (continua)
• Loop
LoopCopiaStringa:
add x5, x19, x11 // indirizzo di s[i]
lbu x6, 0(x5) // x6 = s[i]
add x7, x19, x10 // indirizzo di d[i]
sb x6, 0(x7) // d[i] = s[i]
beq x6, x0, LoopCopiaStringaEnd // se 0 vai a LoopCopiaStringaEnd
addi x19, x19, 1 // i += 1
jal x0, LoopCopiaStringa // salta a LoopCopiaStringa
LoopCopiaStringaEnd
Traduzione RISC-V (continua)
• Epilogo
LoopCopiaStringaEnd
ld x19, 0(sp) // ripristina contenuto di x19
addi sp, sp, 8 // aggiorna lo stack eliminando un elemento
jalr, x0, 0(x1) // ritorna al chiamante
CALCOLATORI
Cenni ad Assembly Intel
Giovanni Iacca
giovanni.iacca@unitn.it
2 / 41
Assembly Intel
3 / 41
Registri General Purpose
4 / 41
Registri General Purpose - 2
I registri %rax ... %rsp estendono registri a 32 bit (%eax ... %esp)
5 / 41
Registri “Speciali”
6 / 41
Convenzioni di Chiamata
7 / 41
Convenzioni di Chiamata
• Primi 6 argomenti:
• %rdi, %rsi, %rdx, %rcx, %r8 ed %r9
• Altri argomenti (7 → n): sullo stack
• Valori di ritorno:
• %rax e %rdx
• Registri preservati:
• %rbp, %rbx, %r12, %r13, %r14 ed %r15
• Registri non preservati:
• %rax, %r10, %r11, oltre ai registri per passaggio parametri: %rdi,
%rsi, %rdx, %rcx, %r8 ed %r9
8 / 41
Modalità di Indirizzamento - 1
9 / 41
Modalità di Indirizzamento - 2
10 / 41
Indirizzamento - Casi Speciali
11 / 41
Indirizzamento - limitazioni
12 / 41
Indirizzamento - riassunto
13 / 41
Istruzioni Intel
14 / 41
Istruzioni Più Comuni - 1
15 / 41
Istruzioni Più Comuni - 3
16 / 41
Istruzioni Più Comuni - 2
• cmp e test
• cmp: cmp arg1, arg2
• Compara arg2 con arg1 (e.g. arg2 < arg1, arg2 = arg1, ...)
• Effettua arg2 − arg1 e setta flags del flag register
• arg1 e arg2 non sono modificati (risultato di sottrazione non memorizzato)
• test: test arg1, arg2
• Compara arg2 con arg1 (e.g. arg2 = arg1, ...)
• Effettua arg2 & arg1 e setta flags del flag register
• arg1 e arg2 non sono modificati (risultato di and bit a bit non memorizzato)
• Spesso usato con arg1 = arg2 (e.g. test %eax, %eax per verificare se il valore è 0 o negativo
(ZF o SF).
17 / 41
Istruzioni Più Comuni - 3
18 / 41
Istruzioni Più Comuni - 4
19 / 41
Istruzioni Più Comuni - Esempi mov
20 / 41
Istruzioni Più Comuni - Esempi mov - 2
21 / 41
Istruzioni Più Comuni - Esempi mov - 3 - Zero/Signed
22 / 41
Istruzioni Più Comuni - Esempi add, and, or, sub
23 / 41
Load Effective Address
24 / 41
Load Effective Address - 2
25 / 41
Load Effective Address - Esempio
26 / 41
Load Effective Address - Esempio 2
void f l ( i n t x ) { / / x = %e d i
return 9 * x + 1;
}
fl : fl :
movl %edi, %eax # tmp = x l e a l 1 ( %edi,%edi, 8 ) , %eax
sall 3 , %eax # tmp *= 8 # eax = 1 + %edi + %edi * 8
addl %edi, %eax # tmp += x ret
addl $ 1 , %eax # tmp += 1
ret
27 / 41
Istruzioni Apparentemente Inutili
28 / 41
Esempio
while ( ( d [ i ] = s [ i ] ) ! = 0 ) {
i += 1 ;
}
}
29 / 41
Accesso a Singoli Byte
30 / 41
Implementazione Assembly: Prologo
31 / 41
Implementazione Assembly: Loop
32 / 41
Implementazione Assembly: Fine del Loop
add $ 1 , %rax
jmp L1 # L1 : l a b e l d i i n i z i o l o o p
33 / 41
Implementazione Assembly: Fine
• Non abbiamo salvato nulla sullo stack: non c’è necessità di epilogo!
• Si può direttamente tornare al chiamante
L2 : r e t
34 / 41
35 / 41
Esempi - codice
36 / 41
Esempi - codice 2
s t r u c t Data { func 2 :
char c ; i n t d ; addb $ 1 , ( %edi )
} subl %esi, 4 ( %edi )
/ / p t r = %edi ret
// x = %esi
void f u n c 2 ( s t r u c t Data * p t r , i n t x ) {
p t r −>c ++;
p t r −>d −= x ;
}
/ / p t r = %edi
// x = %esi
La convenzione per X86 64 richiede che il valore di ritorno di una funzione sia memorizzato in %eax/%rax
37 / 41
Esempi - codice 3
38 / 41
Esempi - codice 4
/ / a = %edi, b = %esi
i n t avg ( i n t a , i n t b ) { avg : movl %edi, %eax
r e t u r n ( a+b ) / 2 ; addl %esi, %eax
} s a r l 1 , %eax
ret
39 / 41
Esempi - codice 5
/ / s t r = %rdi func 5 :
i n t f u n c 5 ( char s t r [ ] ) { movl $ 0 , %eax
int i = 0; jmp . L2
while ( s t r [ i ] ! = 0){ . L3 :
i ++; addl $ 1 , %eax
} . L2 :
return i ; movslq %eax, %rdx
} cmpb $ 0 , ( %rdi,%rdx )
jne . L3
ret
/ / d a t = % r d i , l e n = %esi func 6 :
i n t func 6 ( i n t dat [ ] , i n t len ) { movl ( %rdi ) , %eax
i n t min = d a t [ 0 ] ; movl $ 1 , %edx
f o r ( i n t i =1; i < l e n ; i ++) { jmp . L2
i f ( d a t [ i ] < min ) { . L4 :
min = d a t [ i ] ; movslq %edx, %rcx
} movl ( % r d i , % r c x , 4 ) , %ecx
} cmpl %ecx, %eax
r e t u r n min ; j l e . L3
} movl %ecx, %eax
. L3 :
addl $ 1 , %edx
. L2 :
cmpl %esi, %edx
j l . L4
ret
40 / 41
Esempi - codice 6 - elevato numero parametri
41 / 41
CALCOLATORI
Cenni ad Assembly ARM
Giovanni Iacca
giovanni.iacca@unitn.it
2 / 47
Assembly ARM
3 / 47
Registri ARM
4 / 47
Utilizzo dei Registri
5 / 47
Flag Register
• eq (equal): esegui se la prec. op. era fra numeri uguali (flag Z vale 1);
• ne (not equal): esegui se la prec. op. era fra numeri diversi (flag Z vale 0);
• hs (higher or same) o cs -carry set- (flag C vale 1);
• lo (lower) o cc -carry clear- (flag C vale 0);
• mi (minus): esegui se risultato ultima op. è negativo (flag N vale 1);
• pl (plus): esegui se risultato ultima op. è positivo (flag N vale 0);
• vs (overflow): esegui se ultima op. è risultata in un overflow (flag V vale 0);
• vc (overflow clear): opposto di vs (esegui se il flag V vale 0);
• hi (higher): esegui se in un’op. di confronto primo operando è maggiore del
secondo, assumendo unsigned (flag C vale 1 e Z vale 0);
• ls (lower or same): esegui se in un op. confronto primo operando è minore o
uguale al secondo, assumendo unsigned (flag C vale 0 o Z vale 1);
• ge (greater or equal): esegui se N vale 1 e V vale 1, o se N vale 0 e V vale 0;
• lt (less than): esegui se N vale 1 e V vale 0, o se N vale 0 e V vale 1;
• gt (greater than): come ge, ma con Z che vale 0;
• le (less or equal): come lt, ma esegue anche se Z vale 1.
6 / 47
Convenzioni di Chiamata
7 / 47
Convenzioni di Chiamata
• Primi 4 argomenti:
• r0 ... r3
• Registri non preservati! Utilizzabili anche come registri “temporanei” da
non salvare!
• Altri argomenti (4 → n): sullo stack
• Registri preservati: r4 ... r11
• Eccezione: in alcune ABI r9 non è preservato
• Valori di ritorno: r0 e r1
• I registri che una subroutine può utilizzare senza dover salvare sono quindi
r0, r1, r2, r3 ed r12
• Più eventualmente r9 (dipende da piattaforma / ABI)
8 / 47
Modalità di Indirizzamento - 1
9 / 47
Modalità di Indirizzamento - 2
• Pre Indexed:
• [rb, #i][!]
• [rb,{+|−}ro[,<shift>]][!]
• Post Indexed:
• [rb],#i
• [rb],{+|-}ro[,<shift>]
10 / 47
Indirizzamento - Casi Speciali
11 / 47
Indirizzamento: Esempi esplicativi
• Registro:
ldr r0, [ r1, r2] @ r0 = mem[r1 + r2]
• Scalato:
ldr r0, [ r1, + r2, lsl #8] @ r0 = mem[r1 + r2 << 8]
ldr r0, [ r1, − r2, lsl #8] @ r0 = mem[r1 - r2 << 8]
12 / 47
Indirizzamento: Esempi esplicativi - 2
13 / 47
Indirizzamento: Esempi esplicativi - pre-indexed
14 / 47
Indirizzamento: Esempi esplicativi - pre-indexed writeback
15 / 47
Indirizzamento: Esempi esplicativi - pre-indexed shifted
16 / 47
Indirizzamento: Esempi esplicativi - pre-indexed shift writeback
17 / 47
Indirizzamento: Esempi esplicativi - post-indexed
18 / 47
Indirizzamento: Esempi esplicativi - post-indexed shift
19 / 47
Indirizzamento: esempio applicazione
adr r 1 , t a b l e adr r 1 , t a b l e
l o o p : ldr r0, [r1] l o o p : ldr r0, [r1], #4
adr r1, [r1, #4]
@ o p e r a z i o n i su r 0 @ o p e r a z i o n i su r 0
..... .....
20 / 47
Indirizzamento - Tirando le Somme
21 / 47
Istruzioni ARM
22 / 47
Istruzioni Aritmetiche e logiche
• Perché “<r>”?
• Secondo argomento può essere immediato o registro
• differenza da RISC-V: add invece che add ed addi
• Se registro, può essere shiftato o rotato
• lsl (asl), lsr, asr, ror, rrx
• Esempi:
• add r0, r1, #1
• adds r0, r1, r2
• addeq r0, r1, r2, lsl #2
• addeqs r0, r1, r2, lsl r3
23 / 47
Istruzioni Più Comuni - 1
24 / 47
Istruzioni Più Comuni - 2
25 / 47
Istruzioni Più Comuni - 3
• Nota: molti ARM core NON forniscono una istruzione di divisione tra interi
• L’operazione di divisione è fornita da codice:
i n t d i v i d e ( i n t A , i n t B) {
int Q = 0; int R = A;
while ( R >= B ) {
Q = Q + 1;
R = R − B;
}
r e t u r n Q;
}
26 / 47
Istruzioni Più Comuni - 4
• Movimenti di registri:
• mov
mov r0, r1 @ r0 = r1
mov r0, #21 @ r0 = 21
• mvn: move not. Muove il complemento ad 1 di un registro
mvn r0, r1 @ r0 = not r1
• b: branch (anche salto condizionale, tramite suffisso...)
b label mov r 0 , #0
... loop :
label : . . . ...
add r 0 , r 0 , #1
cmp r 0 , #10
bne l o o p
27 / 47
Istruzioni Più Comuni - 5
28 / 47
Istruzioni Più Comuni - 6
• Altre istruzioni simili: tst (esegue and), teq (xor), cmn (somma), ...
tst r0, r1 @ set cc a r0 and r1
teq r0, r1 @ set cc a r0 xor r1
cmn r0, r1 @ set cc a r0 + r1
29 / 47
Istruzioni Più Comuni - 7
31 / 47
Load multiple
R3
R3 R2
R2 R1
Rn → R1 Rn → Rn → R1 Rn →
R2 R1
R3 R2
R3
32 / 47
Load multiple - 2
addr data
r0 → 0x010 10
r1 : 10
0x014 20
r2 : 20
0x018 30
r3 : 30
0x01c 40
r0 : 0x010
0x020 50
0x024 60
addr data
r0 → 0x010 10
r1 : 10
0x014 20
r2 : 20
0x018 30
r3 : 30
0x01c 40
r0 : 0x01c
0x020 50
0x024 60
33 / 47
Load multiple - 3
addr data
r0 → 0x010 10
r1 : 20
0x014 20
r2 : 30
0x018 30
r3 : 40
0x01c 40
r0 : 0x01c
0x020 50
0x024 60
addr data
0x010 10
r1 : 60
0x014 20
r2 : 50
0x018 30
r3 : 40
0x01c 40
r0 : 0x018
0x020 50
r0 → 0x024 60
34 / 47
Load multiple - 4
addr data
0x010 10
r1 : 50
0x014 20
r2 : 40
0x018 30
r3 : 30
0x01c 40
r0 : 0x018
0x020 50
r0 → 0x024 60
35 / 47
Esempio di uso di load multiple: copia blocchi memoria
loop : ldmia r 9 ! { r 0 − r 7 }
stmia r10 ! { r 0 − r 7 }
cmp r 9 , r11
bne l o o p
36 / 47
Esempio
while ( ( d [ i ] = s [ i ] ) ! = 0 ) {
i += 1 ;
}
}
37 / 47
Accesso a Singoli Byte
38 / 47
Implementazione Assembly: Prologo
39 / 47
Implementazione Assembly: Loop
l d r b r 3 , [ r 1 ] , #1
40 / 47
Implementazione Assembly: Fine del Loop
cmp r 3 , #0 @ c o n f r o n t a r 3 con 0 . . .
beq L2 @ se sono u g u a l i , s a l t a a L2
@ ( esci dal loop ! )
• Se no, cicla...
b copia stringa
41 / 47
Implementazione Assembly: Fine
• Non abbiamo salvato nulla sullo stack: non c’è necessità di epilogo!
• Si può direttamente tornare al chiamante
L2 : mov r 1 5 , r14
42 / 47
Vediamo GCC...
.text
.global copia stringa
copia stringa :
ldrb r 3 , [ r1 ]
strb r 3 , [ r0 ]
cmp r 3 , #0
bxeq lr
L3 :
ldrb r 3 , [ r 1 , #1 ] !
strb r 3 , [ r 0 , #1 ] !
cmp r 3 , #0
bne L3
bx lr
43 / 47
Esempio
i n t gcd ( i n t i , i n t j ) { l o o p : cmp r 0 , r 1
while ( i ! = j ) { subgt r 0 , r 0 , r 1
i f ( i >j ) sublt r 1 , r 1 , r0
i −= j ; bne l o o p
else bx l r
j −= i ;
}
}
44 / 47
Esempio di passaggio parametri con lo stack
int caller () { f1 :
i n t sum = f 1 ( 1 , 2 , 3 , 4 , 5 , 6 ) ; add r0, r 0 , r1
r e t u r n sum ; add r0, r 0 , r2
} add r0, r 0 , r3
ldr r3, [ sp ]
i n t f1 ( i n t a1, i n t a2, i n t a3, i n t a4, add r0, r 0 , r3
i n t a 5 , i n t a6 ) { ldr r3, [ s p , #4 ]
r e t u r n a1+a2+a3+a4+a5+a6 ; add r0, r 0 , r3
} bx lr
caller :
str lr, [ s p , #−4 ] !
sub sp, s p , #12
mov r3, #6
str r3, [ s p , #4 ]
mov r3, #5
str r3, [ sp ]
mov r3, #4
mov r2, #3
mov r1, #2
mov r0, #1
bl f1
add sp, s p , #12
ldr lr, [ sp ] , #4
bx lr
45 / 47
Esempio di divisione come shift e sottrazione per interi positivi
i n t d i v i d e ( i n t A, i n t B) { divide :
int Q = 0 ; i n t R = A; mov r 3 , r0
while ( R >= B ) { cmp r 0 , r1
Q = Q + 1; blt .L4
R = R − B; mov r 0 , #0
} .L3 :
r e t u r n Q; add r 0 , r 0 , #1
} sub r 3 , r 3 , r1
cmp r 1 , r3
ble .L3
bx lr
.L4 :
mov r 0 , #0
bx lr
46 / 47
Versione sofisticata di divisione tra interi positivi
http://www.tofla.iconbar.com/tofla/arm/arm02/index.htm
div :
; divs r 0 , r 1 , r2
cmp r 2 , #0
beq d i v i d e e n d ; check f o r d i v i d e by zero !
mov r 0 , #0 ; c l e a r r 0 t o accumulate r e s u l t
mov r 3 , #1 ; s e t b i t 0 i n r 3 , which w i l l be s h i f t e d l e f t then r i g h t
start :
cmp r 2 , r1
movls r 2 , r 2 , l s l #1
movls r 3 , r 3 , l s l #1
bls start ; s h i f t r 2 l e f t u n t i l i t i s about t o be b i g g e r than r 1 ,
; s h i f t r 3 l e f t i n p a r a l l e l i n o r d e r t o f l a g how f a r we
; have t o go
next :
cmp r1,r2 ; c a r r y s e t i f r1>r 2 ( don ’ t ask why )
subcs r 1 , r 1 , r 2 ; s u b t r a c t r 2 from r 1 i f t h i s would g i v e a p o s i t i v e
; answer
addcs r 0 , r 0 , r 3 ; and add t h e c u r r e n t b i t i n r 3 t o t h e a c c u m u l a t i n g
; answer i n r 0
movs r 3 , r 3 , l s r #1 ; S h i f t r 3 r i g h t i n t o c a r r y f l a g
movcc r 2 , r 2 , l s r #1 ; and i f b i t 0 o f r 3 was z e r o , a l s o s h i f t r 2 r i g h t
bcc next ; I f c a r r y n o t c l e a r , r 3 i s s h i f t e d back t o where
; i t s t a r t e d , and we can end
divide end :
; r 1 h o l d s t h e r e m a i n d e r , i f a n y , r 2 has r e t u r n e d t o t h e v a l u e i t h e l d on
; e n t r y t o t h e r o u t i n e , r 0 h o l d s t h e r e s u l t and r 3 h o l d s z e r o . Both zero
; and c a r r y f l a g s are s e t .
bx l r
47 / 47
Esempi di Programmi Assembly
RISC-V e Intel x86
Giovanni Iacca
1
Scopo della lezione
• In questa lezione vedremo alcuni esempi di
programmi (o frammenti di programmi) in vari
linguaggi assembly per renderci conto delle
differenze
• Partiremo da assembly RISC-V e Intel
• Successivamente passeremo all’assembly ARM
2
Semplici istruzioni
aritmetiche logiche
• Partiamo dal semplicissimo frammento che
abbiamo visto a lezione
f = (g + h) − (i + j);
3
Traduzione RISC-V
• Supponendo che g, h, i, j siano in x19, x20,
x21, e x22, e che si voglia mettere il risultato
in x23, la traduzione è semplicemente
f = (g+h)-(i+j);
f = (g+h)-(i+j);
a[12] = h + a[8];
7
Traduzione RISC-V
a[12]= h + a[8];
8
Traduzione INTEL
• Supponiamo di avere h in edi e l’indirizzo di a in rsi.
• Grazie al fatto di poter avere operandi in memoria
stavolta ce la caviamo con due istruzioni
a[12]= h + a[8];
9
Blocchi condizionali
• Consideriamo il seguente blocco
if (i == j)
f = g + h;
else
f = g – h;
10
Traduzione RISC-V
• Supponendo di avere f, g, h, i, j nei registri da
x19 a x23 avremo
if (i == j)
f = g + h;
else
f = g – h;
13
Condizione con
disuguaglianza
• Supponiamo ora di avere:
if (i < j)
f = g + h;
else
f = g – h;
14
Traduzione RISC-V
• Supponendo di avere f, g, h, i, j nei registri da
x19 a x23 avremo
if (i < j)
f = g + h;
else
f = g – h;
18
Ciclo while
• Consideriamo il seguente ciclo while
i = 0;
while (a[i]==k)
i += 1;
19
Traduzione RISC-V
• Supponendo di avere i in x22, k in x24 e
l’indirizzo base di a sia in x25
i = 0;
while (a[i] == k)
i += 1;
add x22, x0 // i = 0
L1:
slli x10, x22, 2 // x10 = i * 4
add x10, x10, x25 // x10 = indirizzo di a[i]
lw x9, 0(x10) // x9 = a[i]
bne x9, x24, L2 // se a[i] != k vai a L2
addi x22, x22, 1 // i = i + 1
beq x0, x0, L1 // se 0 == 0 vai a L1
L2:
20
…
Traduzione INTEL
• Stavolta è possibile sfruttare la potenza del CISC
i = 0;
while (a[i]==k)
i += 1;
x9 s1 Salvato Chiamato
22
Intel Nomi dei Registri ed
Uso
• %rsp → stack pointer;
• %rbp → base pointer
• Primi 6 argomenti:
§ %rdi, %rsi, %rdx, %rcx, %r8 ed %r9
• Altri argomenti (7 → n): sullo
stack
• Valori di ritorno:
§ %rax e %rdx
• Registri preservati:
§ %rbp, %rbx, %r12, %r13, %r14 ed
%r15
• Registri non preservati:
§ %rax, %r10, %r11
§ registri per passaggio parametri:
%rdi, %rsi, %rdx, %rcx, %r8 ed %r9
23
Funzione Foglia
• Si definisce “foglia” una funzione che non ne
chiama altre.
• Le funzioni foglia nel RISC-V, se non
ottimizzate, sono trattate come qualunque
altra funzione
§ occorre salvare (prologo) il return address e
gestire i registri usati come parametri, e
ripristinare (epilogo) tutto quello salvato
24
Esempio
int esempio_foglia(int g, int h,
int i , int j) {
int f;
f = (g + h) − (i + j);
return f ;
}
esempio_foglia:
addi sp, sp, -24 // aggiornamento stack per fare posto a tre elementi
sd x5, 16(sp) // salvataggio x5 per usarlo dopo
sd x6, 8(sp) // salvataggio x6 per usarlo dopo
sd x20, 0(sp) // salvataggio x20 per usarlo dopo
addw x5, x10, x11 // x5 = g + h
addw x6, x12, x13 // x6 = i + j
subw x20, x5, x6 // f = (g+h)- (i+j)
addi x10, x20, 0 // restituzione di f (x10 = x20 + 0)
ld x20, 0(sp) // ripristino x20 per il chiamante
ld x6, 8(sp) // ripristino x5 per il chiamante
ld x5, 16(sp) // ripristino x5 per il chiamante
addi sp, sp, 24 // aggiornamento sp con eliminazione tre elementi
jalr x0, 0(x1) // ritorno al programma chiamante 26
Traduzione RISC-V
Ottimizzata
• Traduzione tenendo conto che g, h, i, j corrispondono ai registri da
x10 a x13 (aka a0, a1, a2, a3), e che i temporanei possono essere
non salvati/usati.
int esempio_foglia(int g, int h, int i , int j) {
int f;
f = (g + h)- (i + j);
return f ;
}
esempio_foglia:
addw a0, a0, a1 // f = g+h (a0 corrisponde a x10)
addw a3, a2, a3 // a3 = i+j
subw a0, a0, a3 // f = f – a3
ret // alias per jalr x0, 0(x1) o jalr zero, 0(ra)
27
Traduzione INTEL
• Traduzione ottimizzata
esempio_foglia:
leal (%rdi,%rsi), %eax
addl %ecx, %edx
subl %edx, %eax
ret
28
Traduzione non ottimizzata
Senza ottimizzazioni il risultato è piuttosto diverso
esempio_foglia:
pushq %rbp //Prologo
movq %rsp, %rbp
movl %edi, -20(%rbp) //g
movl %esi, -24(%rbp) //h
movl %edx, -28(%rbp) //i
movl %ecx, -32(%rbp) //j
int inc(int n)
{
return n + 1;
}
int f(int x)
{
return inc(x) − 4;
}
30
Traduzione RISC-V
• La traduzione di inc è simile alla precedente traduzione,
supponendo che n è in x10 (aka a0) e risultato in x10 (aka a0)
int inc(int n) {
return n + 1;
}
inc:
addiw a0, a0, 1
ret
31
Traduzione RISC-V
• La traduzione di f richiede più attenzione. Supponiamo anche
qui che n sia in x10 (a0)
int f(int n) {
return inc(n) – 4;
}
f:
addi sp, sp, -16 //Prologo
sd ra, 8(sp)
32
Traduzione RISC-V
• Con il gcc le cose sono un po’ più complesse e vengono fatte più
operazioni (senza ottimizzazioni).
int f(int n) {
return inc(n) – 4;
}
inc:
leal 1(%rdi), %eax
ret
34
Traduzione INTEL
• La traduzione INTEL è più semplice poiché il salvataggio del
return address è fatto in automatico con la call
int f(int n) {
return inc(n) - 4;
}
f:
call inc
subl $4, %eax
ret
35
Ordinamento di array
• Passiamo a qualcosa di più complesso: un algoritmo
noto come «insertion sort»
void sposta(int v[], size_t i) { void ordina(int v[], size_t n) {
size_t j; size_t i;
int appoggio; i = 1;
while (i < n) {
appoggio = v[i]; sposta(v, i);
j = i - 1; i = i+1;
while ((j >= 0) && (v[j] > appoggio)) { }
v[j+1] = v[j]; }
j = j-1;
}
v[j+1] = appoggio;
}
36
Traduzione RISC-V
• Cominciamo da sposta. Stavolta le cose sono più complesse.
Assumiamo che i parametri siano memorizzati in x10, x11(a0, a1)
rispettivamente. Usiamo a3 per appoggio.
void sposta(int v[], size_t i) {
size_t j;
int appoggio;
appoggio = v[i];
j = i - 1;
sposta:
slli a4,a1,2 //a4 = i*4
add a5,a0,a4 //a5 = &v[i]
lw a3,0(a5) //a3 = v[i]
addiw a1,a1,-1 //a1 = a1-1 (i = i-1)
37
Traduzione RISC-V
continua
• Ciclo
while ((j >= 0) && (v[j] > appoggio)) {
v[j+1] = v[j];
j = j-1;
}
38
Traduzione RISC-V
continua
• Uscita da sposta
v[j+1] = appoggio;
}
.L4:
li a1,-1
.L2:
addi a1,a1,1
slli a1,a1,2
add a1,a0,a1
sw a3,0(a1) // v[j+1] = appoggio
ret
39
Traduzione RISC-V
continua
• Passiamo ora alla funzione ordina. I parametri
sono memorizzati in a0, a1 rispettivamente.
void ordina(int v[], size_t n) {
size_t i;
i = 1;
ordina: li a5,1
ble a1,a5,.L11
addi sp,sp,-32
sd ra,24(sp)
sd s0,16(sp)
sd s1,8(sp)
sd s2,0(sp)
mv s1,a1
mv s2,a0
li s0,1 40
Traduzione RISC-V
continua
• Passiamo al loop
while (i < n) {
sposta(v, i);
i = i+1;
}
.L8:
mv a1,s0
mv a0,s2
call sposta
addiw s0,s0,1
bne s1,s0,.L8
41
Traduzione RISC-V
continua
• Epilogo ordina
ld ra,24(sp)
ld s0,16(sp)
ld s1,8(sp)
ld s2,0(sp)
addi sp,sp,32
jr ra
.L11:
ret
42
INTEL
• Riguardiamo lo stesso codice implementato
tramite INTEL
void sposta(int v[], size_t i) {
size_t j;
int appoggio;
appoggio = v[i];
j = i - 1;
ciclo:
cmpq $0, %rax # confronta 0 e rax
jl out # esci se j < 0
movl (%rdi, %rax, 4), %r11d # metti v[j] in %r11d
cmpl %r10d, %r11d # confronta v[j] e appoggio
jle out # se v[j] < appoggio esci
movl %r11d, 4(%rdi, %rax, 4)
dec %rax
jmp ciclo
out: 44
INTEL
• Uscita da sposta
v[j+1] = appoggio;
}
out:
movl %r10d, 4(%rdi, %rax, 4)
ret
45
INTEL
• Vediamo la procedura ordina, che non è foglia.
• Il salvataggio sullo stack è semplificato
void ordina(int v[], size_t n) {
size_t i;
i = 1;
46
INTEL
• Il loop
while (i < n) {
sposta(v, i);
i = i+1;
}
loop_ordina:
cmp %rbx, %rsi
jle out_ordina
pushq %rsi
movq %rbx, %rsi
call sposta
popq %rsi
inc %rbx
jmp loop_ordina
out_ordina 47
INTEL
• Epilogo
out_ordina
popq %rbx
ret
48
Copia Stringhe
• Consideriamo ora
void copia_stringa(char d[], const char s[]) {
size_t i = 0;
49
Traduzione RISC-V
• Traduzione RISC-V
copia_stringa:
addi sp, sp, -8 // aggiorna stack per inserire un elemento
sd x19, 0(sp) // salva x19
add x19, x0, x0 // i = 0
50
Traduzione RISC-V
continua
• Loop
while ((d[i] = s[i]) != `0`) {
i += 1;
}
LoopCopiaStringa:
add x5, x19, x11 // indirizzo di s[i]
lbu x6, 0(x5) // x6 = s[i]
add x7, x19, x10 // indirizzo di d[i]
sb x6, 0(x7) // d[i] = s[i]
beq x6, x0, LoopCopiaStringaEnd // se 0 vai a LoopCopiaStringaEnd
addi x19, x19, 1 // i += 1
jal x0, LoopCopiaStringa // salta a LoopCopiaStringa
LoopCopiaStringaEnd
51
Traduzione RISC-V
continua
• Chiusura
LoopCopiaStringaEnd
ld x19, 0(sp) // ripristina contenuto di x19
addi sp, sp, 8 // aggiorna lo stack eliminando un elemento
jalr, x0, 0(x1) // ritorna al chiamante
52
Traduzione INTEL
• Inizio
void copia_stringa(char d[], const char s[]) {
size_t i = 0;
copia_stringa:
movzbl (%rsi), %eax
movb %al, (%rdi)
testb %al, %al
je L1
movl $0, %eax
53
Traduzione INTEL
• Loop
while ((d[i] = s[i]) != `0`) {
i += 1;
}
L3:
addl $1, %eax
movslq %eax, %rcx
movzbl (%rsi, %rcx), %edx
movb %dl, (%rdi, %rcx)
testb %dl, %dl
jne L3
L1:
ret
54
Esempi di Programmi Assembly
RISC-V e ARM
Giovanni Iacca
1
Scopo della lezione
• In questa lezione vedremo alcuni esempi di
programmi (o frammenti di programmi) in vari
linguaggi assembly per renderci conto delle
differenze
• Abbiamo visto esempi in assembly RISC-V e
Intel
• In questa lezione rivedremo esempi RISC-V e
corrispondenti esempi in ARM
2
RISC-V Nomi dei Registri
ed uso
Registro Nome Uso Chi salva
x9 s1 Salvato Chiamato
3
ARM Nomi dei Registri ed
uso
• Nomi: da r0 a r15
§ Tecnicamente, r15 non è un registro general purpose, spesso usato come PC
• Alcuni registri accessibili tramite un nome simbolico che ne iden8fica
l’u8lizzo
§ r13 == sp (stack pointer)
§ r14 == lr (link register)
• Primi 4 argomen8:
§ r0 ... r3
§ registri non preserva@! U@lizzabili anche come registri “temporanei” da non
salvare!
• Altri argomen8 (4 → n): sullo stack
• Registri preserva8: r4 ... r11
§ Eccezione: in alcuni ABI r9 non è preservato
• Valori di ritorno: r0 e r1
• I registri che una subrou8ne può u8lizzare senza doverli salvare sono
§ r0, r1, r2, r3 ed r12
§ più eventualmente r9 (dipende da piaQaforma / ABI)
4
Semplici istruzioni
aritmetiche logiche
• Partiamo dal semplicissimo frammento che
abbiamo visto a lezione
f = (g + h) − (i + j);
5
Traduzione RISC-V
• Supponendo che g, h, i, j siano in x19, x20,
x21, e x22, e che si voglia mettere il risultato
in x23, la traduzione è semplicemente
f = (g+h)-(i+j);
f = (g+h)-(i+j);
f = (g+h)-(i+j);
8
Accesso alla memoria
• Riguardiamo ancora l’esempio visto a lezione
assumendo che int a[] e int h.
a[12] = h + a[8];
9
Traduzione RISC-V
a[12]= h + a[8];
10
Traduzione ARM
• Anche in questo caso la traduzione è molto simile
§ Assumiamo di avere h in r0 e indirizzo di a in r1
• Si usa indirizzamento pre-indexed senza aggiornamento
della base (senza !)
a[12]= h + a[8];
11
Blocchi condizionali
• Consideriamo il seguente blocco
if (i == j)
f = g + h;
else
f = g – h;
12
Traduzione RISC-V
• Supponendo di avere f, g, h, i, j nei registri da
x19 a x23 avremo
if (i == j)
f = g + h;
else
f = g – h;
if (i == j)
f = g + h;
else
f = g – h;
cmp r2, r3
addeq r0, r0, r1
subne r0, r0, r1
14
Condizione con
disuguaglianza
• Supponiamo ora di avere:
if (i < j)
f = g + h;
else
f = g – h;
15
Traduzione RISC-V
• Supponendo di avere f, g, h, i, j nei registri da
x19 a x23 avremo
if (i < j)
f = g + h;
else
f = g – h;
17
Traduzione ARM
• Nel caso di ARM la traduzione con le istruzioni condizionali
è simile
• Cambiano solo le condizioni... (lt e ge)
if (i < j)
f = g + h;
else
f = g – h;
cmp r2, r3
addlt r0, r0, r1
subge r0, r0, r1
18
Ciclo while
• Consideriamo il seguente ciclo while
i = 0;
while (a[i] == k)
i += 1;
19
Traduzione RISC-V
• Supponendo di tenere i in x22, k in x24 e
l’indirizzo base di a sia in x25
i = 0;
while (a[i] == k)
i += 1;
add x22, x0 // i = 0
L1:
slli x10, x22, 2 // x10 = i * 4
add x10, x10, x25 // x10 = indirizzo di a[i]
lw x9, 0(x10) // x9 = a[i]
bne x9, x24, L2 // se a[i] != k vai a L2
addi x22, x22, 1 // i = i + 1
beq x0, x0, L1 // se 0 == 0 vai a L1
L2:
20
…
Traduzione ARM
• Assumendo che il valore di k sia inizialmente contenuto in r0, che
l’indirizzo dell’array a sia inizialmente contenuto in r1 e che il valore di i
vada salvato in r0
• Con ARM è possibile usare il pre-indexing per scorrere array
• Il codice è ldr r3, [r1]
cmp r0, r3
bne L2
mov r3, #0
L1:
add r3, r3, #1
i = 0; ldr r2, [r1, #4]!
while (a[i] == k)
cmp r2, r0
i += 1;
beq L1
b L3
L2:
mov r3, #0
L3:
mov r0, r3 21
Funzione Foglia
22
Esempio
int esempio foglia(int g, int h,
int i , int j) {
int f;
f = (g + h) − (i + j);
return f ;
}
esempio_foglia:
addw a0, a0, a1 // a0 = g + h
addw a3, a2, a3 // a3 = i + j
subw a0, a0, a3 // f = (g+h)- (i+j)
ret // alias per jalr x0, 0(x1) o jalr zero, 0(ra)
24
Traduzione ARM
• Traduzione molto simile
• Come esempio (non è realmente necessario in questo
caso) rsb viene usato per invertire i due operandi
esempio_foglia:
add r0, r0, r1
add r3, r2, r3
rsb r0, r3, r0 // r0=r0-r3 (equivalente a sub r0, r0, r3)
bx lr
25
Funzioni non foglia
• Consideriamo il seguente caso più complesso
int inc(int n)
{
return n + 1;
}
int f(int x)
{
return inc(x) − 4;
}
26
Traduzione RISC-V
• La traduzione di inc è simile alla precedente
traduzione, supponendo che n è in x10 (aka
a0) e risultato in x10 (aka a0)
int inc(int n) {
return n + 1;
}
inc:
addiw a0, a0, 1
ret
27
Traduzione RISC-V senza
ottimizzazioni
• La traduzione di f richiede più attenzione. Supponiamo anche
qui che n è in x10 (a0) e risultato anche esso in x10 (a0).
int f(int n) {
return inc(n) – 4;
}
f:
addi sp, sp, -16 //Prologo
sd ra, 8(sp)
28
Traduzione ARM
• La traduzione ARM di inc è praticamente identica
• Notare che r0 è sia usato come parametro di
ingresso che come valore di ritorno
int inc(int n) {
return n + 1;
}
inc:
add r0, r0, #1
bx lr
29
Traduzione ARM
• La traduzione ARM può avvalersi del salvataggio multiplo di r4
e lr (con aggiornamento automatico di sp)
• Notare come il ripristino dei registri possa permettere
automaticamente di caricare lr su pc e fare il return
automaticamente
int f(int n) {
return inc(n) - 4;
}
f:
stmfd sp!, {r4, lr}
bl inc
sub r0, r0, #4
ldmfd sp!, {r4, pc}
stmfd = stm full descending = stmdb 30
Ordinamento di array
• Passiamo a qualcosa di più complesso: un algoritmo
noto come «insert sort»
void sposta(int v[], size_t i) { void ordina(int v[], size_t n) {
size_t j; size_t i;
int appoggio; i = 1;
while (i < n) {
appoggio = v[i]; sposta(v, i);
j = i - 1; i = i+1;
while ((j >= 0) && (v[j] > appoggio)) { }
v[j+1] = v[j]; }
j = j-1;
}
v[j+1] = appoggio;
}
31
Traduzione RISC-V
• Cominciamo da sposta. Stavolta le cose sono più complesse.
Assumiamo che i parametri siano memorizzati in x10, x11 (a0, a1)
rispettivamente. Usiamo a3 per appoggio.
appoggio = v[i];
j = i - 1;
sposta:
slli a4,a1,2 //a4 = i*4
add a5,a0,a4 //a5 = &v[i]
lw a3,0(a5) //a3 = v[i]
addiw a1,a1,-1 //a1 = a1-1 (i = j-1)
32
Traduzione RISC-V
continua
• Ciclo
while ((j >= 0) && (v[j] > appoggio)) {
v[j+1] = v[j];
j = j-1;
}
.L4:
li a1,-1
.L2:
addi a1,a1,1
slli a1,a1,2
add a1,a0,a1
sw a3,0(a1) // v[j+1] = appoggio
ret
34
Traduzione RISC-V
continua
• Passiamo ora alla funzione ordina. Assumiamo che i parametri siano
memorizzati in x10, x11 (a0, a1) rispettivamente
ordina: li a5,1
ble a1,a5,.L11
addi sp,sp,-32
sd ra,24(sp)
sd s0,16(sp)
sd s1,8(sp)
sd s2,0(sp)
mv s1,a1
mv s2,a0
li s0,1
35
Traduzione RISC-V
continua
• Passiamo al loop
while (i < n) {
sposta(v, i);
i = i+1;
}
.L8:
mv a1,s0
mv a0,s2
call sposta
addiw s0,s0,1
bne s1,s0,.L8
36
Traduzione RISC-V
continua
• Epilogo ordina
ld ra,24(sp)
ld s0,16(sp)
ld s1,8(sp)
ld s2,0(sp)
addi sp,sp,32
jr ra
.L11:
ret
37
ARM
• Riguardiamo lo stesso codice implementato
tramite ARM
void sposta(int v[], size_t i) {
size_t j;
int appoggio;
appoggio = v[i];
j = i - 1;
sposta:
mov r2, r1, asl #2 // r2 = i * 4
add r3, r0, r2 // r3 = &v[i]
ldr ip, [r0, r1, asl #2] // ip = r12 (scratch) appoggio = v[i]
subs r1, r1, #1 // j = i - 1
38
ARM
• Vediamo il ciclo
while ((j >= 0) && (v[j] > appoggio)) {
v[j+1] = v[j];
j = j-1;
}
v[j+1] = appoggio;
}
L2:
add r1, r1, #1
str ip, [r0, r1, asl #2]
bx lr
40
ARM
• Vediamo la procedura ordina, che non è foglia.
• Il salvataggio sullo stack dei registri avviene in un solo passo
ordina:
cmp r1, #1
bxle lr
stmfd sp!, {r4, r5, r6, lr} // full descending aka db
mov r5, r1
mov r6, r0
mov r4, #1
stmfd = stmdb 41
ARM
• Il loop
• Notare uscita contestuale con rispristino registri
while (i < n) {
sposta(v, i);
i = i+1;
}
ldmfd = ldmia
L8:
mov r1, r4
mov r0, r6
bl sposta
add r4, r4, #1
cmp r5, r4
bne L8
ldmfd sp!, {r4, r5, r6, pc} // full descending 42
Copia Stringhe
• Consideriamo ora
void copia_stringa(char d[], const char s[]) {
size_t i = 0;
43
Traduzione RISC-V
• Traduzione RISC-V
void copia_stringa(char d[], const char s[]) {
size_t i = 0;
copia_stringa:
addi sp, sp, -8 // aggiorna stack per inserire un elemento
sd x19, 0(sp) // salva x19
add x19, x0, x0 // i = 0
44
Traduzione RISC-V
continua
• Loop
while ((d[i] = s[i]) != `0`) {
i += 1;
}
LoopCopiaStringa:
add x5, x19, x11 // indirizzo di s[i]
lbu x6, 0(x5) // x6 = s[i]
add x7, x19, x10 // indirizzo di d[i]
sb x6, 0(x7) // d[i] = s[i]
beq x6, x0, LoopCopiaStringaEnd // se 0 vai a LoopCopiaStringaEnd
addi x19, x19, 1 // i += 1
jal x0, LoopCopiaStringa // salta a LoopCopiaStringa
LoopCopiaStringaEnd
45
Traduzione RISC-V
continua
• Chiusura
LoopCopiaStringaEnd
ld x19, 0(sp) // ripristina contenuto di x19
addi sp, sp, 8 // aggiorna lo stack eliminando un elemento
jalr, x0, 0(x1) // ritorna al chiamante
46
Traduzione ARM
• Inizio
void copia_stringa(char d[], const char s[]) {
size_t i = 0;
copia_stringa:
ldrb r3, [r1]
strb r3, [r0]
cmp r3, #0
bxeq lr
47
Traduzione ARM
• Loop
• Notare come possiamo usare l’aggiornamento del registro
per evitare un registro indice.
L3:
ldrb r3, [r1, #1]!
strb r3, [r0, #1]!
cmp r3, #0
bne L3
bx lr
48
CALCOLATORI
Toolchain: Come generare applicazioni in
linguaggio macchina
Giovanni Iacca
giovanni.iacca@unitn.it
2 / 25
Compilazione: Esempio
3 / 25
Un compilatore C
4 / 25
Allocazione della memoria per programmi e dati nel RISC-V
↑
Dati dinamici
Dati statici
0000 0000 1000 000016 .data
Testo
PC → 0000 0000 0040 000016 .text
Riservato
0
5 / 25
Usando gcc...
6 / 25
Da C ad assembly
• Assembler
• as fa spesso qualcosa in più rispetto alla semplice sostituzione di
codici mnemonici con sequenze di bit
• Pseudo-istruzioni
• Convertite in istruzioni riconosciute dalla CPU
• Converte numeri da decimale / esadecimale a binario
• Gestisce label
• Gestisce salti: se destinazione troppo lontana, j DEST va
convertita in caricamento di registro + jr
• Genera metadati
8 / 25
Pseudo-istruzioni
• etc.
9 / 25
File oggetto
11 / 25
Linker e simboli
12 / 25
Linking in tre passi
13 / 25
Librerie
14 / 25
Librerie statiche
15 / 25
Librerie dinamiche
16 / 25
Possibile complicazione: “lazy linking”
17 / 25
Esempio: funzioni da compilare / linkare
file1.o file2.o
18 / 25
Esempio: file oggetto 1
• Header
• Text size:
10016 (file1) + 20016 (file2) = 30016
• Data size:
2016 (file1) + 3016 (file2) = 5016
Riservato
• Assegnamento indirizzi a simboli: 0
• func 1: 000000000040000016
func 2: 000000000040010016
• x: 000000001000000016
y: 000000001000002016
22 / 25
Linker: mettendo tutto assieme... (cont.)
23 / 25
Linker: mettendo tutto assieme... (cont.)
24 / 25
Quindi...
25 / 25
CALCOLATORI
Il processore
Giovanni Iacca
giovanni.iacca@unitn.it
1. Tutte le istruzioni
usano il PC per 2. Caricamento
prelevare l’istruzione 3. Opera la ALU
degli operandi
Pezzi mancanti
• La figura precedente è incompleta e crea
l’impressione che ci sia un flusso continuo di dati
• In realtà ci sono punti in cui i dati arrivano da
diverse sorgenti e bisogna sceglierne una (punto
di decisione)
• E’ il caso per esempio dell’incremento del PC
§ Nel caso “normale” il suo valore proviene da un
circuito addizionatore (che lo fa puntare alla word
successiva a quella appena letta)
§ Nel caso di salto il nuovo indirizzo viene calcolato a
partire dall’offset contenuto nel campo dell’istruzione
Pezzi mancanti
• Altro esempio
§ Il secondo operando della ALU può provenire dal
banco registri (per istruzioni di tipo R) o dal codice
dell’istruzione stessa (per istruzioni di tipo I)
• Per selezionare quale delle due opzioni
scegliere viene impiegato un particolare rete
combinatoria (multiplexer) che funge da
selettore dei dati
Il multiplexer
• Come abbiamo già visto, il multiplexer ha due (o
più) ingressi dati, e un ingresso di controllo
• Sulla base dell’ingresso di controllo si decide
quale degli input debba finire in output
Ulteriori pezzi mancanti
• Le linee di controllo dei multiplexer vengono impostate
sulla base del tipo di istruzioni
• I vari blocchi funzionali hanno ulteriori ingressi di
controllo
§ La ALU ha diversi ingressi per decidere quale operazione
effettuare
§ Il banco registri ha degli ingressi per decidere se scrivere o
meno in un registro
§ La memoria dati ha degli ingressi per decidere se vogliamo
effettuare letture o scritture
• Per decidere come impiegare i vari ingressi di controllo
abbiamo bisogno di un’unità che funga da “direttore
d’orchestra”
Una figura più completa
Mux per Nel registro
decidere come target
aggiornare il PC memorizziamo
il risultato
della ALU o
quello che
preleviamo
dalla
memoria?
Secondo
Unità che operando
genera i vari registro o
segnali di immediato?
controllo
Informazioni di base
• ASSUNZIONE SEMPLIFICATIVA:
§ Il processore lavora sincronizzandosi con i cicli di
clock
§ Per il momento facciamo l’assunzione
semplificativa che tutte le istruzioni si svolgano in
un singolo ciclo di clock (lungo abbastanza)
• Prima di entrare nella descrizione dei vari
componenti ricordiamo velocemente alcuni
concetti di reti logiche
Reti logiche
• Definiamo rete logica combinatoria un circuito composto di porte
logiche che produce un output che è una funzione (statica)
dell’input
§ Esempio: il multiplexer visto prima
• Vi sono inoltre elementi che chiamiamo «di stato»:
§ In sostanza se a un certo punto salviamo il valore degli elementi di
stato e poi lo ricarichiamo, il computer riparte esattamente da dove si
era interrotto
§ Nel nostro caso elementi di stato sono: registri, memoria dati,
memoria istruzioni
• Gli elementi di stato sono detti sequenziali perché l’uscita a un
ingresso dipende dalla storia (sequenza) degli ingressi precedenti
• Gli elementi di stato hanno (almeno) due ingressi:
§ Il valore da immettere nello stato
§ Un clock con cui sincronizzare le transizioni di stato
Flip-Flop
• Come abbiamo già visto, l’elemento base per memorizzare
un bit è un circuito sequenziale chiamato flip-flop D-latch
In Out
Latch
CLK
Al tempo t l’elemento
di stato 1 viene Il valore viene
Al tempo t+T il valore
aggiornato propagato attraverso
arriva all’elemento di
una rete
stato 2
combinatoria
Considerazioni
• Il tempo di clock T deve essere scelto in modo da
dare tempo ai dati di attraversare la rete
combinatoria
• Nel caso del RISC-V a 64 bit quasi tutti gli
elementi di stato e combinatori hanno ingressi ed
uscite a 64 bit
• La metodologia di memorizzazione sensibile ai
clock permette di realizzare interconnessioni che,
a prima vista, creerebbero dei cicli di retroazione
che renderebbero impredicibile l’evoluzione del
sistema
Esempio
• Consideriamo un caso come questo:
Elemento Logica
di stato combinatoria
s f()
Il PC viene
spostato in
avanti di una
word per il
prossimo ciclo
A ogni ciclo di
Il codice
clock viene
dell’istruzione
prelevata
viene reso
l’istruzione
disponibile
puntata dal PC
Istruzioni di tipo R
• Cominciamo dal vedere come vengono eseguite le istruzioni di tipo R
• Abbiamo visto che si tratta di istruzioni aritmetiche/logiche che
operano tra registri e producono un risultato che viene memorizzato
in un registro
• Esempio:
add x5, x6, x7
ALU: effettua
Banco registri: dà
Se abilitato in l’operazione
in output i registri
scrittura scrive nel aritmetica codificata
specificati nel
registro specificato in 4 bit. Setta un bit
registro di lettura
il dato in ingresso in uscita se il
1e2
risultato è zero
Istruzioni load/store
• Consideriamo ora anche le istruzioni load (ld) e store (sd)
• Forma generale
ld x5, offset(x6)
12 bit 5 bit 3 bit 5 bit 7 bit Tipo I
offset rs1 funz3 rd codop
sd x5, offset(x6)
7 bit 5 bit 3 bit 5 bit 7 bit Tipo S
5 bit
Offset rs2 rs1 funz3 Offset codop
[11,5] [4:0]
A differenza della
memoria istruzioni
questa può essere
usata in lettura e
scrittura. Quindi ho
bisogno di
comandi appositi.
Salto condizionato
• L’istruzione di salto condizionato ha la forma
• Anche in questo caso bisogna sommare all’attuale PC l’offset a 12 bit (dopo averlo
esteso a 64 bit con segno) che consente di fare salti da -212 a 212.
• Due note:
• L’architettura dell’insieme delle istruzioni specifica che l’indirizzo di base per il
calcolo dell’indirizzo di salto è quello dell’istruzione di salto stessa.
• L’architettura stabilisce che il campo offset sia spostato di 1 bit a sinistra per
fare sì che l’offset codifichi lo spiazzamento in numero di mezze parole
(aumentando lo spazio di indirizzamento dell’offset di un fattore 2 rispetto a
codifica dello spiazzamento in byte).
• La ragione per lo shift di uno (anziché di due) è dovuta alla presenza non
documentata sul libro di istruzioni compresse a 16 bit per alcuni processori
RISC-V.
Salto condizionato
• Nell’esecuzione della beq occorre anche un meccanismo in
base al quale decidere se aggiornare il PC a PC + 4 o a PC +
offset
Calcolo
dell’indirizzo di
salto
La ALU effettua la
sottrazione e se il
risultato è 0 si può
saltare
Progetto di un’unità di elaborazione
• Siccome abbiamo il requisito di eseguire ogni
istruzione in un ciclo di clock, non possiamo usare
un’unità funzionale più di una volta in ogni ciclo
§ Perciò dobbiamo distinguere memoria dati e memoria
istruzioni
• Inoltre occorre condividere il più possibile le varie
unità
§ Perciò occorreranno opportuni multiplexer per poter
selezionare l’input corretto all’unità funzionale tra
quelli possibili
Esempio
• Con questo circuito riusciamo a eseguire istruzioni di Tipo R e
istruzioni di trasferimento da (Tipo I) e alla memoria (Tipo S)
Per istruzione di tipo R:
• ALUSrc = 0
• MemtoReg = 0
• REGwrite = 1
• MemRead = 0
• MemWrite = 0
Per istruzione ld
• ALUSrc = 1
• MemtoReg = 1
• REGwrite = 1
• MemRead = 1
• MemWrite = 0
Per istruzione sd
• ALUSrc = 1
• MemtoReg = X
• REGwrite = 0
• MemRead = 0
• MemWrite = 1
Un esempio più completo
Circuiteria per i
salti condizionati
Con questo
DATAPATH
possiamo fare
istruzioni di tipo R,
memorizzazioni,
caricamento e salti
condizionati
Prima implementazione completa
• Per arrivare a una prima implementazione
completa partiamo dal datapath mostrato e
aggiungiamo la parte di controllo
• Implementeremo le istruzioni
üadd, sub, and, or
üld, sd
übeq
Cominciamo dalla ALU
• La ALU viene impiegata per:
§ Effettuare operazioni logico-aritmetiche (tipo R), compreso slt
§ Calcolare indirizzi di memoria (per sd e ld)
§ Sottrazione per beq
• Per queste diverse operazioni abbiamo una diversa configurazione
degli input di controllo (Linea controllo ALU)
ld/sd
beq
add
sub
AND
OR
Unità di controllo principale
• Riguardiamo i campi.
codici
operativi
codice
offset per sd
operativo
Unità di controllo principale
• Riguardiamo i campi.
registri
codice
comparazione
operativo
per beq
In questo Mux è
attiva la linea
bassa (non uso la
memoria per il
risultato)
Istruzione di tipo
R: il registro Istruzione di tipo
destinazione è R: il controllore
codificato della ALU usa un
in 11 - 7 bit di funz7 e
tutto funz3
Load
• Consideriamo ora l’istruzione
ld x5, offset(x6)
• Fasi
1. la fase di prelievo istruzione ed incremento del PC è
uguale a prima
2. prelevare x6 dal register file
3. la ALU somma il valore letto dal register file i 12 bit del
campo offset dell’istruzione, dotati di segno ed estesi a
64 bit.
4. il risultato della somma viene usato come indirizzo per
memoria dati
5. il dato prelevato dall’unità di memoria dati viene scritto
nel register file nel registro x5
Passi sul processore
Prelievo:
nel Mux rimane
attivo il ramo
alto
In questo Mux è
attiva la linea 1
(secondo input
dato da offset)
In questo Mux,
Il registro stavolta, il dato
destinazione è che passa è
codificato in 11-7 quello sopra
(provenienza
memoria)
Salto condizionale
• Consideriamo ora l’istruzione
beq x5, x6, offset
• Fasi
1. la fase di prelievo istruzione ed incremento del PC è
uguale a prima
2. prelevare x5 e x6 dal register file
3. la ALU sottrare x5 da x6. Il valore del PC viene sommato
ai 12 bit del campo offset dell’istruzione, dotati di segno,
estesi a 64 bit e fatti scorrere di una posizione a sinistra.
Il risultato costituisce l’indirizzo di destinazione del salto
4. la linea Zero in uscita dalla ALU viene usata per decidere
da quale sommatore prendere l’indirizzo successivo da
scrivere nel PC.
Sul processore
Con branch
settato, se il
risultato della ALU
è zero si attiva il
ramo uno
Il registro
target è del La ALU
tutto sottrae I due
irrilevante registri
Implementazione
• Dopo aver capito a cosa servono i vari comandi
possiamo passare all’implementazione secondo
la tabella già vista in precedenza
“Coming together is a
beginning. Keeping together
is progress. Working together
is success.” 3
Un esempio
• Supponiamo di dover fare il bucato e che questo
consista nelle seguenti attività:
7
Tempi
• I tempi richiesti per le varie fasi sono i seguenti:
8
Varie implementazioni
• Diagramma temporale per sequenza istruzioni:
Implementazione
a singolo ciclo
Implementazione
a pipeline
11
Comportamento al limite
• Se consideriamo molte più istruzioni (ad
esempio ne aggiungiamo 1000000 all’esempio
di prima) per un totale di 1000003 si passa da
un tempo di esecuzione di 1000003*800ps a
un tempo di esecuzione di 1400 +
1000000*200ps.
• Se facciamo il rapporto vediamo che
l’incremento prestazionale in termini di
throughput si avvicina al 400%
12
Vantaggi del RISC
Vantaggi delle architetture RISC
• Primo vantaggio: Tutte le istruzioni hanno la stessa lunghezza.
Questo facilita di molto il prelievo (sempre una word).
• Secondo vantaggio: I codici degli operandi sono in posizione
fissa. Questo permette di accedervi leggendo il register file in
parallelo con la decodifica dell’istruzione.
• Terzo vantaggio: Gli operandi residenti in memoria sono
possibili solo per ld/lw e sd/sw. Ciò permette di usare la ALU per
il calcolo di indirizzi (cosa che non sarebbe possibile se
dovessimo usare le ALU in due fasi della stessa istruzione).
• Quarto vantaggio: L’uso di accessi allineati fa sì che gli accessi in
memoria avvengano sempre in un ciclo di trasferimento
(impegnando un solo stadio della pipeline). 13
Hazard
• In condizioni normali la pipeline permette di
eseguire un’istruzione per ciclo di clock.
• Alle volte questo non è possibile per il
verificarsi di «condizioni critiche» (detti
«hazard»).
• Passiamo in rassegna alcune tipologie di
hazard.
14
Hazard Strutturali
• Una condizione di hazard strutturale è una
condizione per la quale l’architettura
dell’elaboratore rende impossibile
l’esecuzione di alcune sequenze di istruzioni in
pipeline.
• Ad esempio, se io disponessi di un’unica
memoria, non potrei nello stesso ciclo,
caricare istruzioni e memorizzare (o prelevare)
operandi dalla memoria.
15
Hazard sui dati
• Questo tipo di hazard si verifica quando la pipeline deve
essere messa in stallo per ottenere delle informazioni dagli
stadi precedenti
• Nell’esempio della stiratura, se mi accorgo che manca un
calzino, devo interrompere la stiratura dell’altro e andare a
cercarlo (bloccando anche le fasi precedenti).
• Nel caso del RISC-V, consideriamo la seguente sequenza
17
Torniamo all’esempio
• Una rappresentazione grafica per l’operazione della
pipeline su una delle istruzioni è la seguente:
Instruction Instruction
Write back
fetch decode
Instruction Accesso alla (scrittura
(quadratino (quadratino
Execute memoria risultato nel
memoria spezzato
register file)
istruzioni) register file)
Appena il risultato è
disponibile viene
passato avanti all’unita
che lo deve usare 19
Operand forwarding
(continua)
• L’operand forwarding funziona solo se lo stadio a
cui il dato viene propagato è successivo nel tempo
allo stadio dal quale viene prelevato
§ Non si può propagare tra l’uscita della fase di accesso
alla memoria istruzioni e l’ingresso della fase di
esecuzione dell’istruzione successiva:
üOccorrerebbe propagare un dato indietro nel tempo
20
Operand forwarding
(continua)
• Supponiamo di avere sequenza:
ld x1, 0(x2)
sub x4, x1, x5
§ x1 sarebbe disponibile solo dopo il quarto stadio della prima
istruzione
ü Troppo tardi per essere usato come input al terzo stadio della sub
ü Dovremmo imporre uno «stallo della pipeline» della durata di un ciclo
di clock («hazard sui dati» di una load)
21
Hazard sul controllo
• Il terzo tipo di hazard riguarda il controllo
(sostanzialmente i salti condizionati)
• Torniamo all’esempio del bucato
§ Supponiamo che, a seconda del livello di sporco, si
voglia decidere per un lavaggio aggressivo
§ Quello che dovrei fare è verificare la condizione dei
panni all’uscita dell’asciugatrice e su questa base
cambiare le impostazioni
§ … ma nel far questo, si blocca la pipeline (fino a che
non ho finito l’asciugatura non posso procedere al
lavaggio della prossima mandata)
22
Salto condizionato
• Il caso simile a quello del bucato si presenta
con i salti condizionati
• Supponiamo di avere un circuito molto
sofisticato che ci permette di calcolare
l’indirizzo di salto già al secondo stadio
• Comunque, dobbiamo bloccare la pipeline per
uno o due cicli
23
Esempio
25
Esempio precedente
Si assume
che il salto
non venga
effettuato
26
Circuiteria di branch prediction
• L’esempio che abbiamo appena visto non è
particolarmente sofisticato (funziona bene
solo se il branch non viene effettuato)
• Esistono circuiterie più sofisticate che
permettono di memorizzare l’esito del branch
precedente e assumere che il comportamento
si mantenga coerente
27
Unità di elaborazione finale
e relative unità di controllo
EX/MEM
MEM/WB
28
CALCOLATORI
La gerarchia di memoria
Giovanni Iacca
giovanni.iacca@unitn.it
2
Basics ...
• La memoria serve a contenere dati, bisogna poter
leggere e scrivere in memoria...
• La memoria indirizzata direttamente (memoria
principale, memoria cache):
§ è di tipo volatile, cioè il suo contenuto viene perso se si
spegne l’elaboratore
§ è limitata dallo spazio di indirizzamento del processore
• La memoria indirizzata in modo indiretto (memoria
periferica):
§ è di tipo permanente: mantiene il suo contenuto anche
senza alimentazione
§ ha uno spazio di indirizzamento “software” non limitato
dal processore 3
Basics ...
•
•
•
FF FF
A0 W1
•
•
•
A
1 Address Memory
decoder • • • • • • cells
A2 • • • • • •
• • • • • •
A3
W15
•
•
•
9
Memorie Statiche (SRAM)
T T
1 2
X Y
Word line
(indirizzi)
Bit lines
(dati)
11
SRAM: lettura e scrittura
• b’ = NOT(b): i circuiti di terminazione della linea di bit (sense/write
circuit) interfacciano il mondo esterno che non accede mai
direttamente alle celle
• La presenza contemporanea di b e NOT(b) consente un minor tasso di
errori
• Scrittura: la linea di word è alta e chiude T1 e T2; il valore presente su b
e b’, che funzionano da linee di pilotaggio, viene memorizzato nel latch
a doppio NOT
• Lettura: la linea di word è alta e chiude T1 e T2, le linee b e b’ sono
tenute in stato di alta impedenza: il valore nei punti X e Y viene
“copiato” su b e b’
• Se la linea di word è bassa, T1 e T2 sono interruttori aperti: il consumo è
praticamente nullo
12
RAM dinamiche (DRAM)
• Sono le memorie più diffuse nei PC e simili
• Economiche e a densità elevatissima (in pratica 1
solo componente per ogni cella)
§ la memoria viene ottenuta sotto forma di carica di un
condensatore
• Hanno bisogno di un aggiornamento (refresh)
continuo del proprio contenuto che altrimenti
“svanisce” a causa delle correnti parassite
• Consumi elevati a causa del rinfresco continuo
13
DRAM: cella di memoria
Word line
T
Sense C
Amplifier
14
DRAM: lettura e scrittura
RAS
A 20 - 9 ⁄ A 8 - 0 Sense / Write CS
circuits R /W
Column
address Column
latch decoder
CAS D7 D0 18
DRAM: modo di accesso veloce
20
Organizzazione base di una SDRAM
Refresh
counter
Row
address Row Cell array
latch decoder
Row/Column
address
Column Column Read/Write
address circuits & latches
counter decoder
Clock
R AS Mode register
CAS and Data input Data output
timing control register register
R /W
CS
Data
21
SDRAM: esempio di accesso in FPM
Clock
R /W
RAS
CAS
Data D0 D1 D2 D3
22
Velocità e prestazione
3
Gestione “piatta”
• Per accedere alla pratica scrivo su un bigliettino lo
scaffale dove la pratica può essere trovata, lo
affido a un attendente, e aspetto che me la porti
• Osservazioni:
§ la mia capacità di memorizzazione è molto grande
§ la gran parte del mio tempo (direi il 90%) la spreco
aspettando che l’attendente vada a prendere le
pratiche
§ Sicuramente non è una gestione efficiente del mio
tempo
• Posso essere più veloce?
4
Gestione “veloce”
• In alternativa posso tenere le pratiche sul mio
tavolo e operare solo su quelle
• Osservazioni
§ Sicuramente non perdo tempo (non ho da
aspettare attendenti che vadano in su e in giù)
§ Tuttavia il numero massimo di pratiche che
possono gestite è molto basso
• Posso operare su più dati?
5
Capre e cavoli
• Per riuscire ad avere al tempo stesso velocità e
ampiezza dell’archivio, posso fare due
osservazioni:
1. Il numero di pratiche su cui posso
concretamente lavorare in ogni giornata è
limitato
2. Se uso una pratica, quasi sicuramente dovrò
ritornare su di essa in tempi brevi… tanto
vale tenersela sul tavolo
6
Un approccio gerarchico
• L’idea è che ho un certo numero di posizioni sulla
mia scrivania
§ Man mano che mi serve una pratica la mando a
prendere
§ Se ho la scrivania piena faccio portare a posto quelle
pratiche che non mi servono più per fare spazio
• In questo modo posso contare su un’ampia
capacità di memorizzazione, *ma* la maggior
parte delle volte accedo ai dati molto
velocemente
7
Torniamo ai calcolatori
• Fuor di metafora, all’interno di un calcolatore
possiamo dare al processore (e ai programmi)
l’illusione di avere uno spazio di memoria
molto grande ma con grande velocità
• Questo è possibile grazie a due princìpi
§ Principio di località spaziale
§ Principio di località temporale
8
Località temporale
• Quando si fa uso di una locazione, la si riutilizzerà
presto con elevata probabilità
• Esempio.
Ciclo: slli x10, x22, 3
add x10, x10, x25
ld x9, 0(x10)
bne x9, x24, Esci
addi x22, x22, 1
beq x0, x0, Ciclo
Esci: …. Queste istruzioni vengono
ricaricate ogni volta che si
esegue il ciclo
9
Località spaziale
• Quando si fa riferimento a una locazione, nei
passi successivi si farà riferimento a locazioni
vicine
Ciclo: slli x10, x22, 3
add x10, x10, x25
ld x9, 0(x10)
bne x9, x24, Esci
addi x22, x22, 1 ISTRUZIONI: la modalità di
beq x0, x0, Ciclo esecuzione normale è il
Esci: …. prelievo di istruzioni
successive
DATI: Quando si scorre un
array si va per word
successive
10
Qualche dato
Facciamo riferimento a qualche cifra (relativa al 2012)
Tecnologia di Tempo di accesso tipico $ per GB (2010) $ per GB (2012)
Memoria
SRAM 0.5-2.5 ns $2000 - $5000 $500 - $1000
DRAM 50 – 70 ns $20 - $75 $10 - $20
Memoria flash 70 – 150 ns $4 - $12 $0.75 - $1
Dischi magnetici 5 000 000 – 20 000 000 ns $0.2 - $2 $0.05 - $0.1
12
Struttura della gerarchia
13
Esempio
• Due livelli
14
Terminologia
• Blocco: unità minima di informazione che può essere presente o
assente in ciascun livello
§ Un faldone nell’esempio di un archivio
• Hit rate: Frequenza di successo = frazione degli accessi in cui trovo il
dato nel livello superiore
§ Quante volte trovo il faldone che mi serve nella scrivania
• Miss Rate: 1.0 – Hit rate: frazione degli accessi in cui non trovo il
dato nel livello superiore
§ Quante volte devo andare a cercare un faldone in archivio
• Tempo di hit: Tempo che occorre per accedere al dato quando lo
trovo nel livello superiore
§ Quanto mi ci vuole a leggere un documento nel faldone sulla scrivania
• Penalità di miss: quanto tempo mi ci vuole per accedere al dato se
non lo trovo nel livello superiore
§ Tempo per spostare il faldone dall’archivio alla scrivania + tempo di
accesso al documento nel faldone (dopo che arriva sulla scrivania)
15
Considerazioni
• La penalità di miss è molto maggiore del
tempo di hit (e anche del trasferimento in
memoria di un singolo dato)
§ Da cui il vantaggio
• Quindi è importante ridurre frequenza di miss
§ In questo ci aiuta il principio di località che il
programmatore deve sfruttare al meglio
16
Cache
• Cache: posto sicuro [nascosto] dove riporre le
cose
• Nascosto perché il programmatore non vede
la cache direttamente (non vi accede)
§ L’uso della cache è interamente trasparente
• L’uso della cache fu sperimentato per la prima
volta negli anni ‘70 e da allora è stato
largamente adottato in tutti i calcolatori
17
Un esempio semplice
• Partiamo da un semplice esempio in cui i blocchi di
cache siano costituiti da una sola word
• Supponiamo che a un certo punto il processore
richieda la parola Xn che non è in cache
X4 X4
X1 X1
Xn-2 Xn-2
Xn-1 Xn-1
Cache Miss
X2 X2
Xn
X3 X3
18
Domande
• Come facciamo a capire se un dato richiesto è
nella cache?
• Dove andiamo a cercare per sapere se c’è?
• Cache a mappatura diretta: a ogni indirizzo della
memoria corrisponde una precisa locazione della cache
• Possibilità: indirizzo locazione dove un indirizzo è
mappato = (indirizzo blocco) modulo (numero di
blocchi in cache)
• Se la dimensione (=numero di blocchi) della cache è
potenza di due, è sufficiente prendere i bit meno
significativi dell’indirizzo in numero pari al
logaritmo in base due della dimensione della cache19
Esempio
• Se la nostra cache dispone di 8 parole devo
prendere i tre bit meno significativi
20
Problema
• Siccome molte parole posso essere mappate
sullo stesso blocco di cache, come facciamo a
capire se, in un dato momento, vi si trova
l’indirizzo che serve a noi?
• Si ricorre a un campo, detto tag, che contiene
un’informazione sufficiente a risalire al blocco
correntemente mappato in memoria
• Ad esempio possiamo utilizzare i bit più
significativi di una parola, che non sono usati per
la mappatura sulla cache, per trovare la locazione
di memoria corrispondente all’indirizzo mappato
da quel blocco di cache.
21
Validità
• Usiamo i bit più significativi (due nell’esempio
fatto) per capire se nel blocco di cache
memorizziamo l’indirizzo richiesto
• Inoltre abbiamo un bit di validità che ci dice se
quello che memorizziamo in un blocco di
cache in un certo momento sia o meno valido
22
Esempio
Accesso a
10110:
Miss
Accesso a
10010:
Accesso a miss
11010:
Miss
Accesso a
11010: hit
23
Esempio RISC-V a 64 bit
• Esempio
§ Indirizzo su 64 bit
§ Cache a mappatura diretta
§ Dimensioni della cache: 2n blocchi, di cui n bit usati
per l’indice
§ Dimensione del blocco di cache: 2m parole, ossia
2m+2 byte (m bit usati per individuare una parola nel
blocco), due bit per individuare un byte in una parola
24
Schema di risoluzione
• Un indirizzo viene risolto in cache con il seguente
schema (n=10, m=0, dimensione tag = 52):
Se il campo tag
è uguale ai 52 bit più
significative
dell’indirizzo e se il bit
di validità è 1, allora si
ha una hit e si da il dato
al processore,
altrimenti scatta una
miss.
25
Esempio
• Si consideri una cache con 64 blocchi di 16 byte
ciascuno. A quale numero di blocco corrisponde
l’indirizzo 1200 espresso in byte?
• Blocco identificato da:
(indirizzo blocco) modulo (numero blocchi in cache)
• Dove:
Indirizzo del Dato in byte
Indirizzo Blocco = Byte per blocco
26
Esempio
• Quindi l’indirizzo del blocco è 1200/16=75
• Blocco contente il dato è: 75 modulo 64 = 11
0 16 1200
1 17 1201 MEMORIA
.... .... PRINCIPALE
.. ... ...
15 31 1215
0 1 75
0 0 0 0
1 1 1 1 MEMORIA
.... ....
.. ... ... ... CACHE
15 15 15 15
0 1 11 63 27
Trade-off
• Blocchi di cache molto grandi esaltano la località
spaziale e da questo punto di vista diminuiscono
le probabilità di miss
• Tuttavia, a parità di dimensioni della cache avere
pochi blocchi diminuisce l’efficacia nello
sfruttamento della località temporale
• Quindi abbiamo un trade-off
• Inoltre avere dei miss con blocchi grandi porta a
un costo di gestione alto (bisogna spostare molti
byte)
28
Frequenza delle miss
Miglior trade-off:
fenomeno più
evidente per
cache piccole
29
Gestione delle miss
• La presenza di una cache non modifica molto il
funzionamento del processore (con pipeline)
fino a che abbiamo delle hit
§ Il processore non si «accorge» neanche della
presenza della cache
§ In caso di miss, bisogna generare uno stallo nella
pipeline e gestire il trasferimento da memoria
principale alla cache (ad opera della circuiteria di
controllo)
30
Gestione delle miss
• Ad esempio, per una miss sulla memoria
istruzioni, bisognerà:
1. Inviare il valore PC – 4 alla memoria (PC viene
incrementato all’inizio, quindi la miss è su PC-4)
2. Comandare alla memoria di eseguire una lettura
e attenderne il completamento
3. Scrivere il blocco che proviene dalla memoria
della cache aggiornando il tag
4. Far ripartire l’istruzione dal fetch, che stavolta
troverà l’istruzione in cache
31
Scritture
• Gli accessi in lettura alla memoria dati avvengono con
la stessa logica
• Gli accessi in scrittura sono un po’ più delicati perché
possono generare problemi di consistenza
• Una politica è la cosiddetta write-through
§ Ogni scrittura viene direttamente effettuata in memoria
principale (sia che si abbia una hit che una miss)
§ In questo modo non ho problemi di consistenza, ma le
scritture sono molto costose
§ Posso impiegare un buffer di scrittura (una coda in cui
metto tutte le scritture che sono in attesa di essere
completate)
32
Scritture
• Un’altra possibile politica è la write-back
§ Se il blocco è in cache le scritture avvengono
localmente in cache e l’update viene fatto solo
quando il blocco viene rimpiazzato (o quando una
locazione nel blocco viene acceduta da un altro
processore su architetture multi-core)
§ Questo schema è conveniente quando il
processore genera molte scritture e la memoria
non ce la fa a «stargli dietro»
33
Esempio
FastMath Intrinsity
(basato su architettura MIPS)
• Cache di 16K
• 16 parole per
blocco
• Possibilità di
operare in
write-through o
in write-back
34
Esempio
FastMath Intrinsity
(basato su architettura MIPS)
35
Cache associative
• Le cache a mappatura diretta sono piuttosto
semplici da realizzare
• Tuttavia hanno un problema: se ho spesso bisogno
di locazioni di memoria che si mappano sullo
stesso blocco, ho cache miss in continuazione
• All’estremo opposto ho una cache completamente
associativa
§ Posso mappare qualsiasi blocco in qualsiasi blocco di
cache
36
Cache completamente associativa
• Il problema per le cache completamente
associative è che devo cercare ovunque il dato (il
tag è tutto l’indirizzo del blocco)
• Per effettuare la ricerca in maniera efficiente,
devo farla su tutti i blocchi in parallelo
• Per questo motivo ho bisogno di n comparatori
(uno per ogni blocco di cache) che operino in
parallelo
• Il costo HW è così alto che si può fare solo per
piccole cache
37
Cache set-associativa
• Le cache set-associative sono un via di mezzo tra le due
che abbiamo visto
• In sostanza ogni blocco di memoria può essere
mappato su una linea di n blocchi diversi di cache (n
«vie»)
• Quindi combiniamo due idee
§ Associamo ciascun blocco di memoria a una certa linea (e
quindi a uno degli n blocchi di quella linea su cui possiamo
mappare il blocco di memoria)
§ All’interno della linea, effettuiamo una ricerca parallela
come se avessimo una cache completamente associativa
38
Mappatura del blocco
• In una cache a mappatura diretta un blocco di memoria
viene mappato in un blocco di cache dato da:
(indirizzo blocco) modulo (numero blocchi della cache)
39
Posizione del blocco
41
Schema per cache a 4 vie
42
Vantaggi dell’associatività
Tempo di esecuzione
Cache miss
45
CALCOLATORI
I/O
Giovanni Iacca
giovanni.iacca@unitn.it
3
Classificazione
• I dispositivi di I/O sono di vario tipo e possono
essere classificati in vari modi
§ Comportamento: che operazioni posso effettuare
con il dispositivo (R/W)
§ Partner: può essere un uomo o una macchina
§ Velocità di trasferimento
4
Esempi
5
Prestazioni
• A seconda del tipo di applicazione, posso
essere interessato a diverse prestazioni
§ Ad esempio per un sistema di streaming mi
interessa il throughput
§ Per un sistema bancario, mi può servire
massimizzare il numero di file di piccole
dimensioni su cui opero contemporaneamente
6
Connessione tra
processori e periferiche
• Le connessioni avvengono tramite delle strutture di
comunicazione chiamate bus
• Esistono due tipi di bus
§ Bus processore/memoria:
ü specializzati, corti e veloci
§ Bus I/O
ü possono essere lunghi e permettono il collegamento con periferiche
eterogenee
ü tipicamente, non sono collegati alla memoria in maniera diretta ma
richiedono un bus processore/memoria o un bus di sistema
• Nelle prime architetture avevamo un unico grosso bus
parallelo che collegava tutto
• Per problemi di clock e frequenze ora si usano architetture
di comunicazione più complesse fatte di più bus paralleli
condivisi e di bus seriali punto/punto
7
Terminologia
• Transazione di I/O
§ Invio indirizzo e spedizione o ricezione dei dati
• Input
§ Trasferimento di dati da una periferica verso la
memoria dove il processore può leggerla
• Output
§ Trasferimento dalla memoria al dispositivo
8
Bus sincrono
• Tra le linee di controllo deve avere clock
• Le comunicazioni avvengono con un
protocollo collegato al ciclo di clock
• Esempio: dato richiesto al clock n viene messo
sul bus al clock n+5
9
Bus sincrono: funzionamento base
Bus clock
Address and
command
Data
t0 t1 t2
11
Bus asincrono
• Per ovviare agli inconvenienti discussi si tende
a usare interconnessioni asincrone
• In sostanza non abbiamo più un clock e tutte
le transazioni sono governate da una serie di
segnali di handshake
• Questo richiede l’introduzione di apposite
linee di controllo per segnalare inizio e fine di
transazioni, ma permette di collegare
periferiche a velocità diversa
12
Ciclo di un bus asincrono
T ime
Address
and command
Master-ready
Slave-ready
Data
t0 t1 t2 t3 t4 t5
Bus cycle 13
Bus asincrono
• Pro
§ Consente di essere robusto rispetto a ritardi
§ Consente di comunicare con periferiche di tipo diverso
• Contro
§ Lento nelle interazioni (diversi segnali di controllo
devono circolare per riuscire a comunicare)
§ Circuiteria di gestione del protocollo complessa
• Spesso si usano tecnologie ibride (in cui c’è un
segnale di clock) ma prevalentemente asincrone
14
Tecnologie (asincrone) attuali
15
Esempio x86
HUB per il
controllo
memoria
HUB per
connessione
ad altri bus
Trend più
recente:
incorporare il
tutto all’interno
del processore
16
Prospettiva del programmatore
• Rimane da capire
§ Come trasformare una richiesta di I/O in un comando per la
periferica
§ Come trasferire i dati
§ Qual è il ruolo del sistema operativo?
• Riguardo al SO occorre osservare
§ Programmi che condividono il processore condividono anche il
sistema di I/O
§ I trasferimenti dati vengono spesso effettuati usando interrupt,
che hanno un impatto sulle funzionalità del SO
ü Quindi devono essere eseguiti in una particolare modalità del
processore (supervisor) cui solo il codice del kernel può accedere
§ Il controllo di operazioni I/O spesso si interseca con
problematiche di concorrenza
17
Funzionalità richieste al SO
• Garantire che un dato utente abbia accesso ai
dispositivi di I/O cui ha diritto (permessi) di
accedere
• Fornire comandi di alto livello per gestire le
operazioni di basso livello
• Gestire le interruzioni generate dai dispositivi di
I/O (in maniera simile a quanto avviene con le
eccezioni generate nei programmi)
• Ripartire l’accesso a ciascun dispositivo in
maniera equa tra i vari programmi che lo
richiedono
18
Requisiti
• Per implementare le funzionalità appena
discusse occorre
§ Rendere possibile al SO di inviare comandi alle
periferiche
§ Rendere possibile ai dispositivi notificare la
corretta esecuzione di un’operazione
§ Consentire trasferimenti diretti di dati tra
dispositivo e memoria
19
Come impartire i comandi ai dispositivi
• Questo si fa fornendo sulle relative linee di
bus alcune «parole» di controllo
• Può essere fatto in due modi:
§ Scrivendo/leggendo in particolari locazioni di
memoria (memory mapped I/O)
§ Tramite alcune istruzioni speciali (dedicate all’I/O)
20
Esempio
• Scrivendo una particolare parola in una locazione di memoria
associata al dispositivo
§ Il sistema di memoria ignora la scrittura
§ Il controllore di I/O intercetta l’indirizzo particolare e trasmette il dato
al dispositivo sotto forma di comando
• Queste particolari locazioni di memoria NON sono accessibili ai
programmi utente ma solo al sistema operativo (quindi occorre una
chiamata di sistema che faccia commutare il processore in modalità
supervisore)
• Il dispositivo stesso può usare queste locazioni per trasmettere dati
o pre-segnalare il suo stato
• Ad esempio posso chiedere la stampa di un carattere a terminale, e
a stampa finita un particolare bit di un registro di stato mappato in
memoria verrà commutato (a 1 per indicare la corretta stampa)
21
Come trasmettere/ricevere i dati
22
Esempio
• Input: lettura dalla tastiera in x7
lui x5, 0xffff #ffff0000 Memory Map
Waitloop: lw x6, 0(x5) #control
andi x6, x6,0x0001
beq x6, x0, Waitloop #ffff0000 input control reg
lw x7, 4(x5) #data #ffff0004 input data reg
#ffff0008 output control reg
• Output: stampa del dato da x7 #ffff000C output data reg
lui x5, 0xffff #ffff0000
Waitloop: lw x6, 8(x5) #control
andi x6,x6,0x0001
beq x6,x0, Waitloop
sw x7, 12(x5) #data
• Questo ciclo di attesa è chiamato polling
23
Costo del polling
• Consideriamo un processore a 500Mhz e
supponiamo che occorrano 400 cicli di clock
per un’operazione di polling. Qual è il costo
percentuale?
§ Esempio 1: Mouse. Per non perdere movimenti da
parte dell’utente occorre acquisire il dato 30 volte
al secondo.
§ Esempio 2: Hard disk. I dati vengono trasferiti in
blocchi di 16 byte a 8MB/s senza la possibilità di
perdite.
24
Esempio 1 (Mouse)
• Cicli di clock al secondo spesi per il polling
= 30 * 400 = 12000 clocks/sec
§ % Processor for polling:
12*103/500*106 = 0.002%
Þ Fare polling sul mouse «ruba» un utilizzo di processore
trascurabile
26
CALCOLATORI
I/O
Giovanni Iacca
giovanni.iacca@unitn.it
7 0
Receiver data unused
(0xffff0004)
received byte
(read only)
1 0
Receiver control unused
(0xffff0000)
interrupt enable ready
(read only)
4
Interruzioni di programma
• Un’interruzione I/O è un segnale usato per segnalare al processore che la
periferica è pronta ad eseguire il trasferimento richiesto
§ Le interruzioni possono avere diverso grado di urgenza (è possibile definire
priorità).
§ Occorre un modo per segnalare al processore quale periferica richiede
l’interruzione.
• Le interruzioni I/O sono sempre asincrone rispetto all’esecuzione delle
istruzioni
§ Non esistono particolari istruzioni assembly per eseguire le interruzioni.
Un’interruzione può arrivare mentre una qualsiasi istruzione viene eseguita, e
dà comunque modo di terminare l’esecuzione dell’istruzione.
ü Il programmatore può spesso differire l’esecuzione dell’interruzione a un momento più
conveniente (es. sezioni non interrompibili nel codice del kernel).
• Vantaggio
§ Non occorre interrompere l’esecuzione del programma se non quando il dato
può essere effettivamente riferito in memoria.
• Svantaggio – occorre un hardware speciale per:
§ Permettere ai dispositivi di I/O di generare un’interruzione.
§ Rilevare l’interruzione, salvare lo stato del processore per eseguire una
particolare routine di servizio (Interrupt Service Routine, ISR) e poi riprendere
dal punto dove si era interrotto.
5
Input a interruzione di programma
1. Interrupt
Processor dall’input add
sub user
and program
2.1 Salva PC or
beq
Memory Receiver
2.3 Servi
2.2 Salta l’interrupt
Keyboard alla ISR
lbu input
sb interrupt
... service
2.4 Ritorno al jr routine
Nel mezzo c’è una codice utente
commutazione in modalità
«Supervisore» (solo il SO può memory
gestire l’interruzione)
6
Esempio controllo terminale SPIM
1. La periferica indica con un’interruzione che ha un nuovo carattere
dalla tastiera nell’opportuno registro di ricezione
Receiver data unused 65 Byte
(0xffff0004) ricevuto
ü Contestualmente il bit pronto viene messo a uno nel registro di controllo
7
Che ci si guadagna?
• Ritorniamo all’esempio di prima dell’hard disk e supponiamo che un
interrupt costi 500 cicli di clock (plausibile che costi di più del
polling)
• Se le interruzioni vengono generate alla frequenza di polling
§ Disk Interrupts/sec = 8 MB/s /16B
= 500K interrupts/sec
§ Disk Polling Clocks/sec = 500K * 500
= 250,000,000 clocks/sec
§ % Processor: 250*106/500*106= 50%
§ Sembrerebbe che non ci sia guadagno…anzi
• Tuttavia se l’hard disk è attivo solo per il 5% del tempo, gli interrupt
generati saranno il 5% e la spesa di processore sarà:
5% * 50%=2.5%
• Traps (Eccezioni)
§ Causate da eventi interni al programma
ü Condizioni eccezionali (ad es. arithmetic overflow, undefined instr.)
ü Errori (ad es. hardware malfunction, memory parity error, segmentation fault)
ü Fault (ad es. non-resident page – page fault)
§ Sincrone all’esecuzione del programma
§ Gestite da un trap handler
§ E’ possibile riprovare ad eseguire l’istruzione che ha causato l’eccezione o
abortire il programma
• Environment call/break
§ La environment call (istruzione ecall) è causata da una esplicita richiesta di un
servizio di sistema (stampa di un carattere, un intero, …) attraverso esplicita
ecall.
§ La environment break (istruzione ebreak) è causata da una esplicita chiamata a
ebreak per motivi diagnostici o di debug (ad es. la break nel GDB) 10
Gestione delle eccezioni
Exception Handler
• Esistono vari metodi per gestire le eccezioni
§ Salto diretto all’indirizzo della routine di gestione
§ Vettore di interruzione
11
Salto diretto ad indirizzo
della routine di gestione
• Salto diretto ad un indirizzo
specifico: Mem
causa SCAUSE
PC <- ind_proc_gest
ind_proc_gest STVEC
§ Nel RISC-V Routine di
ü Indirizzo di gestione è contenuto nel gestione
registro speciale STVEC
ü La causa dell’eccezione è
memorizzata in registro speciale
SCAUSE
• Vantaggi:
§ Non è necessario fare un accesso in
memoria per prelevare l’indirizzo
della routine di gestione
• Svantaggi
§ Nella routine di gestione occorre
analizzare la causa dell’eccezione
12
Vettore di interruzione
• Memorizzo in una tabella gli indirizzi
delle Routine di Gestione per ogni
possible causa:
causa SCAUSE
Vettore di Interruzione
PC <- Mem[base + causa*4] Ind. routine 0 ind_proc_gest STVEC
Ind. routine 1
• Nel RISC-V causa*4
§ Indirizzo base delle routine di gestione
delle eccezioni contenuto in registro Ind. routine m
speciale STVEC
§ La causa dell’eccezione è memorizzata Routine di
in registro speciale SCAUSE gestione
• Vantaggi
§ La causa dell’eccezione è nota ed
utilizzata per identificare la routine
relativa di gestione
• Svantaggi
§ E’ necessario accedere alla memoria
per prelevare l’indirizzo della routine di
gestione
13
Salvataggio dello stato della
macchina al verificarsi di una
eccezione
• Vari approcci
§ Salvataggio sullo stack
§ Salvataggio in registri ausiliari (sia visibili che non)
§ Salvataggio in registri speciali
• Nel RISC-V
§ Salvataggio sullo stack e su registri speciali
üSEPC, SCAUSE, SSTATUS, STVAL (o BadVaddr), …
14
Supporto alla gestione delle eccezioni
• Il RISC-V ha vari registri a 64 bit
nel RISC-V
§ SEPC - contiene indirizzo dell’istruzione che ha generato l’eccezione
§ SSTATUS - contiene i bit di abilitazione globale degli interrupt
§ SCAUSE - I bit 63 e [3-0] codificano le possibili sorgenti di eccezione
ü Illegal instruction = {0, 2}
ü Breakpoint = {0, 3}
ü Time interrupt = {1, 5}
ü External interrupt = {1,9}
ü …
§ STVAL – Supervisor Trap Value – contiene l’indirizzo al quale si è verificato un riferimento
errato alla memoria
§ SIE – Supervisor Interrupt Enable – specifica abilitazioni più fini degli interrupt in attesa
§ SIP – Supervisor Interrupt Pending – monitoraggio degli interrupt in attesa
§ STVEC – Supervisor Trap Vector – Indirizzo base della lista dei vettori di interrupt
§ SSCRATCH – Registro per salvataggi temporanei
• SIE (S- Interrupt Enable) abilita globalmente gli interrupt a quel dato livello – per
non «entrare in loop» viene subito disabilitato (0)
• SPIE (S- Previous Interrupt Enable) indica lo stato precedente del bit SIE al
momento in cui si verifica l’eccezione
16
Controllo fine degli
interrupt
9 5 10
SIE
SEIE STIE SSIE
9 5 10
SIP
SEIP STIP SSIP
E poi…
STVEC PC Salto alla routine di gestione
1Tipicamente si usa SRET per ritornare da una routine di gestione interrupt o eccezione
18
SCAUSE Register
63 3 0
SCAUSE
Int CODE
19
Lettura/Scrittura di SEPC,
SCAUSE, STVEC, STVAL, …
• Per modificare i contenuti dei registri speciali CSR:
§ CSR Atomic Read & Write (/Immediate): CSRRW rd, csr, rs CSRRWI rd, csr, imm
§ CSR Atomic Read & Set bits (/Immediate): CSRRS rd, csr, rs CSRRSI rd, csr, imm
§ CSR Atomic Read & Clear bits (/Immediate): CSRRC rd, csr, rs CSRRCI rd, csr, imm
• Esempi
CSRRW t0,stvec,t1 # t0 ß stvec, stvec ß t1
CSRRW x0,sscratch,t1 # sscratch ß t1 (in questo caso rd = x0: CSR non letto)
• Nota
§ Se il vecchio valore di CSR non serve, CSRRS/CSRRSI e CSRRC/CSRRCI vengono chiamate con rd=x0
20
Supporto del RISC-V per la
gestione delle eccezioni
• I soli tipi di eccezioni che possono essere generate
nell’implementazione della CPU analizzata sono:
§ Esecuzione di una istruzione non valida
§ Malfunzionamenti hardware
• In caso di eccezione il processore deve:
§ Salvare l’indirizzo dell’istruzione che ha generato l’eccezione nel
registro program counter dell’eccezione (SEPC - supervision exception
program counter register)
§ Salvare la causa dell’eccezione nel registro causa dell’eccezione
(SCAUSE – supervisor cause exception register)
§ Trasferire il controllo ad un indirizzo specifico del sistema operativo
per gestire l’eccezione
§ Terminata la gestione ripristinare lo stato del processore e ritornare
all’esecuzione precedente (se possibile)
• Le eccezioni sono trattate nel RISC-V come se fossero degli hazard
sul controllo
21
Supporto del RISC-V per la
gestione delle eccezioni (cont.)
• Per la gestione di una eccezione in IF si usa stesso meccanismo di gestione degli
errori di predizione per i salti (convertendo l’istruzione in una nop)
• Assumendo che l’indirizzo della prima istruzione del codice di gestione delle
eccezioni sia 0000 0000 1C09 0000esa, per saltare al codice di gestione
dell’eccezione occorre aggiungere una linea che porta il valore
0000 0000 1C09 0000esa al multiplexer che seleziona il nuovo valore del PC
22
Supporto del RISC-V per la
gestione delle eccezioni (cont.)
• Problema:
§ Se non si interrompe l’istruzione prima della fine della sua esecuzione
non sarà più possibile vedere il valore originale del registro di
destinazione (ad es. x1, che potrebbe essere «sporcato» dalla
destinazione dell’istruzione che ha generato eccezione)
ü Se supponiamo che eccezione venga riconosciuta nello stadio EX si può
utilizzare EX.Flush per prevenire che l’istruzione scriva il registro destinazione
nello stadio WB
• Nota:
§ Molte eccezioni richiedono di completare normalmente l’esecuzione
dell’istruzione che ha causato l’eccezione
ü Il modo più semplice per ottenere ciò consiste nell’eliminare l’istruzione e farla
eventualmente ripartire dall’inizio dopo che l’eccezione è stata gestita
23
Supporto del RISC-V per la
gestione delle eccezioni (cont.)
Nota:
errore
sul libro