CPU
thumb|Una CPU moderna La CPU (acronimo di Central Processing Unit, detta comunemente processore) è l'implementazione fisica di uno dei due componenti della macchina di Turing (l'altro è la memoria).
Compito della CPU è quello di leggere le istruzioni e i dati dalla memoria ed eseguire le istruzioni; il risultato della esecuzione di una istruzione dipende dal dato su cui opera e dallo stato interno della CPU stessa, che tiene traccia delle passate operazioni.
In base all'organizzazione della memoria si possono distingure due famiglie di CPU:
- con architettura von Neumann classica, in cui dati ed istruzioni risiedono nella stessa memoria (è dunque possibile avere codice automodificante). Questa architettura è la più comune, perché la più semplice e flessibile.
- con architettura Harvard: i dati e le istruzioni risiedono in due memorie separate. Questa architettura garantisce migliori prestazioni poiché le due memorie possono lavorare in parallelo ma è più complessa da gestire. È tipicamente utilizzata nei DSP.
Qualunque CPU contiene:
- una ALU (Unità Aritmetico-Logica) che si occupa di eseguire le operazioni logiche e aritmetiche;
- una Unità di Controllo che legge dalla memoria le istruzioni, se occorre legge anche i dati per l'istruzione letta, esegue l'istruzione e memorizza il risultato se c'è, scrivendolo in memoria o in un registro della CPU.
- dei registri, speciali locazioni di memoria interne alla CPU, molto veloci, a cui è possibile accedere molto più rapidamente che alla memoria: il valore complessivo di tutti i registri della CPU costituisce lo stato in cui essa si trova attualmente. Due registri sempre presenti sono:
- il registro IP (Instruction Pointer) o PC (Program Counter), che contiene l'indirizzo in memoria della prossima istruzione da eseguire;
- il registro dei flag: questo registro non contiene valori numerici convenzionali, ma è piuttosto un insieme di bit, detti appunto flag, che segnalano stati particolari della CPU e alcune informazioni sul risultato dell'ultima operazione eseguita. I flag più importanti sono:
- Zero: vale 1 se l'ultima operazione ha avuto risultato zero, altrimenti vale 0.
- Carry: vale 1 se l'ultima operazione ha ecceduto la capacità del registro che contiene il risultato, altrimenti vale 0 (esempio: in un registro a 8 bit, che può rappresentare solo numeri da 0 a 255, la somma 178+250 darebbe come risultato 172 e il carry verrebbe posto a 1).
- Interrupt: se a questo flag viene assegnato valore 1, la CPU smette di rispondere alle richieste di servizio esterne delle periferiche (i segnali delle linee IRQ) finché non viene ripristinato al valore 0, o finché non arriva dall'esterno un segnale di RESET.
A proposto di registri le CPU si distinguono in:
- basate su stack: i registri sono organizzati in una struttura a stack e tutte le istruzioni operano esclusivamente su questo stack (ad esempio il Transputer); questa architettura ha il vantaggio di non dover specificare su quale registro interno operare (è sempre quello in cima allo stack), ottenendo istruzioni (opcodes) più corte e più semplici da decodificare. L'altra faccia della medaglia è che nel caso sia necessario un dato "sepolto" in fondo allo stack, il suo recupero è un'operazione molto lenta.
- basate su registri: queste CPU sono dotate di ulteriori registri generici che restano a disposizione dell'utente per memorizzarci valori temporanei. Le istruzioni possono accedere a questi registri generici. La maggioranza delle CPU sono di questo tipo.
Una CPU è un circuito digitale sincrono: vale a dire che il suo stato cambia ogni volta che riceve un impulso da un segnale di sincronismo detto CLOCK: quindi il tempo di esecuzione di una istruzione si misura in cicli di clock, cioè in quanti impulsi di clock sono necessari perché la CPU la completi. In effetti, una parte importante e delicata di ogni CPU è il sistema di distribuzione che porta il segnale di clock alle varie unità e sottounità di cui è composta, per fare in modo che siano sempre in sincronia: tale sistema si dirama in una struttura ad albero con divisori e ripetitori che giunge ovunque nella CPU. Nei processori più moderni (Pentium, Athlon, PowerPC) questa "catena di ingranaggi" elettronica arriva ad impiegare circa il 30% di tutti i transistor disponibili. La velocità di questa distribuzione determina in maniera diretta la massima frequenza operativa di una CPU: nessuna CPU può essere più veloce del suo critical path, cioè del tempo che impiega il clock per percorrere il tratto più lungo in tutto l'albero di distribuzione del clock. Per esempio, se il segnale di clock di una data CPU impiega un nanosecondo per attraversare tutto il chip ed arrivare fino all'ultima sottounità, questa CPU potrà operare a non più di 1 GHz, perché altrimenti le sue componenti interne perderebbero la sincronizzazione, con risultati imprevedibili (per motivi di tolleranze e margini di sicurezza, il limite pratico sarà anzi ben minore di 1GHz).
| Indice |
Set di istruzioni
Le istruzioni di una CPU (instruction set) sono semplicemente dei numeri, detti opcode o codici operativi: in base al loro valore l'unità di controllo intraprende delle azioni predefinite, come per esempio leggere la successiva locazione di memoria per caricare un dato, oppure attivare la ALU per eseguire un calcolo, oppure scrivere il contenuto di un registro in una certa locazione di memoria o in un altro registro, oppure una combinazione di queste.
Per una persona, stendere programmi scrivendo direttamente gli opcode è estremamente noioso e prono all'errore. Per questo motivo si utilizza l'assembly. L'assembly associa un simbolo mnemonico ad ogni istruzione della CPU e introduce una sintassi che permette di esprimere i vari metodi di indirizzamento.
Una caratteristica importante dell'insieme (set) delle istruzioni di una CPU è la sua ortogonalità: vale a dire, il fatto che ogni istruzione che usi i registri possa usarli tutti indifferentemente (tranne quelli "speciali" come l'IP) e che nessun registro sia in qualche modo privilegiato rispetto agli altri perché su di esso si possono compiere operazioni particolari: è stato dimostrato che un set di istruzioni ortogonali, a parità di tempo di esecuzione delle istruzioni e di numero dei registri, è più efficiente di uno non ortogonale.
Architettura della CPU
Qualunque CPU possiede almeno due unità distinte:
- una ALU (Unità Aritmetico-Logica) che si occupa di eseguire le operazioni logiche e aritmetiche;
- una Unità di Controllo che legge dalla memoria le istruzioni, se occorre legge anche i dati per l'istruzione letta, esegue l'istruzione e memorizza il risultato se c'è, scrivendolo in memoria o in un registro della CPU.
Oltre a queste possono esserne presenti altre, per esempio:
- una FPU (Floating Point Unit) che si occupa di eseguire calcoli in virgola mobile;
- una MMU (Memory Management Unit) che si occupa di tradurre gli indirizzi di memoria logici in indirizzi fisici, supportando la protezione della memoria e/o uno o più meccanismi di memoria virtuale.
Una generica CPU deve gestire una serie di operazioni sincronizzandole con il resto del sistema: perciò è dotata, oltre a quanto sopra elencato, anche di uno o più bus interni che si occupano di collegare registri, ALU, unità di controllo e memoria: inoltre, all'unità di controllo interna della CPU fanno capo una serie di segnali elettrici esterni che si occupano di tenere la CPU al corrente dello stato del resto del sistema e di agire su di esso. Il tipo e il numero di segnali esterni gestiti possono variare ma alcuni, come il RESET, le linee di IRQ e il CLOCK sono sempre presenti.
Il normale ciclo di operazioni di una CPU è il seguente: l'unità di controllo legge la locazione di memoria indicata dal registro IP, incrementa il registro IP di uno e la decodifica; se la decodifica indica che l'istruzione è più lunga di una locazione di memoria, legge anche il resto dell'istruzione e incrementa l'IP del numero di locazioni lette; esegue l'istruzione; se necessario scrive in memoria (o in altri registri) il risultato. Dopodiché torna a leggere l'indirizzo puntato da IP per caricare ed eseguire un'altra istruzione, all'infinito.
Questo ciclo elementare può essere migliorato in vari modi: per esempio, la decodifica di una istruzione può essere fatta contemporaneamente all'esecuzione della precedente e alla lettura dalla memoria della prossima (instruction prefetch) e lo stesso può essere fatto con i dati che si prevede saranno necessari alle istruzioni (data prefetch). La stessa esecuzione delle istruzioni può essere suddivisa in passi più semplici, da eseguire in stadi successivi, organizzando la unità di controllo e la ALU in stadi consecutivi, come delle catene di montaggio (pipeline): in questo modo più istruzioni possono essere eseguite "quasi contemporaneamente", ciascuna occupando ad un certo istante uno stadio diverso della pipeline.
Il problema di questo approccio sono le istruzioni di salto condizionato, cioè istruzioni che impostano il valore stesso del registro IP, ma solo se al momento della loro esecuzione sono verificate certe condizioni (particolari valori di registri). La CPU non può sapere a priori se dovrà eseguire o no il salto prima di aver eseguito quelle precedenti, così deve decidere se impostare la pipeline tenendo conto del salto o no: e in caso di previsione errata la pipeline va svuotata completamente e le istruzioni in corso di decodifica rilette da capo, perdendo molto tempo: e si perde tanto più tempo quanti più stadi ha la pipeline. Per evitare questo i processori moderni hanno unità interne (Branch prediction unit) il cui scopo è tentare di prevedere se, data una istruzione di salto condizionato e quelle eseguite in precedenza, il salto dovrà essere eseguito o no.
Architetture CISC e architetture RISC
Quando i transistor disponibili su un solo chip erano pochi e i calcolatori venivano spesso programmati in assembler, era naturale sfruttarli in modo tale da avere CPU con istruzioni potenti, evolute e complesse: più queste erano vicine alle istruzioni dei linguaggi di programmazione ad alto livello più il computer sarebbe stato facile da programmare, e i programmi avrebbero occupato poco spazio in memoria (anch'essa poca e preziosa). Le CPU progettate secondo questo approccio sono dette CISC ed avevano unità di controllo complesse capaci di sfruttare al meglio pochi registri e i cui programmi erano di dimensioni relativamente piccole. A cavallo fra gli anni '70 e gli '80 però la situazione era in gran parte cambiata: la RAM era molto più economica ed erano ormai disponibili ottimi compilatori in grado di generare linguaggio macchina molto efficiente: per questo si iniziò a pensare ad un nuovo modo di progettare le CPU, prendendo in esame la possibilità di usare i transistor disponibili per avere invece molti registri e un set di istruzioni elementare, molto ridotto, che delegasse al compilatore il lavoro di tradurre le istruzioni complese in serie di istruzioni più semplici, permettendo così di avere unità di controllo particolarmente semplici e veloci. Oramai però la distinzione fra queste due architetture è venuta in gran parte meno: il numero di transistor disponibili su un solo chip è aumentato tanto che si può avere sia la botte piena che la moglie ubriaca, e attualmente tutti i processori moderni sono CRISP, cioè implementano molti registri e hanno anche unità di controllo complesse.
CISC (Complex Instruction Set Computer)
CISC è l'acronimo di Complex Instruction Set Computer: tipicamente un processore di questo tipo implementa un numero relativamente scarso (una decina) di registri di uso generale, ed ha una unità di controllo microprogrammata: vale a dire che ogni opcode viene tradotto in una serie di passi da compiere secondo un procedimento software interno all'unità di controllo stessa, che è in pratica una CPU nella CPU.
Il set di istruzioni associato a CPU di tipo CISC è molto esteso e composto in genere di alcune centinaia di codici operativi diversi che svolgono funzioni anche molto complesse, fra cui sono caratteristici i trasferimenti memoria-memoria, assenti nei RISC; le istruzioni hanno lunghezza variabile e possono presentarsi in formati diversi, e sono necessari due o più (a volte molti di più) cicli di clock per completare una istruzione; è possibile specificare la posizione dei dati necessari alle istruzioni usando molti metodi di indirizzamento diversi. Il ridotto numero di registri interni obbliga questi processori a scrivere in memoria ogni volta che si verifica una chiamata di funzione, che si verifica un context switch o che viene salvato un registro nello stack.
Programmare in Assembler un CPU CISC è un compito (relativamente) facile, perché le istruzioni presenti sono più vicine a quelle dei linugaggi ad alto livello: i compilatori che generano codice per CPU CISC sono piuttosto semplici ed inoltre il codice prodotto è molto compatto e occupa poca memoria.
RISC (Reduced Instruction Set Computer)
RISC è l'acronimo di Reduced Instruction Set Computer: questi processori hanno una unità di controllo cablata molto semplice e riservano invece molto spazio per i registri interni: una CPU RISC ha di solito da un minimo di un centinaio ad alcune migliaia di registri interni generici, organizzati in un file di registri. Il tipico set di istruzioni RISC è molto piccolo, circa sessanta o settanta istruzioni molto elementari (logiche, aritmetiche e istruzioni di trasferimento memoria-registro e registro-registro): hanno tutte lo stesso formato e la stessa lunghezza, e tutte o quasi vengono eseguite in un solo ciclo di clock. Sono presenti solo un numero ristretto di metodi di indirizzamento. Il fatto di avere un formato unico di istruzione permette di strutturare l'unità di controllo come una pipeline, cioè una catena di montaggio a più stadi: questa innovazione ha il grosso vantaggio di ridurre il critical path interno alla CPU e consente ai RISC di raggiungere frequenze di clock più alte rispetto agli analoghi CISC.
Nel caso di context-switch o di chiamata a subroutine o comunque di uso dello stack i RISC invece di accedere alla memoria di sistema usano un meccanismo chiamato register renaming, che consiste nel rinominare i registri in modo da usare per la nuova esecuzione una diversa zona del file di registri, senza dover accedere alla memoria ogni volta.
La complessità nei RISC si sposta dall'hardware al software: un compilatore che genera codice per CPU RISC deve affrontare un duro lavoro per generare codice compatto ed efficiente, che in ogni caso sarà più grande ed occuperà più memoria dell'equivalente per CISC.
