Macchina di Von Neumann

Questo modello (ideato da Von Neumann, matematico vissuto nel XX secolo) costituisce ancora oggi l’architettura generale di (quasi) ogni sistema informatico esistente.
Le idee alla base di questa macchina sono:
– un processore (CPU): esso esegue una sequenza di istruzioni (appartenenti ad un insieme predefinito di istruzioni possibile) caricate in memoria dal programmatore, che elaborano dei dati, anch’essi caricati in memoria.
– la memoria del computer è unica e contiene quindi sia il programma (con le istruzioni da eseguire) che i dati che esso utilizza: essa viene controllata dalla CPU, che carica sia istruzioni che dati, li elabora secondo le istruzioni ricevute, e li salva nella memoria. La memoria nei computer moderni corrisponde alla RAM, ma in ogni caso la RAM è una specifica implementazione della memoria.
– l’I/O (Input/Output) consente l’accesso con l’esterno: è un componente che ha una memoria interna che memorizza i dati ricevuti dall’esterno e quelli da inviare e quindi comunica con le periferiche (tastiere, mouse, monitor, memorie di massa, rete, ecc.).
– ogni componente comunica con gli altri mediante bus, linee dati dedicate.
Questo modello è è stato esteso e oggi sono state realizzate architetture più complesse, ma sempre basate su questo modello.
Vediamo in dettaglio i singoli componenti.
CPU

La CPU (Central Processing Unit) è composta dai seguenti componenti:
– una ALU (Arithmenic Logic Unit), che svolge le attività aritemiche e logiche (operazioni, confronti);
– una CU (Control Unit), che svolge le attività di gestione e controllo;
– un registro IR, che memorizza l’istruzione corrente;
– un registro PC, che memorizza la posizione della prossima istruzione in memoria;
– un clock che temporizza le attività della CPU;
– un insieme di registri, che memorizzano gli operandi delle operazioni di calcolo, e gli eventuali risultati.
La CPU opera mediante istruzioni semplici che svolgono operazioni elementari. Von Neumann prevede istruzioni delle seguenti categorie:
– operazioni aritmetiche (somma, prodotto, ecc.) e di confronto tra operandi, eseguite dalla ALU:
– operazioni di controllo, ad esempio salti ad una istruzione specifica (a seconda di una condizione o liberi), eseguiti dalla CU
– operazioni di caricamento di registri dalla memoria e viveversa, eseguiti dalla CU
Come sappiamo ogni algoritmo può essere schematizzato in una combinazione di operazioni di assegnazione, di calcolo, e mediante condizioni e cicli che permettono di ripetere il codice e prendere decisioni. Siccome la macchina di Von Neumann prevede tutte le suddette operazioni, essa è un computer completo, e può realizzare qualsiasi programma.
Le istruzioni sono codificate in linguaggio macchina, interpretato dalla CU, che nella maggior parte dei computer sono codificate tramite codice binario.
Come funziona la CPU?
La sua attività è scandita da un clock, un componente che emette un segnale temporizzato che determina il ciclo di esecuzione di una istruzione. Questo ciclo è diviso in 3 macro fasi:

– nella fase di “fetch“, la CU legge il valore di PC e carica dalla memoria l’istruzione all’indirizzo indicato da PC
– nella fase di “decode“, la CU decodifica l’istruzione ed individua quindi l’operazione da eseguite e gli eventuali operandi (ad esempio se è una somma (“ADD”) decodificherà anche gli operandi, che ad esempio potranno essere registri o anche valori assoluti).
– nella fase di “exec” viene eseguita l’istruzione, con relativo salvataggio del dato se previsto, e viene aggiornato il PC all’istruzione successiva. Se l’istruzione prevede un salto, viene memorizzato in PC l’indirizzo dell’istruzione successiva.
Il ciclo ricomincia con l’istruzione successiva, che viene ricaricata dall’indirizzo indicato da PC, e così via.
Le istruzioni hanno tutte pari tempo di esecuzione, un ciclo di clock.
Chiaramente operazioni relativamente semplici in un linguaggio ad alto livello possono richiedere molte istruzioni in linguaggio macchina. I cicli di clock, a seconda dell’architettura e della tecnologia utilizzata possono variare dalle poche centinaia al secondo dei primi computer (ENIAC) ai miliardi di operazioni al secondo dei computer moderni, aumentando così la velocità dei computer.
Negli ultimi decenni si sono evolute architetture di cpu moderne che prevedono istruzioni che sono in grado di svolgere anche operazioni relativamente complesse, le più conosciute sono CISC (x86-x64) e RISC (ARM).
Il funzionamento del codice macchina verrà approfondito in apposita lezione.
Memoria

Nella macchina di Von Neumann, la memoria è organizzata come un grosso vettore[1], ovvero una sequenza di celle ciascuna con un indirizzo assoluto (da 0 alla memoria massima allocabile). La memoria massima logica di un programma è in genere data dalla sua architettura: i primi computer potevano avere pochi K di memoria, quelli moderni arrivano a decine di GB.
La memoria è unica e contiene sia le istruzioni che i dati, cioè le variabili, le strutture dati, i dati di input/ouput ed ogni dato di elaborazione del programma
In molte implementazioni il codice viene memorizzato nella parte bassa della memoria, e ciò avviene al caricamento del programma in memoria (da parte del sistema operativo).
Il programma in esecuzione ha pieno accesso alla memoria, e quindi può leggere e scrivere in qualsiasi locazione di memoria, conoscendone il solo indirizzo, cioè la posizione in memoria. I programmi scritti in linguaggio ad alto livello hanno comunque meccanismi di protezione della memoria, ma quando vengono eseguiti forniscono al programmatore direttamente l’accesso a variabili e tipi di dato (numeri, stringhe, ecc.).
Internamente però queste variabili vengono memorizzate nell’area di memoria immediatamente “sopra” al codice, che viene chiamata “heap” (cumulo).
La memoria “in alto” invece viene utilizzata per memorizzare le chiamate ai sottoprogrammi (detti anche subroutine). I sottoprogrammi sono parti di un programma che possono essere eseguite in parti diverse del programma (svolgono compiti di utilità e semplificano lo sviluppo di software, evitando di ripetere di scrivere lo stesso codice). Le chiamate ai sottoprogrammi richiedono di memorizzare l’indirizzo di ritorno della chiamata, i parametri o argomenti che questi possono ricevere, ed eventuali valori di ritorno. Quest’area di memoria viene chiamata “stack” (pila) e verrà approfondita in apposita lezione.
BUS e I/O

Nella macchina di Von Neumann riveste importanza l’architettura del bus, che prevede sempre almeno due linee dedicate:
– una per i dati, dove transita il contenuto da una sorgente ad una destinazione;
– una per gli indirizzi e le informazioni di controllo (possono anche essere due bus dedicati), che serve per determinare la destinazione e l’indirizzo dove riporre i dati.
Quindi la cpu quando scrive in memoria prima invia un comando ed un indirizzo via bus indirizzi, e poi trasferisce l’informazione tramite bus dati. La stessa identica cosa succede nella comunicazione dell’i/o con la memoria. Di fatto per semplicità la CPU comunica con la memoria e la memoria con l’input/output.
L’input/output consiste in un componente collegato da una parte al bus per trasferire informazioni.
Dal punto di vista interno, l’i/o ha un comportamento simile alla memoria, ha cioè degli indirizzi o gruppi di indirizzi a cui sono associati delle periferiche. Quando avviene un evento di input, l’i/o è progettato in modo tale da scrivere nei propri indirizzi il flusso di dati (ad esempio input da tastiera, movimento del mouse, dati dalla rete, ecc.) a partire dalla fonte digitale da cui essi provengono. Viceversa quandon viene generato un evento di output, l’i/o legge dagli indirizzi ed invia digitalmente il dato alla periferica associata all’indirizzo.
[1] un vettore è una lista di elementi (in questo caso bytes) dello stesso tipo, identificati da una posizione. Nella memoria la posizione si chiama “indirizzo di memoria”.