



















Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Argomenti: 1- introduzione: tipologie di dispositivi, componenti dei calcolatori 2- aritmetica dei calcolatori 3- reti logiche 4- introduzione all'Assembly 5- Assembly RISC-V 6- Intel x86 7- ARM 8- toolchain 9- CPU 10- pipeline 11- gerarchia delle memorie 12- sistemi di I/O
Tipologia: Appunti
1 / 27
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!




















I calcolatori sono dei dispositivi utilizzati per l’elaborazione di dati e segnali. Sono classificabili in 3 tipologie:
personali → (personal computer) hanno costi ridotti e offrono delle prestazioni discrete server → macchine in grado di svolgere e gestire molto lavoro embedded → sono sviluppate per svolgere solo determinate task (calcolatori “dedicati”), hanno come caratteristica l’alta durabilità
Ogni calcolatore è composto dai seguenti componenti:
processore → incaricato di effettuare le operazioni. Composto da due parti: unità operativa (ALU/datapath) e da un’unità di controllo periferiche → sistemi di I/O unità di memoria : volatile (DRAM, SRAM) → memoria principale, contiene dati che servono durante l’esecuzione di un programma e che verranno persi allo spegnimento non volatile (CD/DVD/SSD/dischi rigidi) → memoria di massa, memorizza dati e programmi tra esecuzioni diverse
In un calcolatore si possono distinguere 2 tipi di prestazioni (tempo medio di risposta, ovvero il tempo che passa tra l’avvio e la terminazione di una task, e throughput, ovvero il numero di task completate nell’unità di tempo) che dipendono dagli algoritmi, linguaggi di programmazione, sistema operativo e dai componenti elencati sopra.
Ciò che misura le prestazioni è il clock: vengono utilizzati i CPI (cicli di clock per istruzione) come unità di misura.
CPI)/frequenza clock
Dati che i calcolatori non sono altro che un’insieme di circuiti che servono a svolgere operazioni in maniera efficiente, le sequenze di 0 e 1 che vediamo in realtà non sono altro che la rappresentazione/codifica della corrente che gli passa attraverso.
Ogni singola cifra si chiama bit (8 bit = 1 byte).
la codifica in questo caso avviene secondo il codice binario, ma non è l’unico esistente (es. decimale, esadecimale…)
Per rappresentare i numeri interi devo ricorrere a delle tecniche specifiche:
modulo e segno → si usano k-1 bit per rappresentare il modulo e 1 bit (in testa) per il segno del numero complemento a 1 → i numeri negativi vengono modificati rendendo gli 0 degli 1e viceversa (i numeri positivi restano invariati) complemento a 2 → i numeri negativi vengono sottoposti al complemento a 1 e successivamente gli viene sommato 1 (i numeri positivi restano invariati)
Con queste tecniche si può verificare un overflow , ovvero l’evento in cui il numero eccede lo spazio che gli è stato riservato in memoria, e lo posso evitare seguendo la matematica dell’orologio. Per la matematica dell’orologio, come concetto di base, considero solo n numeri interi che vanno da 0 a n-1 e appena arrivo al più grande riparto dal primo.
I numeri reali, invece, non sono sempre rappresentabili tramite un numero finito di bit e in questo caso ricorro a 2 tecniche:
rappresentazione in virgola fissa → prendo un numero di cifre e decido quante dedicarne alla parte intera e quante alla parte decimale rappresentazione in virgola mobile → parte dall’idea che ogni numero è rappresentabile come X = M * B^E (con M=mantissa e E=esponente).
Anche per codificare un testo viene usata una codifica in binario, ad esempio per rappresentare l’alfabeto anglosassone bastano 7 bit (2^7=128 caratteri) tramite
Le funzioni logiche possono essere implementate in maniere più o meno efficiente e a stabilirlo è il costo (somma del numero di porte e del numero di ingressi della rete).
Nelle reti logiche sequenziali invece, introduco un concetto di memoria che si ottiene tramite un ri-direzionamento dell’uscita al atri nodi. Ad esempio il LATCH è un componente che utilizza la logica sequenziale e consiste nella realizzazione di un circuito formato da 2 porte NOR e da uno schema di temporizzazione della tavola di verità.
Gli elementi di memoria sono sempre regolati dai cicli di clock per evitare problematiche relative all’indecidibiltà.
L’assembly è un linguaggio di programmazione che funge da collante tra i linguaggi di alto livello (usati dai programmatori) e il linguaggio macchina ( sequenze di 0 e 1 che servono alla macchina per decifrare le istruzioni).
I programmi Assembly consistono in dei file ASCII contenenti una descrizione testuale delle istruzioni, vengono compilati da un assembler che traduce il linguaggio in dei set di istruzioni (ISA) riconosciute dalla CPU.
La struttura di un programma Assembly varia da un’architettura all’altra perché ne è strettamente legato. Ad esempio:
architettura RISC → semplifica l’implementazione della CPU architettura CISC → semplifica la scrittura del programma
In un programma assembly ho 3 tipologie di istruzioni: aritmetico-logiche, di movimento dati e di salto o controllo di flusso.
L’architettura RISC-V supporta le operazioni aritmetiche gestite fra i registri (e non con la memoria) perché ha come obiettivo il mantenimento della semplicità.
La sintassi delle istruzioni prevede sempre 3 operandi , le operazioni più complesse devono essere scomposte, e il destinatario dell’operazione è sempre il primo operando.
I registri utilizzati sono 32 (a 64 bit) e sono molto veloci da raggiungere a differenza della memoria però, dato che tante volte ho più operandi che registri disponibili, è bene usare la memoria con le operazioni di
Un‘istruzione RISC-V occupa 32 bit (mantiene l’efficienza):
funz7 e funz3 → codici operativi aggiuntivi rs1 e rs2 → 1° e 2° operando sorgente rd → operando destinazione codop → codice operativo istruzione
Le operazioni possono essere svolte fra soli registri ( R ) oppure anche con degli operandi immediati ( I ) che consistono in valori numerici e servono per operazioni che hanno bisogno dell’utilizzo di costanti. Inoltre se i registri non bastano su può utilizzare la pila puntata dal registro x2 dove le variabili locali sono individuate tramite offset a partire da un puntatore (alcuni programmi utilizzano x8 per indicare il puntatore alla prima parola).
Il RISC-V offre anche delle operazioni di tipo logico che permettono di operare su porzioni di parole o bit singoli invece che sull’intera parola. [le operazioni logiche del RISC-V sono gli shift e le operazioni che fanno uso dell’algebra di Boole]
I salti invece sono una tipologie di operazioni che servono per svolgere operazioni di if-else e cicli e possono essere di 2 tipi: condizionati e non-condizionati.
Le sequenze di istruzioni comprese tra 2 salti condizionati si chiamano blocchi di base (stralci di codice non contenenti né salti né labels di destinazione) e l’individuazione di questi blocchi è una delle prime fasi della compilazione.
Campi delle istruzioni RISC-V
scala → è una costante (1, 2, 4, 8 → dimensione array, se è 1 non serve scriverla)
[se una delle componenti elencate qua sopra è nulla posso ometterla nella formula]
Una limitazione importante è che non posso avere entrambi gli operandi in memoria ma ho determinate combinazioni valide:
Forme di indirizzamento:
Le istruzioni Intel hanno una sintassi generale
I valori immediati vanno indicati anteponendo $ al numero mentre gli indirizzi diretti sono semplici numeri.
load effective address → istruzione che serve per calcolare indirizzi senza però accedervi, in sostanza copia l’indirizzo sorgente nel registro destinazione
Se invece ho bisogno di accedere a singoli byte invece che a parole intere non devo per forza usare variazioni di funzioni come nel RISC-V, ma invece Intel fornisce dei (sotto)registri a 8 bit e la funzione mov è capace di operare su dati di diversa dimensione.
L’architettura ARM viene principalmente utilizzata nei sistemi embedded e nei dispositivi mobili, è un sistema meno evoluto di Intel perché fa da ponte fra RISC e CISC.
Prevede 16 registri general purpose (a 32 bit) [in realtà r15 non è general purpose ma contiene il program counter e i flag]
Lista dei flag :
Le modalità di indirizzamento dell’ARM prevedono principalmente istruzioni a 3 operandi ma senza operandi in memoria [le uniche operazioni che accedono alla memoria sono
base + offset (immediato) base + indice (eventualmente scalato)
a. dispone in memoria i vari segmenti b. assegna l’indirizzo assoluto ad ogni simbolo c. corregge le varie istruzioni con gli indirizzi calcolati
Se uso GCC (GNU compiler collection) come compilatore, posso compilare molti linguaggi di alto livello e ho a disposizione delle varianti di invocazione:
gcc -S → dato un file .c genera un file Assembly .s. Sintassi: gcc -S
I file oggetto sono composti da più segmenti:
header → specifica la dimensione e la posizione degli altri segmenti del file segmento di testo → contiene il codice in linguaggio macchina segmento dati → contiene tutti i dati per la durata del programma tabella dei simboli → associa i simboli a degli indirizzi ed enumera quelli non definiti tabella di rilocazione → enumera le istruzioni che fanno riferimento a istruzioni e dati che fanno riferimento a indirizzi assoluti nel momento in cui il programma viene caricato in memoria …….
Le librerie sono collezioni di file .o e possono essere sia statiche ( .a , servono solo durante il linking ma appesantiscono il programma) che dinamiche ( .so , non fanno aumentare le dimensioni dell’eseguibile).
Per eseguire qualunque operazione ci sono delle operazioni comuni da fare:
Mentre i passi successivi sono specifici per ogni istruzione. La cosa fondamentale da dire è che TUTTE le istruzioni utilizzano la ALU in qualche maniera dopo aver letto gli operandi.
Ad esempio:
add ha bisogno di 2 blocchi funzionali in più: il banco registri che fornisce in output i registri specificati e, se abilitato, scrive nel registro specificato il dato in
[il tempo di clock deve essere scelto per fare in modo di permettere ai dati di attraversare la rete].
Tendenzialmente la metodologia di memorizzazione sensibile al clock è abbastanza precisa in modo tale da non rendere impredicibile l’evoluzione del sistema.
Siccome abbiamo il requisito di eseguire ogni operazione in 1 ciclo di clock, non possiamo utilizzare un’unità funzionale più di una volta per ciclo e occorre condividere il più possibile le varie unità.
Nello specifico la ALU viene utilizzata per effettuare operazioni logico-aritmetiche (tipo R), calcolare indirizzi di memoria ed eseguire la sottrazione per beq. Per ognuna di queste operazioni abbiamo una diversa configurazione degli input di controllo. Per generare i bit di controllo si usa una piccola unità di controllo che riceve in ingresso i campi funz3 e funz7 dell’istruzione e due bit detti ALUop.
L’ unità di controllo genera tutti i segnali di controllo (inclusi ALUop) e consiste in una rete combinatoria che segue una tabella per generare l’output corretto.
add x5, x6, x
In ogni caso, ora a dettare il clock sono le istruzioni più lente e non viene più fatto seguire il singolo ciclo di clock dal processore.
La pipeline è un meccanismo per parallelizzare l’esecuzione di un programma.
Le fasi di esecuzione di un’istruzione sono le seguenti:
Queste fasi vanno mantenute nella pipeline MA vanno eseguite in parallelo.
Ogni istruzione è composta da più stadi, ogni stadio ha bisogno di un tempo diverso per portare a termine la sua esecuzione (l’istruzione più lenta e la load, quindi detta il clock).
gli accessi in memoria occupano un solo stadio della pipeline
In condizioni normali la pipeline permette di eseguire un’istruzione per ciclo di clock ma a volte non è possibile se si verificano degli hazard.
hazard strutturali → condizione per la quale l’architettura dell’elaboratore rendo impossibile l’esecuzione di alcune sequenze di istruzioni in pipeline hazard sui dati → condizione che si verifica quando la pipeline deve essere messa in stallo per ottenere informazioni sugli stadi precedenti [evitabile tramite l’inversione di alcune istruzioni] Per evitare questo problema posso ricorrere all’ operand forwarding → utilizzato per rendere il risultato disponibile bypassando l’operazione di write back, funziona solo se lo stadio a cui il dato viene propagato è successivo nel tempo allo stadio dal quale viene prelevato. ES - add e sub in pipeline
hazard sul controllo → riguarda i salti condizionati, perché prima di eseguire il salto devo aspettare la conclusione di alcuni passaggi per poi controllare l’avvenire di una condizione. In alcuni casi la cosa è risolvibile con dei circuiti in grado di prevedere i salti (branch prediction)
Ogni elaboratore prevede una gestione della memoria “personalizzata”. La memoria serve a contenere dati ed è suddivisa in due parti:
memoria principale ( cache ) → è di tipo volatile ed è limitata dallo spazio di indirizzamento del processore. Le sue informazioni sono accessibili al processore in qualsiasi momento. memoria periferica → è di tipo permanente (mantiene il contenuto anche senza alimentazione) e ha uno spazio di indirizzamento non limitato dal processore. Le sue informazioni, per essere accessibili al processore, devono prima essere trasferite dal sistema operativo nella memoria principale.
Alcune definizioni:
tempo di accesso → tempo richiesto per 1 operazione di lettura/scrittura nella memoria tempo di ciclo → tempo che intercorre tra l’inizio di 2 operazioni consecutive tra locazioni diverse accesso casuale → tipico delle memorie a semiconduttori, non c’è relazione o ordine nella memorizzazione dei dati accesso sequenziale → tipico dei dischi e dei nastri, il tempo di accesso dipende dalla posizione e l’accesso in memoria è ordinato RAM → memoria a semiconduttori accessibile sia in scrittura che in lettura ROM → memoria a semiconduttori accessibile solo in lettura
Le memorie RAM memorizzano singoli bit , organizzati in byte o word. Fissata una capacità, la memoria può essere organizzata in modi diversi in base al parallelismo [es. capacità=512Kbit → 512K x1, 128K x4, 64K x8]. L’organizzazione influenza il numero di pin di I/O del circuito integrato della memoria.
Le memorie SRAM sono memorie in cui i bit possono essere tenuti indefinitamente (purché non manchi l’alimentazione), sono molto veloci e consumano poca corrente [hanno un costo alto].
→ per risolvere il problema possiamo dare al processore l’illusione di avere uno spazio di memoria molto grande e ad alta velocità tenendo nei “registri” solo quello che serve al momento e, quando ho riempito i “registri”, tutto ciò che non è utile al momento può essere spedito nella memoria.
Questo è possibile grazie a 2 principi (per prevedere l’utilità di alcuni dati rispetto ad altri):
principio di località spaziale → quando si fa uso di una locazione, nei passi successivi si farà riferimento a locazioni vicine principio di località temporale → quando si fa uso di una locazione, la si riutilizzerà presto con alta probabilità
Struttura della gerarchia delle memorie:
Le più veloci e piccole vengono poste vicino al processore, le più lente e grandi lontano dal processore.
Alcune definizioni:
blocco → unità minima di informazione che può essere presente (o assente) in ciascun livello hit rate → (frequenza di successo) frazione di accessi in cui trovo i dati nel livello superiore miss rate → (1-hit rate) frazione degli accessi in cui non trovo i dati nel livello superiore tempo di hit → tempo che occorre per accedere al dato nel livello superiore penalità di miss → tempo che occorre per accedere al dato che non viene trovato nel livello superiore [è molto maggiore del tempo di hit].
La cache consiste in un posto nascosto (non accessibile dal programmatore) dove vengono riposte le cose. Se la cache è a mappatura diretta , ad ogni indirizzo della memoria corrisponde una locazione della cache.
Su uno stesso blocco di cache posso mappare più parole , quindi, per capire dove si trova l’indirizzo che ci serve, si ricorre al campo tag che contiene un’informazione sufficiente a risalire al blocco correntemente mappato in memoria. Inoltre abbiamo dei bit di validità che ci dicono se quello che memorizziamo in un blocco di cache è valido o meno.
Calcolo del tag: ho un indirizzo su 64 bit una cache a mappatura diretta la dimensione della cache è di 2^n blocchi (n bit usati per l’indice) la dimensione del blocco di cache è 2^m parole (ossia 2*(m+2) byte, m bit usati per individuare una parola nel blocco e due bit per individuare un byte nella parola) → allora la dimensione del tag è 64-(n+m+2).