Architettura Orale Flashcards
Quali sono i concetti fondamentali di un processore MIPS?
Il processore MIPS utilizza un set di istruzioni RISC (istruzioni semplici ad elevate prestazioni).
Tutte le istruzioni hanno 3 operandi.
La dimensione dei registri è di 32 bit.
Un gruppo di 32 bit è detto word.
L’assembler deve contenere le istruzioni per il trasferimento dei dati dalla memoria ai registri.
“Load” sposta un dato dalla memoria al registro, mentre “Store” trasferisce i dati dal registro alla memoria.
Le parole devono iniziare sempre a indirizzi multipli di 4 e questo requisito è detto vincolo di allineamento.
MIPS utilizza il posizionamento Big Endian, ovvero dal byte più significativo (a sx) a quello meno significativo, per specificare l’indirizzo della parola.
Esistono 3 formati di istruzioni:
R = per le operazioni aritmetico logiche; op(6 bit), rs(5 bit), rt(5 bit), rd(5 bit), shamt(5 bit), funct (6 bit);
I = per le operazioni immediate o di trasferimento; 32 bit: op(6 bit), rs(5 bit), rt(5 bit) e costante (16 bit);
J = per i salti, condizionati o meno; op e indirizzo di salto;
Ogni operazione aritmetica può eseguire una sola operazione e deve contenere esattamente 3 variabili.
Le istruzioni aritmetiche possono leggere nello stesso ciclo di clock due registri su cui eseguire le operazioni e scrivere i risultati.
Cosa differenzia ARM e MIPS?
Entrambi fanno parte della famiglia RISC.
MIPS ha 32 registri a 32 bit, mentre ARM ha 16 registri a 32 bit e non riserva un registro per il valore 0. Nella maggior parte delle architetture dei processori, è comune avere un registro specifico che è sempre impostato a zero. Questo registro è spesso utilizzato per operazioni di inizializzazione, calcoli e altre operazioni in cui è necessario avere un valore costante di zero. Tuttavia, nell’architettura ARM, non c’è un registro dedicato a questo scopo. In ARM puoi comunque settare un registro con il valore zero laddove ti serva.
ARM utilizza i condition code (cosa che un RISC non dovrebbe avere), ovvero ogni volta che viene fatto un confronto, vengono impostati dei bit che indicano se il risultato del confronto è uguale, maggiore o minore di zero e il risultato è codificato in una word di stato (registro interno al processore che si occupa delle informazioni delle operazioni interne).
Nei registri del processore MIPS, invece, non vengono salvati i risultati dei confronti.
L’utilizzo dei condition code non è buono in quanto se un processore è in grado di eseguire 2 o 3 operazioni contemporaneamente, quindi dispone di 2 o 3 ALU, è possibile effettuare più somme e ciascuna di essa tenterà di modificare i condition code.
Quindi un unico set di condition code non è sufficiente.
E’ meglio salvare, di volta in volta, il risultato in un registro separato e, quindi, eseguire queste operazioni in parallelo.
Una caratteristica eccellente di ARM è che ogni istruzione può essere condizionale, ovvero l’esecuzione dipende dal risultato di un confronto.
In questo modo si evitano i branch e quindi si impedisce al processore di fare delle previsioni.
MIPS ha 3 modalità di indirizzamento(tecnica utilizzata dai processori per accedere ai dati in memoria o ai registri), mentre ARM ne ha ben 9.
Uno di questi 9 consente di spostare il contenuto di un registro di un numero arbitrario di posizioni, quindi sommarlo al contenuto di un altro registro per formare un indirizzo e infine aggiornare il contenuto di un terzo registro con tale indirizzo.
Differenza tra processore MIPS e 0x86?
Nell’x86 le istruzioni dispongono di un solo operando che funge sia da sorgente che da destinazione, mentre MIPS e ARM consentono di averne due separati.
e le architetture dei processori che consentono due registri separati per gli operandi tendono ad essere più flessibili e meno vincolanti nell’utilizzo dei registri rispetto a quelle con un solo operando.
Un’ulteriore differenza è come le diverse architetture dei processori trattano gli operandi che sono memorizzati in memoria anzichè nei registri. Per capire meglio questa differenza, vediamo in MIPS e X86 le differenze:
- Architetture con operandi solo in registri MIPS:
Nelle architetture come MIPS, le istruzioni operano tipicamente su operandi che risiedono nei registri. Ciò significa che le istruzioni accedono direttamente ai dati memorizzati nei registri del processore per eseguire le operazioni.
- Architetture con operandi in memoria (x86 e altre):
In alcune altre architetture, come l’x86, è possibile utilizzare operandi direttamente dalla memoria principale, oltre che dai registri. Ciò significa che un’istruzione può accedere direttamente ai dati presenti in memoria e utilizzarli come operandi.
L’implementazione dell’x86 è più difficile, ma l’ampiezza del mercato delle loro CPU permette di avere più risorse per risolvere la loro complessità.
I componenti architetturali dell’x86 utilizzati più frequentemente sono più facili da implementare.
Quale è l’istruzione più lunga nel MIPS?
L’operazione più lunga nel MIPS è la “load” (caricamento di un dato dalla memoria in un registro) in quanto utilizza 5 unità funzionali in sequenza:
- Memoria istruzioni: il primo passo coinvolge l’accesso alla memoria delle istruzioni, dove viene prelevata l’istruzione di “load” dal programma in esecuzione;
- Register File: l’istruzione di “load” può contenere l’indirizzo di un registro all’interno del register file, indicando in quale registro il dato caricato dalla memoria dovrebbe essere memorizzato;
- ALU: In alcuni casi, potrebbe essere necessario calcolare l’indirizzo effettivo nella memoria a cui accedere. L’ALU potrebbe essere coinvolta nel calcolo di questo indirizzo, ad esempio aggiungendo un offset ad un registro base;
- Memoria dati: una volta calcolato l’indirizzo effettivo, l’operazione di “load” coinvolge l’accesso alla memoria dei dati. Il dato viene prelevato dalla memoria in base all’indirizzo calcolato;
- Register File: infine, il dato caricato dalla memoria viene scritto nel registro specificato nell’istruzione di “load”;
Perchè i processori utilizzano la rappresentazione dei numeri in complemento a 2?
La rappresentazione in complemento a 2 è utilizzata per rappresentare numeri interi in informatica e sistemi digitali.
Un bit viene utilizzato per il segno del numero (0 positivo, 1 negativo) i restanti bit, invece, rappresentano la magnitudo del numero.
Questa rappresentazione ha alcuni vantaggi rispetto ad altre:
- Semplicità delle operazioni aritmetiche =
L’addizione e la sottrazione possono essere svolte utilizzando soltanto operazioni di bit a livello hardware il che le rende veloci ed efficienti;
- Risparmio di spazio =
Permette di rappresentare sia numeri positivi che negativi con lo stesso numero di bit, senza occupare spazio extra per il segno; - Compatibilità con la logica binaria =
si adatta perfettamente alla logica binaria utilizzata nei circuiti elettronici dei processori.
Per ottenere la rappresentazione in complemento a 2 di un numero negativo si prende innanzitutto il numero in valore assoluto (bit di segno = 0), successivamente si prende il complemento ad 1 di ogni bit e si aggiunge 1 al risultato.
Nel caso di numeri positivi, il complemento a 2 è uguale alla rappresentazione segno-grandezza.
Rappresentazione normalizzata (numeri in virgola mobile)
Un numero in notazione scientifica è rappresentato da una cifra significativa (mantissa) e da una potenza di 10.
Se il numero non ha zeri significativi davanti la virgola, allora è normalizzato.
Nel caso di numeri binari, ovvero la cui base è 2, si parlerà di virgola binaria.
Per scrivere un numero binario normalizzato, occorre avere una base da aumentare o decrementare dello stesso numero di bit di cui il numero deve essere scalato, arrivando ad avere una sola cifra non nulla dopo la virgola.
L’aritmetica dei calcolatori che supporta tali numeri è detta aritmetica in virgola mobile poichè rappresenta numeri la cui virgola binaria non è fissa.
Perchè l’esponente della rappresentazione a virgola mobile non è mai rappresentato in complemento a 2?
L’esponente della rappresentazione a virgola mobile non è rappresentato in complemento a 2, ma tramite un formato di codifica chiamato “formato di codifica dell’esponente normalizzato”.
La scelta di rappresentare l’esponente dipende dall’esigenza e dalla precisione richiesta per le operazioni in virgola mobile.
L’esponente viene rappresentato come un intero codificato utilizzando un insieme di bit specifico.
La codifica dell’esponente normalizzato ha un valore di bias, cioè un numero che viene sottratto dall’esponente effettivo prima della codifica.
Questo valore di bias è scelto in modo da garantire sia la rappresentazione di numeri positivi che negativi.
La codifica dell’esponente normalizzato consente di effettuare rapidamente le operazioni di confronto e trasferimento di dati tra unità di elaborazione e di memoria utilizzando solo operazioni di bit a livello hardware.
Come capire se un numero in virgola mobile è più grande di un altro??
Basta confrontare le loro mantisse ed esponenti.
Il numero con la mantissa più grande è il maggiore.
Se le mantisse sono uguali, si confronterà l’esponente.
5) Come vengono scritti gli esponenti nella notazione in virgola mobile?
Gli esponenti in virgola mobile si rappresentano come potenza di 2 su 8 bit se a singola precisione, ma questo potrebbe causare problemi di overflow o underflow avendo un esponente relativamente limitato.
Nella notazione a virgola mobile a doppia precisione la rappresentazione è una potenza di 2 su 11 bit.
Questo fornisce un intervallo più ampio per l’esponente consentendo di evitare problemi di overflow e underflow.
Perchè l’uso della polarizzazione per l’esponente?
Lo Standard IEEE 754 prevede una rappresentazione dell’esponente in eccesso 127 per la singola precisione e 1023 per la doppia precisione.
Questa rappresentazione si applica attravero la “polarizzazione dell’esponente”.
La polarizzazione di 127 per la singola precisione significa che il valore reale dell’esponente viene traslato aggiungendo 127.
Viene usato 127 per la singola precisione perchè avendo 8 bit e volendo rappresentare sia i numeri negativi che positivi, il bit più significativo è utilizzato per indicare il segno.
Quindi l’intervallo di valori sarà compreso tra -126 e 127.
Se non si considera il segno, l’intervallo di valori sarà compreso tra 0 e 256.
Attraverso la polarizzazione dell’esponente si può fare in modo che il numero più grande sia pari a 11111111 e il più piccolo pari a 00000000.
Nel momento in cui si va a confrontare i due esponenti non occorre controllare il bit più significativo, ma semplicemente considerarli come due interi senza segno.
Es.
Polarizzazione a singola precisione (127), rappresentazione di -125:
-125 + 127 = 2 = 00000010
Polarizzazione a singola precisione (127), rappresentazione di 0:
0 + 127 = 127 = 01111111
Cosa è la rappresentazione per eccessi? Perchè si usa questa e non il complemento a 2?
La differenza principale tra il complemento a 2 e la rappresentazione per eccesso è che il bit di segno viene invertito.
I numeri negativi sono ottenuti sommando il valore del numero stesso a un valore costante K.
Mappa i numeri negativi nell’intervallo da 0 a -1, mentre il complemento a 2 li mappa da -2^(l-1) a -1
Es.
Nel caso l=8, nella rappresentazione in complemento a 2 i numeri sono mappati nell’intervallo di valori -126 e 127, mentre nella rappresentazione per eccesso sono mappati da 0 e 255.
Perchè il processore utilizza la rappresentazione a 2 e non il “modulo e segno”??
Nel contesto della rappresentazione binaria è necessario utilizzare una convenzione per rappresentare il segno dei numeri.
In genere si utilizza il bit più significativo per indicare il segno.
Questo approccio è noto come modulo e segno.
Tuttavia presenta un problema quando si effettuano operazioni aritmetiche come l’addizione.
I numeri negativi, infatti, devono essere trattati con opportune operazioni speciali per essere sommati correttamente.
L’anteposizione di un semplice bit 1 per rappresentare un numero negativo non basta per compiere una addizione diretta.
Nel complemento a due, la rappresentazione dei numeri negativi è ben diversa dall’anteporre un semplice bit 1.
Questo permette al processore di sommare direttamente i numeri.
Cos’è la pipeline?
La pipeline sfrutta il principio del parallelismo tra le istruzioni.
Consiste nel suddividere l’esecuzione di un’istruzione in fasi, ciascuno dei quali esegue una parte specifica dell’istruzione.
Gli stati sono 5:
1. Fetch dell’istruzione: l’unità di controllo del processore preleva l’istruzione successiva dalla memoria istruzioni;
2. Lettura dei registri: qui vengono letti i registri coinvolti nell’istruzione, ad esempio i registri sorgente necessari per eseguire un’operazione;
3. Esecuzione di un’operazione: l’operazione specificata dall’istruzione viene effettivamente eseguita;
4. Accesso ad un operando della memoria: viene calcolato l’indirizzo effettivo di memoria per recuperare o memorizzare dati dalla/nella memoria principale;
5. Scrittura del risultato in un registro: il risultato dell’operazione viene scritto nel registro appropriato;
Esistono 2 modi per aumentare il parallelismo:
- Aumentare la profondità delle pipeline in modo da sovrapporre più istruzioni nel tempo; Aumentando la profondità della pipeline, si aumenta il numero di fasi in cui l’istruzione viene suddivisa. Questa consente di suddividere il percorso di esecuzione in un’unità più piccole e svolgere più istruzioni contemporaneamente, ognuna in una fase diversa.
- Replicare i componenti interni del calcolatore per eseguire più istruzioni in ogni stadio della pipeline, detta anche esecuzione parallela (multiple-issue); Questo può essere fatto duplicando o replicando le unità funzionali interne come ALU ecc.
Esistono anche 2 metodi per realizzare processi a esecuzione parallela:
- “Parallelizzazione statica”: le decisioni vengono prese durante la fase di compilazione, ovvero prima che il programma venga effettivamente eseguito. Il compilatore, quindi, ristruttura il codice in modo da suddividerlo in porzioni che possono essere eseguite in parallelo.
- “Parallelizzazione dinamica” che avviene durante l’esecuzione, cioè le decisioni sono prese in base alle condizioni attuali;
La “speculazione” è un metodo per scoprire e sfruttare al massimo il parallelismo.
Consiste nello scoprire le caratteristiche di una istruzione per avviare l’esecuzione di altre istruzioni che dipendono da quella istruzione.
Poichè la predizione può essere sbagliata occorre un meccanismo per verificarne la correttezza e correggere gli errori.
La speculazione può essere fatta dal compilatore riordinando le istruzioni o dal processore stesso durante l’esecuzione.
La speculazione può essere:
- “Software” e quindi il compilatore controlla la corretta speculazione e fornisce una procedura di riparazione in caso di errore;
- “Hardware” mantiene i risultati di speculazione in un buffer e li scrive nei registri o nella memoria solo se la speculazione è corretta.
In caso contrario, l’Hardware scarta i risultati.
MIPS è stata progettata per sfruttare al meglio le potenzialità della pipeline, anche se la si trova in tutti i calcolatori.
Che effetto ha l’istruzione più lunga sulla pipeline?
Il ciclo di clock è la misura che quantifica il tempo all’interno di un processore.
Rappresenta il tempo impiegato per eseguire un’operazione elementare nel processore.
Il ciclo di clock, quindi, deve essere progettato affinchè dia abbastanza tempo all’istruzione più lunga del set di istruzioni del processore di essere eseguita.
Cosa sono gli Hazard?
Gli Hazard si riferiscono alle situazioni in cui l’esecuzione corretta delle istruzioni può essere ritardata a causa di dipendenze tra istruzioni o risorse limitate, che possono verificarsi quando più istruzioni vengono eseguite in parallelo attraverso diverse fasi della pipeline. Gli hazard possono causare ritardi nell’esecuzione delle istruzioni e possono impattare le prestazioni complessive del processore. Ci sono tre tipi principali di hazard:
- Hazard Strutturali =
Questo tipo di hazard si verifica quando le risorse necessarie per eseguire un’istruzione non sono disponibili nel ciclo di clock successivo. Ad esempio, potrebbe esserci una sola ALU nel processore, e se due istruzioni richiedono l’uso dell’ALU nello stesso ciclo di clock, si verifica un hazard strutturale. Questo tipo di hazard richiede la gestione delle risorse in modo efficace. - Hazard sui dati =
Questo tipo di hazard si verifica quando un’istruzione condizionale cambia il flusso di controllo, ad esempio una branch instruction (istruzione di salto condizionale). Poiché l’istruzione di salto dipende da condizioni che potrebbero non essere note fino a quando l’istruzione non è arrivata alla fine della pipeline, potrebbe essere necessario ritardare l’esecuzione di istruzioni successive fino a quando il risultato del branch non è disponibile. Questo può causare “stalli” nella pipeline. - Hazard di controllo o sui salti condizionati =
Si verifica quando è necessario prendere una decisione in merito al risultato dell’esecuzione di un’istruzione, ma altre istruzioni sono già state avviate all’esecuzione.
Ad esempio quando potrebbe esserci il bisogno di prendere un salto basato su un confronto, ma le istruzioni successive sono già state eseguite e quindi la decisione potrebbe essere presa in base a dati non aggiornati.
Che cos’è la predizione del salto (branch prediction)?
La predizione del salto condizionato serve ad anticipare se un’istruzione di salto sarà eseguita oppure no. Quando una istruzione di salto condizionale è raggiunta durante l’esecuzione del programma, il processore non sa immediatamente quale sarà la direzione del flusso di esecuzione successivo. Pertanto, la pipeline potrebbe subire uno “stallo” mentre si attende il risultato del branch. La predizione del salto cerca di prevedere se il branch sarà “predetto come vero” (taken) o “predetto come falso” (not taken) e agisce di conseguenza.
Ovviamente con pipeline più lunghe il costo della predizione aumenta.
Una strategia comune per la “predizione dinamica dei salti” è quella di basarsi sulla storia, ovvero controllare se l’ultima volta che un’istruzione di salto è stata eseguita se il salto è avvenuto effettivamente.
Se è stato eseguito, vengono caricate le istruzioni a partire dall’indirizzo di destinazione del salto.
Tutte queste informazioni vengono salvate in un buffer di predizione dei salti che altro non è che una piccola memoria indicizzata contenente bit che indicano se il salto è stato eseguito o meno nell’ultima esecuzione.
Ovviamente la predizione non è sicura al 100%.
Se infatti la predizione è errata, occorre eliminare le istruzioni caricate erroneamente, inveritire il bit di predizione e riprendere l’esecuzione dall’ultima istruzione corretta.
Questo processo di correzione è detto “flushing” della pipeline.