diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/assemble.sh b/2 - Architettura dei calcolatori/0 - Script forniti/assemble.sh new file mode 100644 index 0000000..e71a142 --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/assemble.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Assemble RISV-V assembly (.s) files +riscv64-unknown-elf-as -g -o "$1.o" "$1.riscv" +# Link RISC-V object (.o) files +riscv64-unknown-elf-ld -o "$1.elf" "$1.o" + +exit $? diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/disassemble.sh b/2 - Architettura dei calcolatori/0 - Script forniti/disassemble.sh new file mode 100644 index 0000000..c250d97 --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/disassemble.sh @@ -0,0 +1,4 @@ +#!/bin/bash +riscv64-unknown-elf-objdump "$1.elf" + +exit $? diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/g++.sh b/2 - Architettura dei calcolatori/0 - Script forniti/g++.sh new file mode 100644 index 0000000..502181a --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/g++.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Compile using g++ +riscv64-unknown-elf-g++ -o "$1.elf" "$1.cc" + +exit $? \ No newline at end of file diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/gcc.sh b/2 - Architettura dei calcolatori/0 - Script forniti/gcc.sh new file mode 100644 index 0000000..f7b37b0 --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/gcc.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Compile using g++ +riscv64-unknown-elf-gcc -o "$1.elf" "$1.cc" + +exit $? diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/gprof.sh b/2 - Architettura dei calcolatori/0 - Script forniti/gprof.sh new file mode 100644 index 0000000..65a1d9d --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/gprof.sh @@ -0,0 +1,4 @@ +#!/bin/bash +riscv64-unknown-elf-gprof "$1.elf" + +exit $? diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/readelf.sh b/2 - Architettura dei calcolatori/0 - Script forniti/readelf.sh new file mode 100644 index 0000000..b3c01e4 --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/readelf.sh @@ -0,0 +1,4 @@ +#!/bin/bash +riscv64-unknown-elf-readelf "$1.elf" -a + +exit $? \ No newline at end of file diff --git a/2 - Architettura dei calcolatori/0 - Script forniti/run.sh b/2 - Architettura dei calcolatori/0 - Script forniti/run.sh new file mode 100644 index 0000000..c49363a --- /dev/null +++ b/2 - Architettura dei calcolatori/0 - Script forniti/run.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Sintassi: ./run.sh nomefile +# Non mettete l'estensione! +# Potrebbe esserci bisogno di installare xterm prima +# Se non funziona, installatelo con +# sudo apt install xterm + +echo "Cleaning phase" +rm "$1.o" +rm "$1.elf" +echo "Compilation phase" +riscv64-unknown-elf-as -g -o "$1.o" "$1.riscv" +riscv64-unknown-elf-ld -o "$1.elf" "$1.o" +echo "Debugging phase" +xterm -e "qemu-riscv64 -g 1234 \"$1.elf\"" & +xterm -e "riscv64-unknown-elf-gdb --ex=\"target remote localhost:1234\" \"$1.elf\"" & +wait diff --git a/2 - Architettura dei calcolatori/1 - Appunti/00 - Informazioni sul corso.md b/2 - Architettura dei calcolatori/1 - Appunti/00 - Informazioni sul corso.md new file mode 100644 index 0000000..4fa84b0 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/00 - Informazioni sul corso.md @@ -0,0 +1,24 @@ +# Architettura dei Calcolatori + +Docente: [**Andrea Mariongiu**](mailto:andrea.mariongiu@unimore.it) + +Crediti: **9 CFU** (72 ore di lezione) + +### Materiale + +Libri: +- `ISBN9780128122761` +- `ISBN9788871924618` + +**[Sito web](http://algo.ing.unimo.it/people/andrea/Didattica/Architetture/index.html)** + +[Slides 2017](http://dolly.fim.unimore.it/2017/course/view.php?id=57) + +### Esame + +- Prova scritta + - Serie di domande a risposta singola, multipla e libera, simile a Programmazione 1 + - Rimane valida fino alla fine della sessione seguente +- Prova orale + - Può essere sostituita con lo sviluppo di un _progettino_ + diff --git a/2 - Architettura dei calcolatori/1 - Appunti/01 - Principi.md b/2 - Architettura dei calcolatori/1 - Appunti/01 - Principi.md new file mode 100644 index 0000000..6ab0c93 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/01 - Principi.md @@ -0,0 +1,47 @@ +# I principi dell'architettura dei calcolatori + +## Gli otto grandi principi + +- Progetta per la **Legge di Moore** +- **Astrai** per semplificare il design +- Velocizza i **casi più comuni** +- Sfrutta il **parallelismo** + - Instruction level parallelism + - In una sola CPU vengono realizzati più calcoli in parallelo +- Sfrutta le **pipeline** + - Pipelining + - Separare le azioni del processore in fasi + - Il fetch è separato dal decoding, mentre decodifico un'istruzione posso già fetchare quell'altra +- **Prevedi** le istruzioni successive +- **Gerarchizza** le memorie per velocità di accesso + - La CPU cerca sempre di accedere alle memorie più veloci +- Rendi affidabile con la **ridondanza** + +### Parallelismo + +> Chiedo a 100 persone di fare una moltiplicazione ciascuna invece che fare 100 moltiplicazioni io da solo + +### Pipelining + +![Senza](https://upload.wikimedia.org/wikipedia/commons/2/2c/Nopipeline.png) + +![Con](https://upload.wikimedia.org/wikipedia/commons/2/21/Fivestagespipeline.png) + +### Gerarchia delle memorie + +> Hit or miss? I hope they never miss. + +La CPU prova sempre ad accedere alle memorie più in alto di questa lista, effettuando un **hit** se trova i dati che le servono in una data memoria ed effettuando un **miss** se invece non trova i dati necessari e deve accedere a uno strato inferiore. + +- [Cache](https://it.wikipedia.org/wiki/Cache) (piccolissima e velocissima) +- [Random Access Memory](https://it.wikipedia.org/wiki/RAM) (piccola e veloce) +- [Solid State Drive]() (medio e di media velocità) +- [Hard Disk Drive](https://it.wikipedia.org/wiki/Disco_rigido) (grosso, ma lento) +- [Tape storage](https://en.wikipedia.org/wiki/Magnetic_tape_data_storage) (enorme, ma lentissimo) + +I _miss_ rallentano l'esecuzione dei programmi, in quanto causano ritardi nel ritrovamento dei dati, passando da _meno di 1 µs_ per le cache a _più di 1 ms_ per gli hard disk. + +Inoltre, esistono due tipi di cache, anch'essi gerarchizzati: + +- [SRAM](https://it.wikipedia.org/wiki/SRAM) +- [DRAM](https://it.wikipedia.org/wiki/DRAM) diff --git a/2 - Architettura dei calcolatori/1 - Appunti/02 - Dentro il processore.md b/2 - Architettura dei calcolatori/1 - Appunti/02 - Dentro il processore.md new file mode 100644 index 0000000..f7a8d96 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/02 - Dentro il processore.md @@ -0,0 +1,41 @@ +# Dentro il processore + +## Come sono fatti i circuiti integrati? + +Sono composti da un layer di silicio (semiconduttore), più delle maschere di conduttori (µfili metallici), isolanti (plastica e vetro) e transistor. + +### Processo di produzione + +- Lingotto di silicio +- Tagliato in _wafer_ da 2mm +- Creazione dei _patterned wafer_ +- Testing dei _patterned wafer_ +- Taglio i _tested wafers_ +- Creo un package con i _tested dies_ +- Testo i _packaged dies_ +- Spedisco i _tested packaged dies_ ai clienti. + +### Lo yield + +Lo yield è quanti _tested packaged dies_ riesco a produrre da un singolo lingotto di silicio. + +## Come definiamo la performance di un processore? + +Ci sono varie metriche per definirla: + +- **Response time**: tempo impiegato a compiere un'operazione +- **Throughput**: operazioni fatte in un'unità di tempo + +### Il clock + +Il clock della CPU è il timer che scandisce le operazioni della CPU. + +Ha, ovviamente, un periodo e una frequenza costanti. + +### Il CPU time + +Il tempo impiegato da una CPU a compiere una determinata operazione è dato da `numero di cicli di clock richiesti * periodo di clock`, ed è detto **CPU time**. + +### Il Cost per instruction (CPI) + +Alcune istruzioni di una CPU potrebbero richiedere più di un ciclo di clock. diff --git a/2 - Architettura dei calcolatori/1 - Appunti/03 - Assembly.md b/2 - Architettura dei calcolatori/1 - Appunti/03 - Assembly.md new file mode 100644 index 0000000..6f29544 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/03 - Assembly.md @@ -0,0 +1,165 @@ +# Assembly + +Useremo il RISC-V. +Esso è little-endian, ed è indirizzato a gruppi di 8 bit. + +## Registri + +RISC-V ha a disposizione **32** registri da 64 bit, che vanno da `x0` a `x31`. + +Un dato a 32 bit viene chiamato **word**, uno a 64 bit viene chiamato **doubleword**. + +- `x0`: costante +- `x1`: indirizzo risultato +- `x2`: indirizzo della cima dello stack +- `x3`: indirizzo dell'area di memoria con variabili globali +- `x4`: indirizzo dell'area di memoria specifica del thread attivo +- `x5`: registro temporaneo +- `x6`: registro temporaneo +- `x7`: registro temporaneo +- `x8`: frame pointer +- `x9`: registro salvato, può essere copiato in memoria +- `x10`: argomento o risultato delle funzioni +- `x11`: argomento o risultato delle funzioni +- `x12`: argomento delle funzioni +- `x13`: argomento delle funzioni +- `x14`: argomento delle funzioni +- `x15`: argomento delle funzioni +- `x16`: argomento delle funzioni +- `x17`: argomento delle funzioni +- `x18`: registro salvato, può essere copiato in memoria +- `x19`: registro salvato, può essere copiato in memoria +- `x20`: registro salvato, può essere copiato in memoria +- `x21`: registro salvato, può essere copiato in memoria +- `x22`: registro salvato, può essere copiato in memoria +- `x23`: registro salvato, può essere copiato in memoria +- `x24`: registro salvato, può essere copiato in memoria +- `x25`: registro salvato, può essere copiato in memoria +- `x26`: registro salvato, può essere copiato in memoria +- `x27`: registro salvato, può essere copiato in memoria +- `x28`: registro temporaneo +- `x29`: registro temporaneo +- `x30`: registro temporaneo +- `x31`: registro temporaneo + +## Operazioni + +### Operazioni aritmetiche + +Sono sempre formate da tre operandi: registro a cui il risultato verrà assegnato e gli argomenti dell'operazione. + +- `add`: addizione +- `sub`: sottrazione +- `addi`: addizione di costanti + +#### Esempio + +```assembly +add a, b, c +``` + +##### Equivalente Python + +```python +a = b + c +``` + +### Operazioni di trasferimento dati + +Permettono di caricre / scaricare dati dalla memoria. + +### Operazioni logiche + +Consentono di effettuare operazioni di logica. + +- `and` +- `or` +- ... + +### Operazioni di shift + +Consentono di spostare i bit di un registro di alcune posizioni, moltiplicando o dividendo effettivamente per potenze di 2. + +### Operazioni condizionali + +Consentono di saltare ad un'altra serie di istruzioni se certe condizioni si verificano. + +- `blt`: minore di, signed +- `bge`: maggiore di, signed +- `bltu`: minore di, unsigned +- `bgeu`: maggiore di, unsigned + +### Operazioni non-condizionali + +Consentono di saltare ad un'altra serie di istruzioni in qualunque caso. + +#### Chiamare una procedura + +> `jal x1, ProcedureLabel` + +Salva l'indirizzo dell'istruzione successiva in x1, poi salta a ProcedureLabel. + +#### Ritornare da una procedura + +> `jalr x0, 0(x1)` + +Ritorna all'indirizzo puntato da `x1`. + +## Memoria + +Per accedere a una posizione in memoria, si usa `offset(indirizzo)`. + +##### Esempio + +`64(x22)` accede al 64° byte dopo la posizione di memoria archiviata in `x22`. + +## Segni + +E' possibile usare rappresentazione standard o a complemento-a-2. + +Possiamo usare la **sign extension** quando abbiamo un dato che vogliamo portare a un numero di bit maggiore di quello che è. + +##### Esempio + +``` +[0]101 0101 --> 0000 0000 [0]101 0101 +[1]100 0000 --> 1111 1111 [1]100 0000 +``` + +### Operazioni di sincronizzazione + +- `lr`: load reserved +- `sc`: store conditional + +#### Load reserved + +``` +lr rd, (rs1) +``` + +> Non ho la minima idea di come funzioni. + +Controlla se `rs1` è bloccato; se sì, mette un valore != 0 in `rd`...? + +> Forse... crea un lock in `rd`? + +#### Store conditional + +``` +sc rd, rs2, (rs1) +``` + +> Non ho la minima idea di come funzioni. + +Copia il dato da `rs2` a `rs1`, se non è bloccato, e metti `x0` in `rd`...? + +> Carica un dato... se ho ancora il lock, altrimenti, metti `x0` in `rd`. + +#### Unlock + +``` +sd x0, 0(x20) +``` + +## Operazioni floating point + diff --git a/2 - Architettura dei calcolatori/1 - Appunti/04 - Hello world.riscv b/2 - Architettura dei calcolatori/1 - Appunti/04 - Hello world.riscv new file mode 100644 index 0000000..6152a71 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/04 - Hello world.riscv @@ -0,0 +1,29 @@ +# Definisci delle costanti +.equ _SYS_EXIT, 93 # Syscall exit +.equ _SYS_WRITE, 64 # Syscall write + +# Inizio del programma +.global _start + +# Variabili statichie inizializzate a 0 +.section .bss + +# Dati modificabili +.section .data + +# Dati in sola lettura +.section .rodata + msg: .string "Hello mondo!\n" + +# Testo del programma +.section .text + _start: + # Chiamata a WRITE(stream, primocarattere, lunghezza) + li a0, 0 # Print to stdout + la a1, msg # Stampa il messaggio in msg + li a2, 13 # Stampa 13 caratteri dopo msg + li a7, _SYS_WRITE # Seleziona la syscall WRITE + ecall + + li a7, _SYS_EXIT + ecall diff --git a/2 - Architettura dei calcolatori/1 - Appunti/05 - Primo programma.riscv b/2 - Architettura dei calcolatori/1 - Appunti/05 - Primo programma.riscv new file mode 100644 index 0000000..8659db2 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/05 - Primo programma.riscv @@ -0,0 +1,33 @@ +# Che bello! Adoro l'assembly! +# Questo programma funziona, è verificato! +.global _start + +# La sezione .data contiene i dati del programma +.section .data + + # Creo una variabile con etichetta "ciao" + ciao: .string "0\n\0" + +# La sezione .rodata contiene i dati del programma in sola lettura +.section .rodata + +# La sezione .text contiene il testo del programma +.section .text + + # Creo una label _start che funzionerà da entrypoint + _start: + li t1, 10 # Voglio ripetere il ciclo 10 volte + li a7, 64 # PRINT(stream, indirizzo, numerobytes) è la syscall 64 + li a0, 0 # stream è il primo argomento: vogliamo scrivere su stdout (0) + la a1, ciao # indirizzo è il secondo argomento: partiamo dall'etichetta ciao + li a2, 2 # numerobytes è il terzo argomento: vogliamo stampare 2 bytes + print: + ecall # PRINT(a0, a1, a2) + lbu t0, 0(a1) # Carica il byte con il 0 + addi t0, t0, 1 # Aggiungici 1 + sb t0, 0(a1) # Ricaricalo in memoria + addi t1, t1, -1 # Togli 1 dal numero di cicli rimasti + bne t1, zero, print # Se rimangono dei cicli, torna indietro e falli! + end: + li a7, 93 # Tutti i programmi terminano chiamando la syscall 93, EXIT() + ecall # EXIT() diff --git a/2 - Architettura dei calcolatori/1 - Appunti/06 - Stack.riscv b/2 - Architettura dei calcolatori/1 - Appunti/06 - Stack.riscv new file mode 100644 index 0000000..8528bec --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/06 - Stack.riscv @@ -0,0 +1,40 @@ +# Che bello! Adoro l'assembly! +# Funziona! +.global _start + +# La sezione .data contiene i dati del programma +.section .data + +# La sezione .rodata contiene i dati del programma in sola lettura +.section .rodata + +# La sezione .text contiene il testo del programma +.section .text + + # Creo una label _start che funzionerà da entrypoint + _start: + li s0, 5 # Scrivi il numero 5 + li s3, 3 # COSTANTE: 3 + + # Pushing phase + pushing: + sb s0, 0(sp) # Metti nello stack s0 + addi sp, sp, 1 # Aumenta di 1 lo stack pointer + addi s2, s2, 1 # Aumenta di 1 la dimensione dello stack + addi s0, s0, -1 # Diminuisci di 1 il numero da fattorializzare + bgeu s0, s3, pushing # Continua a pushare finchè non arrivi a 2 + li s1, 2 + + # Popping phase + popping: + addi sp, sp, -1 # Diminuisci di 1 lo stack pointer + # Attenzione: diminuisco lo stack pointer PRIMA di caricare dallo stack, altrimenti caricarei il nulla + lb s0, 0(sp) # Prendi dallo stack il prossimo numero + addi s2, s2, -1 # Diminuisci di 1 la dimensione dello stack + mul s1, s1, s0 # Effettua la moltiplicazione + bne s2, zero, popping # Se ci sono altri valori nello stack, continua a moltiplicare + + # Ending phase + ending: + li a7, 93 + ecall diff --git a/2 - Architettura dei calcolatori/1 - Appunti/07 - Prodotto scalare.riscv b/2 - Architettura dei calcolatori/1 - Appunti/07 - Prodotto scalare.riscv new file mode 100644 index 0000000..49dd112 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/07 - Prodotto scalare.riscv @@ -0,0 +1,48 @@ +# Che bello! Adoro l'assembly! +# Ommioddiofunzionawowimawizard +.global _start + +# La sezione .data contiene i dati del programma +.section .data + + primo: .word 1, 2, 3, 4, 5 + secondo: .word 1, 2, 3, 4, 5 + +# La sezione .rodata contiene i dati del programma in sola lettura +.section .rodata + +# La sezione .text contiene il testo del programma +.section .text + + # s0: primo[s0] + # s1: secondo[s0] + # s2: monomio + # s3: indirizzo da cui prendere il valore + # s4: indice + # s5: dimensione array + # a0: somma + + _start: + li s2, 0 + li a0, 0 + li s4, 0 + li s5, 5 + + somma: + la s3, primo + add s3, s3, s4 + lw s0, 0(s3) + + la s3, secondo + add s3, s3, s4 + lw s1, 0(s3) + + mulw s2, s1, s0 + add a0, a0, s2 + addi s4, s4, 4 + addi s5, s5, -1 + bne zero, s5, somma + + _end: + li a7, 93 + ecall diff --git a/2 - Architettura dei calcolatori/1 - Appunti/08 - Appunti e deduzioni.md b/2 - Architettura dei calcolatori/1 - Appunti/08 - Appunti e deduzioni.md new file mode 100644 index 0000000..be4d540 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/08 - Appunti e deduzioni.md @@ -0,0 +1,57 @@ +# Appunti + +## Registri + +I registri sono divisi in tre tipi: di **proprietà del chiamante**, di **proprietà del chiamato** (anche se in realtà è solo una convenzione) e... gli **altri**. + +Tutti i registri sono grandi 64 bit: possono quindi contenere una _doubleword_. + +### Proprietà del chiamante + +Questi registri **non devono essere sovrascritti** quando viene chiamata una funzione, o se vengono sovrascritti, **devono essere ripristinati** prima che la funzione restituisca. + +Essi sono: +- `s1..s11`: **Saved** registers, registri generici che possono essere usati per qualsiasi cosa. +- `sp`: **Stack Pointer**, il primo indirizzo dello stack disponibile per la scrittura. Va _decrementato_ quando si aggiunge qualcosa allo stack. +- `fp`: **Frame Pointer**, punta all'inizio dello stack. + +### Proprietà del chiamato + +Questi registri **possono essere sovrascritti** quando viene chiamata una funzione: bisogna salvarli in memoria prima di chiamarla! + +Essi sono: +- `t0..t6`: **Temporary** registers, registri generici che possono essere usati per qualsiasi cosa. +- `ra`: **Return Address**, l'indirizzo a cui continuare l'esecuzione del programma dopo aver terminato una funzione. _Solitamente si usa solo con `jal` e `jalr`!_ +- `a0..a7`: Function **Arguments**. Se all'interno di una funzione, contengono gli argomenti che sono stati passati. Se all'esterno di una funzione, contengono i valori restituiti dall'ultima funzione chiamata. + +### Altri + +- `zero`: **Zero**, registro che vale sempre 0, anche dopo essere stato sovrascritto da altro. Come `/dev/null` su Linux. +- `gp`: **Global Pointer**, punta a un indirizzo _[(?)](https://groups.google.com/a/groups.riscv.org/forum/#!topic/sw-dev/60IdaZj27dY)_ +- `tp`: **Thread Pointer**, punta al thread-local storage _[(?)](https://groups.google.com/a/groups.riscv.org/d/msg/sw-dev/cov47bNy5gY/zLnlKkw9CQAJ)_ +- `pc`: **Program Counter**, punta all'istruzione che sta venendo eseguita. + +## Comandi preprocessore + +Scrivendo file assembly per RISC-V, è possibile aggiungere direttive per il preprocessore. + +Per ora ho scoperto queste: + +- `.global INDIRIZZO`: definisce l'indirizzo a cui inizia il programma. _(Forse setta il `gp` a quell'indirizzo?)_ +- `.equ NOME, VALORE`: definisce una costante con uno specifico nome. _Il nome delle costanti dovrebbe iniziare sempre con un underscore per convenzione._ +- `.section [.data|.rodata|.bss|.text]`: marca l'inizio di una specifica sezione del programma. Le sezioni sono: + - `.data`: Variabili del programma + - `.rodata`: Variabili sola lettura del programma + - `[.bss](https://en.wikipedia.org/wiki/.bss)`: Variabili statiche non costanti non inizializzate a nessun valore. _A volte, vengono inizializzate a 0. Solo a volte. Dipende dall'OS._ + - `.text`: Il testo del programma, con le istruzioni in assembly. +- `.TIPOVARIABILE`: crea una variabile di quel tipo e la inizializza a qualcosa. + +# Link utili + +- [algo.ing.unimo.it](http://algo.ing.unimo.it/people/andrea/Didattica/Architetture/index.html) +- [wikichip.org](https://en.wikichip.org/wiki/risc-v) +- [en.wikiversity.org](https://en.wikiversity.org/wiki/Computer_architecture) +- [it.wikiversity.org](https://it.wikiversity.org/wiki/Materia:Architetture_degli_elaboratori) +- [stackoverflow.com](https://stackoverflow.com/questions/tagged/riscv) +- [softwareengineering.stackexchange.com](https://softwareengineering.stackexchange.com/questions/tagged/assembly) +- [rv8.io](https://rv8.io/isa.html) diff --git a/2 - Architettura dei calcolatori/1 - Appunti/09 - Memorie.md b/2 - Architettura dei calcolatori/1 - Appunti/09 - Memorie.md new file mode 100644 index 0000000..215ca12 --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/09 - Memorie.md @@ -0,0 +1,133 @@ +# Memorie + +La _memoria_ di un calcolatore solitamente è composta da molteplici strati, realizzati per velocizzare gli accessi. + +Ogni strato offre lettura più veloce, ma è più costoso da realizzare e ha consumi più alti. + +In ordine di velocità, sono: + +- Registers +- Cache L1 +- Cache L2 +- Cache L`X` +- Random Access Memory +- Solid State Drive +- Hard Disk Drive + +## Cache + +### Cache L1 + +Piccola, ma miss penalty bassa + +### Cache L2 + +Molto grande, ma miss penalty alta + +### Direct mapping + +Una cache _direct mapped_ associa a ogni indirizzo di RAM un **indice** uguale agli **N bit meno significativi** dell'indirizzo dell'inferiore, e un **tag** uguale agli **N bit più significativi**. + +Se durante un accesso _il tag di un blocco è diverso dal tag dell'indirizzo a cui vogliamo accedere_, si ha un cache miss. + +> Il dato di cui abbiamo bisogno è contenuto nell'indirizzo `0x0ABC` della RAM. +> Il suo indice sarà `0xBC`, e il suo tag sarà `0x0A`. +> Essendo la cache vuota all'inizio, viene immediatamente caricato. +> +> Voglio poi accedere all'indirizzo `0x0BBC` della RAM. +> Controlliamo il blocco con indice `0xBC`: il suo tag è `0x0A`, ma noi stiamo cercando `0x0B`! Si ha quindi un cache miss, e devo andare a prendere dalla memoria il dato che sto cercando e scriverlo sulla RAM. + +Inoltre, in ogni blocco di memoria della cache è presente **un bit di validità**, che rappresenta se il dato in cache è stato inizializzato o no: parte da `0` e viene impostato a `1` quando viene caricato dalla RAM un dato nel relativo blocco. + +> Un esempio potrebbe essere un processore con blocchi di memoria da 32 bit e indirizzi a 64 bit: la cache, contenente 1024 blocchi di memoria, avrà index a 12 bit, e di conseguenza il tag sarà grande 52 bit. + +#### Dimensione blocchi + +Fare blocchi grandi o fare blocchi piccoli ha significative differenze sulla velocità della cache: + +Avere blocchi più grandi significa che ci saranno meno blocchi in tutta la cache, quindi: + +```diff ++ Riduce il miss rate per il principio di località spaziale +- Avendo una quantità minore di blocchi, aumenta la possibilità di conflitto +- La miss penalty è più alta +# La miss penalty è compensabile con tecniche come early restart o critical-word-first +``` + +Con blocchi più piccoli, invece +```diff ++ Miss penalty minore ++ Più blocchi significa meno conflitti +- E' possibile che abbia bisogno di fare più di una richiesta alla cache, rallentandola significativamente +``` + +#### Scrittura tramite cache + +Quando scrivo in memoria un dato presente nella cache, l'informazione presente nella cache diventa errata. + +Si può risolvere questo problema con una politica di riscrittura: +- Write-through +- Write-back + +##### Write-through + +_Quando **viene scritto** su dato in cache_, aggiorna tutte le memorie che lo contengono. + +```diff ++ Non necessita di ulteriore memoria di cache ++ Caricare dati è veloce ++ Se si verifica un write miss non ha per forza bisogno di portare in cache il dato da scrivere +- I write richiedono molto più tempo, soprattutto se ripetuti +``` + + +##### Write-back + +Scrivi la modifica di dato solo nella cache, e _marca il blocco come **dirty**_. + +Quando un blocco dirty viene **sovrascritto**, aggiorna le memorie che lo contengono. + +```diff ++ I write richiedono poco tempo, anche se ripetuti +- Caricare dati dalla RAM è più lento +- Necessita di memoria da dedicare al dirty bit +- Devo fetchare obbligatoriamente i dati da sovrascrivere +``` + +##### Miglioramenti alle policy + +E' possibile utilizzare un **write buffer** invece che fare attendere il tempo di scrittura alla CPU. I dati saranno scritti successivamente, ma prima che questi vengano utilizzati. + +```diff ++ Il processore non ha bisogno di fermarsi per le write +- Il buffer potrebbe riempirsi, neganode i vantaggi +- Il buffer utilizza memoria che forse sarebbe stata più utile come cache +``` + +### Fully associative + +Ogni dato può essere messo in qualunque indirizzo della cache. + +Richiede che l'indirizzo di origine venga salvato assieme al dato, e tanti comparatori. + +### Set associative + +Divido tutti i blocchi di cache in vie. + +Ogni via può contenere `n` dati. + +Un set è l'insieme dei dati che hanno lo stesso index ma sono in vie diverse. + +Ogni dato può essere messo in qualunque indirizzo del set a cui appartiene. + +Identifico il numero di set di appartenenza facendo `indirizzo % numerodiset`. + +Una cache `1`-way Set Associative è una cache Direct Mapped, mentre una cache `numeroentries`-way Set Associative è una cache Fully Associative. + +Quando non c'è spazio in un set, rimpiazzo un dato secondo la politica **Least Recently Used**, rimuovendo il dato usato meno recentemente. Posso usare anche la politica **Random**, se ho un'alta associatività. + +#### Performance + +Più una cache è associativa, più il miss rate sarà ridotto, ma l'associatività richiede un maggior numero di comparatori e potenzialmente più ritardi nella restituzione del dato. + +Inoltre, la percentuale di miss non diminuisce linearmente con il numero di vie: dopo un certo numero di vie, i guadagni sono molto ridotti. diff --git a/2 - Architettura dei calcolatori/1 - Appunti/10 - Meltdown.md b/2 - Architettura dei calcolatori/1 - Appunti/10 - Meltdown.md new file mode 100644 index 0000000..e625eea --- /dev/null +++ b/2 - Architettura dei calcolatori/1 - Appunti/10 - Meltdown.md @@ -0,0 +1,21 @@ +# Meltdown + +Esempio circa comprensibile: + +```c++ +int meltdown() { + bool array[2] = {false, true}; + + if( qualcosa_che_restituisce_false() ) { + //Questo viene predetto + //Durante le predizioni non ci sono segfault! + int protetto = *(indirizzo_bersaglio); + //Se il primo bit di protetto è 1, verrà caricato "true" in cache, altrimenti, verrà caricato "false", ANCHE SE E' SOLO UNA PREDIZIONE! + //Dopo essere stato caricato, NON VIENE RIMOSSO, anche se la predizione è sbagliata. + int caricato = array[protetto & 1]; + } + + if( in_cache(true) ) cout << "Il primo bit del bersaglio è 1.\n"; + else cout << "Il primo bit del bersaglio è 0.\n"; +} +``` \ No newline at end of file