Architetture dei processori/Unità di decodifica: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Diablo (discussione | contributi)
mNessun oggetto della modifica
Hellisp (discussione | contributi)
mNessun oggetto della modifica
Riga 9:
Molti microprocessori moderni sono progettati per eseguire set di istruzioni molto complessi, sviluppati per processori CISC. Questo set di istruzioni renderebbe difficile l'esecuzione di più istruzioni contemporaneamente. Quindi molti microprocessori traducono le istruzioni complesse in sequenze di istruzioni più semplici da eseguire rendendo il set di istruzioni simile a quello di un'architettura RISC. Anche questo lavoro viene svolto dall'unità di decodifica.
 
Nei microprocessori superscalari l'unità di decodifica diventa una componente critica del processore dato che l'unità si occupa di riceve le unità e di organizzarle in modo da ridurre al minimo possibile gli stalli delle pipeline. Questo viene ottenuto eseguendo le istruzioni fuori ordine, cioèridenominando l'unitài diregistri, decodificaprevedendo riconoscele qualicondizioni istruzionidi nonsalto sonoe vincolate da altre istruzioneprecaricando e provvede a anticiparle o ritardarle rispetto al normale flusso del programma al fine di massimizzare l'utilizzo delle pipeline. dati
 
== Esecuzione fuori ordine ==
Le istruzioni possono essere vincolare in vari modo. Per esempio un'istruzione può essere vincolata dal fatto che necessita del risultato dell'istruzione precedente oppure due istruzioni possono fare uso della stesso registro. Oppure un'istruzione può modificare il flusso del programma (istruzioni di salto) e quindi dal suo risultato dipende il flusso futuro del programma. Questi problemi vengono affrontati in modo diverso dall'unità di decodifica.
L'esecuzione fuori ordine indica la capacità di molti processori di eseguire le singole istruzioni senza rispettare necessariamente l'ordine imposto dal programmatore. Il processore in sostanza analizza il codice che dovrà eseguire, individua le istruzioni che non sono vincolate da altre istruzioni e le esegue in parallelo con il codice principale. La rilevazione delle dipendenza tra le varie istruzioni è un compito complesso dato che le istruzioni possono essere vincolare in vari modo. Per esempio se ho un'istruzione che esegue una somma tra due registri e poi il risultato della somma viene utilizzato come operando per una divisione va da se che la divisione e la somma non possono essere eseguite in parallelo senza generale conflitti. Il problema può essere risolto replicando delle unità funzionali (vedi sezione sulla pipeline) oppure eseguendo in parallelo all'istruzione di somma un'istruzione che non ha dipendenze e rimandando la divisione. In generali queste unità funzionali decodificano le istruzioni e pongono le istruzioni che non hanno vincoli (o che hanno vincoli che sono stati risolti) entro un buffer che alimenta le pipeline. Dopo le pipeline vi è un'unita che utilizzando delle etichette collegate alle istruzioni ricostruisce l'ordine cronologico delle istruzioni facendo uscire dal processore i risultati delle istruzioni con l'ordine cronologico impostato dal programma. Questa unità inoltre provvede ad eliminare le eventuali operazioni eseguite erroneamente dal processore. La presenza di unità di predizione dei salti implica che il processore spesso esegue delle istruzioni presupponendo che il processore esegua ( o non esegua) un certo salto. Ma, se la previsione fornita dall'unità di predizione dei salti si dovesse dimostrare non corretta le istruzioni eseguite erroneamente vanno eliminate per preservare il corretto funzionamento del programma.
 
== Ridenominazione dei registri ==
Nel caso dell'utilizzo da parte di più istruzioni di elementi condivisi (come il registro) il processore può essere dotato di un certo numero di registri nascosti che il decodificato assegna alle istruzioni rinominando i registri ed evitando i conflitto. In questo caso nella pipeline una volta eseguita l'istruzione un'apposita unità deve provvedere a copiare i valori negli opportuni registri.
Nei processori che implementano l'esecuzione fuori ordine non vi è una reale corrispondenza tra ciò che il programmatore si aspetta di trovare nei registri e ciò che realmente si trova nei registri dato che l'ordine cronologico del programma non è rispettato. Quando un'istruzione di trova ad accedere ad un registro che non contiene un dato corretto per il programma la pipeline dovrebbe andare in stallo fino a quando il registro non contenga il dato corretto o fino a quando non sia libero. Per evitare questo spreco di cicli di calcolo i processori implementano la ridenominazione dei registri. In sostanza il processore dispone di molti registri fisici nascosti che vengono assegnati alle istruzioni quando servono dei registri che risultano già occupati. Quindi in un dato istante per esempio un ipotetico registro R0 potrebbe essere in realtà assegnato a tre registri fisici che ''fingono'' di essere il registro R0 per le istruzioni che li usano. La corrispondenza tra questi registri fisici e i registri visti dal programma viene mantenuta da una tabella che viene aggiornata ad ogni decodifica di un'istruzione.
 
== Predizione dei salti ==
Nel caso di un'istruzione (B) che attende il risultato di un'altra istruzione (A) la pipeline può essere dotata di un'accumulatore aggiuntivo che provvede a fornire all'istruzione A il risultato appena l'istruzione B è stata elaborata.
Un processore basato su pipeline viene influenzato molto negativamente dalla presenza di salti condizionati dato che questi possono costringere il processore a svuotare la pipeline. Mediamente ogni 6 istruzioni un processore basato su architettura x86 incontra un salto condizionato e quindi i processori implementano delle unità molto complesse per cercare di prevedere se il salto condizionato verrà eseguito in modo da caricare in anticipo il blocco di istruzioni corretto nella pipeline. Vista l'importanza di questa unità si rimanda al capitolo apposito per spiegazioni più approfondite.
 
== Precaricamento dei dati ==
Nel caso di salti condizionati l'unità di predizione può cercare di stimare il percorso più probabile tramite l'unità di predizione dei salti o può decidere di seguire entrambi le strade e di scartare in seguito il percorso sbagliato.
I processori sono diventati sempre più veloci e nel giro di un decennio sono passati da frequenze di poche decine di Megahertz a frequenze di funzionamento dell'ordine dei Gigahertz. Le memorie invece sono non sono diventate molto più veloci di quanto fossero dieci anni fa, aumentando invece la quantità di dati trasmessi per secondo. I processori per attutire il problema hanno implementato cache di primo, secondo e a volte anche di terzo livello. La presenza delle cache con gli ultimi processori non è più sufficiente per evitare un eccessivo deperimento delle prestazioni e quindi alcuni processori implementano delle unità che analizzano il codice cercando di prevedere in anticipo quali dati o quali istruzioni serviranno al processore e provvedendo al loro caricamento in cache (o direttamente nel processore) prima della loro reale necessita. Alcune architetture prevedono delle istruzioni apposite per indicare quali blocchi precaricare ma la maggior parte delle architetture non sono dotate di queste istruzioni e quindi devono basarsi solamente sull'ispezione del codice in tempo reale. Il precaricamento dei dati e delle istruzioni introduce dei problemi, per esempio se il microprocessore carica delle istruzioni dipendenti da un salto e quel salto non viene eseguito il processore deve provvedere ad eliminare le istruzioni caricate erroneamente prima di caricare le istruzioni da eseguire. Problemi anche maggiori si hanno nel caso di generazione di un'eccezione, per esempio l'accesso a una locazione con consentita genera una eccezione che va segnalata al sistema operativo, ma la segnalazione va effettuata quando dovrebbe effettivamente avere luogo e non prima per via del precaricamento. Il precaricamento dei dati invece deve garantire la coerenza e la validità dei dati quindi se un dato viene precaricato nessuna istruzione deve modificarlo prima del suo effettivo utilizzo da parte delle istruzioni che hanno forzato il precaricamento. Questi problemi rendono il precaricamento dei dati e delle istruzioni molto complesso da implementare in hardware senza un supporto diretto del set di istruzioni. Invece se il set di istruzioni supporta nativamente questa caratteristica la sua gestione diventa molto più semplice.
 
== Esecuzione predicativa ==
L'unità di decodifica comunque analizza le varie dipendenze e organizza le istruzioni in modo da massimizzare l'utilizzo delle pipeline. Essendo che le varie istruzioni possono dipendere dai salti a queste vengono associate delle etichette che ne segnalano la reale esecuzione o meno. In sostanza se le istruzioni dipendono da un salto tramite l'etichetta viene memorizzato lo stato di istruzione dipendente. Se poi il salto viene realmente eseguito l'etichetta dell'istruzione viene modificata per segnalare che l'istruzione è realmente stata eseguita e vengono apportati gli opportuni cambiamenti ai registri. Se il salto non viene eseguito tramite le etichette si identificano le istruzioni da non eseguire ed i loro effetti sui registri vengono annullati in modo da mantenere la coerenza del programma.
L'esecuzione predicativa è un approccio che mira a sostituire la predizione delle diramazioni. I microprocessori moderni sono dotati di molte unità funzionali in grado di eseguire operazioni in parallelo ma queste unità sono quasi sempre vuote. Processori anche molto complessi come il Pentium 4 pur potendo in teoria eseguire fino a 6 operazioni in contemporanea in realtà per la maggior parte del tempo eseguono una o due operazioni in parallelo. Partendo da questa constatazione l'approccio predicativo punta a riempire al massimo le unità di elaborazioni eliminando le istruzioni eseguite per errore. Un processore dotato di esecuzione predicativa oltre a leggere le istruzione legge dei predicati associate a esse, questi predicati indicano al processore in caso di salto condizionato quale ramo risulta effettivamente valido. In sostanza in caso di salto condizionato il processore associa alla condizione di salto un predicato (per esempio il predicato P0). Al blocco di istruzioni da eseguire se il salto viene eseguito viene associato il predicato P0=0 mentre al blocco di istruzioni da eseguire nel caso il salto non venga eseguito viene associato il predicato P0=1. Poi il processore esegue in parallelo l'istruzione di salto in un'unità funzionale, le istruzioni del primo blocco in una seconda unità funzionale e l'istruzione del secondo blocco in una terza unità funzionale. Quando l'istruzione di salto viene calcolata si vede se il predicato vale 0 oppure 1 e quindi si può stabilire quale blocco di istruzioni sia valido. Il blocco con le istruzioni non valide vengono individuate tramite i predicati loro associati ed eliminate. Questa filosofia di sviluppo quindi preferisce eseguire anche istruzioni inutili pur di mantenere sempre piene le pipeline.
 
Ovviamente tutte queste infrastrutture aggiuntive rendono le unità di decodifica molto complesse e queste occupano buona parte dei transistor dei moderni processori. Per ridurre il problema si sono sviluppate architetture come le architetture VLIW e derivate che affrontano il problema alla radice con un paradigma diverso.