Appunti sulla " Macchina di Von Neumann "

Corso di Programmmazione per Matematica e SMID, 2003/04

docente Gerardo Costa


NOTA:

Questi appunti sono ricavati dagli Appunti del corso di ARCHITETTURA DEI CALCOLATORI, Corso di Laurea in Informatica, del prof. Giovanni Chiola, tagliando alcune parti che mi sono sembrate troppo complicate, aggiungendo qualche spiegazione ulteriore e facendo qualche modifica per permettere di leggere le note senza aver letto quanto le precedeva. La versione originale si trova QUI.

Gli appunti del prof. Chiola sono protetti dalle condizioni di "Copyleft" che seguono. In base a queste condizioni, mi assumo la piena responsabilità del prodotto finale.


Copyright © 1996-2003.

Questo documento é protetto dalla legge sul diritto di autore, e di proprietà esclusiva dell'autore che se ne riserva tutti i diritti.

L'autore concede a chiunque il permesso di copiare gratuitamente qualunque sottoinsieme dei files che compongono questi appunti per qualsiasi uso e su qualsiasi supporto, purchè la copia di ciascun file sia integrale, senza alcuna modifica ad eccezione di un eventuale cambiamento di formato di rappresentazione per necessità di stampa o memorizzazione su sistemi diversi, e purchè ogni copia sia sempre accompagnata dalla presente clausola completa di Copyright.

Le informazioni presenti in questo documento distribuito dall'autore in forma gratuita vengono fornite in quanto tali, senza nessuna garanzia di correttezza, consistenza o assenza di errori da parte dell'autore stesso, il quale per altro declina ogni responsabilità per qualsiasi danno o inconveniente che potesse derivare dall'uso del documento stesso, anche se si dovesse verificare il caso che talune informazioni qui contenute non fossero corrette.

É permessa anche la riutilizzazione parziale o totale del testo e delle figure per la produzione di lavori derivati, purchè i lavori derivati siano soggetti alle condizioni di diffusione ed uso di questo documento originale, con particolare riferimento alla possibilità per chiunque di copiare liberamente e senza restrizione alcuna il documento in forma integrale e di usarne parti per la creazione di lavori derivati soggetti alle medesime condizioni di diffusione ed uso. Nel caso di creazione di lavori derivati mediante inclusione e/o modifica sostanziale del testo originale, l'autore delle modifiche si assumerà la paternità delle aggiunte e delle modifiche, ed il nome dell'autore del testo originale non potrà essere utilizzato senza il suo preventivo ed esplicito consenso per patrocinare l'opera derivata.

Le condizioni di questo Copyright sono liberamente ispirate al "copyleft" originariamente introdotto da Richard Stallman, e dovrebbero essere una versione semplificata ma sostanzialmente equivalente alla "Free Documentation License" raccomandata dalla "Free Software Foundation.



La nostra Macchina di Von Neumann

Facciamo qui riferimento ad una organizzazione di un sistema di calcolo effettivamente realizzato e reso operativo nell'ambito di un progetto dell'Università di Princeton sotto la guida del matematico John Von Neumann. Prescinderemo ovviamente da alcuni dettagli operativi derivanti dai limiti della tecnologia elettronica degli anni '40, oggi ampiamente superati, e ci concentreremo sulle intuizioni di base, che ancor oggi stanno dietro la progettazione e la realizzazione dei sistemi di calcolo.

Struttura della Macchina

La macchina é realizzata mediante l'interconnessione di quattro dispositivi complessi: memoria, unità operativa, ingresso e uscita, come illustrato in figura:

RAM (Random Access Memory, memoria ad accesso arbitrario mediante indirizzo)
Realizza la memorizzazione di un vettore di numeri interi. Sia la dimensione del vettore (numero di elementi componenti) che il massimo valore memorizzabile in ogni elemento del vettore sono predeterminati al momento della costruzione e/o assemblaggio del dispositivo. Inteso come macchina virtuale, supporta l'esecuzione di due istruzioni fondamentali: memorizza V nella cella N; recupera il valore precedentemente memorizzato nella cella N.
Input (unità di ingresso)
Permette all'utente di interagire con la macchine (per esempio attraverso l'uso di una tastiera numerica) per l'introduzione di valori di tipo intero, uno alla volta.
Output (unità di uscita)
Permette alla macchina di stampare in un formato numerico leggibile dall'utente un valore intero per volta.
Control Unit (unità di controllo)
realizza il funzionamento della macchina secondo le modalità spiegate nel seguito. Contiene due registri in grado di memorizzare valori numerici interi, esattamente come le celle della RAM: un registro accumulatore ed un registro contatore di programma (Program Counter, abbreviato PC). Inoltre, contiene un ulteriore registro (Instruction Register, abbreviato IR), il cui ruolo è trasparente al programmmatore, in quanto, come vedremo, entra in gioco solo a livello dell'esecuzione dei programmi.
Per fissare le idee possiamo ipotizzare che l'unità RAM disponga di mille elementi (o celle, o parole) di memoria, e che ciascuna cella di memoria sia quindi univocamente individuata da un numero compreso tra 0 e 999. Potremmo quindi indicare con la notazione RAM[5] la sesta parola di memoria.

Nota. Per semplicità ignoriamo i dettagli che riguardano la codifica dei numeri interi e i limiti sui valori interi rappresentabili.

L'unità di controllo é realizzata in modo da poter eseguire (direttamente oppure comandando parte dell'esecuzione alle altre unità della macchina) un insieme finito di istruzioni. Sempre a titolo esemplificativo potremmo individuare un insieme di nove istruzioni base.

Istruzioni della Macchina

L'idea fondamentale di Von Neumann, che viene ancora oggi seguita nella realizzazione dei sistemi di calcolo fu quella di codificare le istruzioni in forma numerica, e di inserirle, insieme agli eventuali dati, nella unità RAM della macchina. Un modo molto semplice per ottenere una codifica numerica per l'identificazione delle istruzioni é quella di elencarle in una lista ordinata, e di usare il numero d'ordine all'interno della lista per individuare l'istruzione. Proviamo a considerare il seguente esempio:

l'Insieme delle istruzioni della nostra Macchina di Von Neumann

istruzione 0
somma di due numeri interi; in particolare, somma il valore memorizzato nel registro accumulatore col valore memorizzato in una cella di memoria, individuata dall'indirizzo N; Il risultato della somma viene memorizzato nel registro accumulatore al posto del primo addendo.
istruzione 1
differenza tra due numeri interi; in particolare, sottrae il valore contenuto nella cella RAM[N] al valore contenuto nel registro accumulatore; Il risultato della differenza viene memorizzato nel registro accumulatore al posto del primo operando.
istruzione 2
lettura di un valore numerico dal dispositivo di ingresso; Il valore letto viene memorizzato nel registro accumulatore (perdendo memoria del valore precedentemente memorizzato nell'accumulatore stesso).
istruzione 3
scrittura di un valore numerico sul dispositivo di uscita; Il valore memorizzato nel registro accumulatore viene trasferito per la stampa sul dispositivo di uscita (senza perdere memoria del valore nel registri accumulatore).
istruzione 4
memorizza il valore contenuto nel registro accumulatore anche all'interno della cella RAM[N] (senza perdita di memoria da parte del registro accumulatore).
istruzione 5
copia il valore memorizzato nella cella RAM[N] anche nel registro accumulatore; il registro accumulatore perde traccia del valore in esso precedentemente memorizzato, mentre la cella di RAM[N] mantiene invariata la sua memoria.
istruzione 6
memorizza il valore numerico N nel registro PC, perdendo quindi memoria del valore precedente;
istruzione 7
controlla il valore numerico contenuto nel registro accumulatore; se il valore é zero allora esegui le stesse operazioni definite per l'istruzione 6, altrimenti non fare niente;
istruzione 8
ferma l'interpretazione del programma.
Tralasciando per il momento le istruzioni 6, 7 e 8, possiamo fare tre considerazioni essenziali: Per esempio, se vogliamo stampare la somma tra due numeri usando la nostra macchina, potremmo leggere il primo addendo (istruzione 2), poi memorizzarlo (per esempio) nella cella RAM[50] (istruzione 4), poi leggere il secondo addendo (istruzione 2 di nuovo), poi sommare il contenuto di RAM[50] (istruzione 0), ed infine stampare il risultato (istruzione 3).

Interpretazione dei programmi

Supponendo di saper codificare qualsiaso istruzione (compresi i suoi eventuali operandi) sotto forma numerica in una singola cella di RAM, allora possiamo individuare una modalità di funzionamento sequenziale per la nostra macchina facendo uso del registro PC, esprimibile con il seguente algoritmo:

inizializzazione:
memorizza il valore 0 nel registro PC;
fetch:
recupera il valore contenuto nella cella RAM[PC] (ovvero, la cella RAM[i], dove i é il valore memorizzato correntemente in PC) e memorizzalo in un terzo registro chiamato delle istruzioni (instruction register, abbreviato IR);
somma il valore costante 1 al valore contenuto nel registro PC e sostituisci il valore contenuto in PC col risultato di questa somma;
decodifica:
confronta il valore contenuto nel registro IR con la tabella delle istruzioni della macchina per individuare quale istruzione é codificata da tale numero;
esecuzione:
realizza l'istruzione codificata dal valore numerico contenuto nel registro IR;
alla fine torna ad eseguire il passo fetch, a meno che l'istruzione eseguita non fosse la 8.
Applicando questo semplice algoritmo, l'unità di controllo é in grado di eseguire programmi sequenziali le cui istruzioni sono codificate sotto forma numerica nelle celle della RAM a partire dall'indirizzo 0. Ovvero, RAM[0] contiene la codifica della prima istruzione del programma, RAM[1] contiene la codifica della seconda istruzione del programma, ecc.

Il caricamento di programmi sequenziali nella RAM (codificati in forma numerica) costituisce quindi un mezzo semplice ma molto efficace per definire sequenze di istruzioni (anche molto lunghe) che la macchina dovrà eseguire una volta attivata (per esempio attraverso la pressione di un apposito pulsante).

Le istruzioni 6, 7 e 8 servono per alterare l'esecuzione sequenziale di programmi codificati in memoria. L'istruzione 6 viene chiamata normalmente salto di programma, in quanto determina una interruzione nella continuità della scansione della RAM in forma sequenziale: fa si che la prossima istruzione da eseguire venga decodificata a partire dal contenuto della cella RAM[N] anziché dal contenuto della cella di indirizzo successivo rispetto a quella che contiene la codifica dell'istruzione di salto. L'istruzione 7 viene chiamata (per ovvie ragioni) salto condizionale. Notare che anche le istruzioni 6 e 7 richiedono la codifica di un parametro numerico oltre all'individuazione della istruzione, mentre l'istruzione 8 é priva di parametri.

Un algoritmo semplice per ottenere la codifica numerica di tutte le istruzioni, comprese quelle con un operando, consiste nel moltiplicare per 1000 il numero d'ordine dell'istruzione e sommare il valore dell'eventuale operando (sempre compreso tra 0 e 999 a causa della limitazione sulla dimensione della RAM). La decodifica verra' effettuata prendendo la parte intera della divisione del valore numerico diviso 1000 per ottenere il numero d'ordine dell'istruzione, ed il resto di questa stessa divisione come valore dell'indirizzo della cella di memoria da usare come operando.

Esempi di programmi

Il nostro programma per la stampa della somma di due numeri corrisponderà quindi a caricare in memoria i seguenti valori prima di attivare l'esecuzione della macchina:

RAM[0] = 2000 (istruzione 2)
RAM[1] = 4050 (istruzione 4 con parametro N=50)
RAM[2] = 2000 (istruzione 2)
RAM[3] = 50 (istruzione 0 con parametro N=50)
RAM[4] = 3000 (istruzione 3)
RAM[5] = 8000 (istruzione 8)

Notare che il programma potrebbe funzionare altrettanto bene se sostituissimo i valori contenuti nelle celle RAM[1] con 4000 e RAM[3] con 0. Notare anche che con tale sostituzione la stessa cella RAM[0] verrebbe utilizzata (in tempi diversi) per contenere prima la codifica della prima istruzione del programma e poi (dopo che tale istruzione é stata eseguita) il valore del primo addendo letto dal dispositivo di ingresso. Una tale variazione potrebbe aver senso in questo caso per "risparmiare" l'uso di una cella di memoria, tuttavia riduce la capacità del programmatore di comprendere il funzionamento del programma e garantirne la correttezza. In effetti, per garantire la correttezza di una tale utilizzazione ottimizzata della memoria bisogna essere certi che l'istruzione di cui si perde memoria non dovrà mai più essere eseguita nel prosieguo dell'interpretazione del programma. Per esempio l'utilizzazione della cella RAM[2] (ottenuta sostituendo i valori 4002 nella cella RAM[1] e 2 nella cella RAM[3]) porterebbe ad un programma errato (a meno che, per pura coincidenza, l'utente non decida di specificare il valore 2000 come primo addendo).

Per verificare il livello di comprensione circa la possibilità di programmare una macchina di questo genere proviamo ad organizzare una modifica dell'esempio proposto in modo da poter ripetere indefinitamente l'operazione di somma tra due numeri e stampa del risultato, fin quando non si incontra un addendo uguale a zero (nel qual caso il programma deve terminare). In altre parole, in input diamo una successione di numeri: a_1, a_2, a_3, a_4, ..... ed in output vogliamo: a_1 + a_2, a_3 + a_4, .....; il programma si ferma appena legge un a_k che è zero. Per realizzare questa modifica occorre prevedere l'aggiunta di istruzioni di tipo 6 (per ripetere indefinitamente la sequenza delle istruzioni dall'inizio) e di tipo 7 (per distinguere il caso di addendo uguale a zero da quello di addendo diverso da zero). Una possibile realizzazione sotto forma di programma é la seguente:

RAM[0] = 2000 (istruzione 2)
RAM[1] = 7009 (istruzione 7 con parametro N=9)
RAM[2] = 4008 (istruzione 4 con parametro N=8)
RAM[3] = 2000 (istruzione 2)
RAM[4] = 7009 (istruzione 7 con parametro N=9)
RAM[5] = 8 (istruzione 0 con parametro N=8)
RAM[6] = 3000 (istruzione 3)
RAM[7] = 6000 (istruzione 6 con parametro N=0)
RAM[8] = 0 (usata per dati, non per istruzioni)
RAM[9] = 8000 (istruzione 8)
Notare che questa volta il programma non può essere automodificante in quanto le istruzioni devono poter essere eseguite per un numero indefinito di volte a causa della presenza di un ciclo da ripetere per un numero di volte non determinabile a priori (dipendente dalla sequenza di valori inseriti dall'utente).

Istruzioni o dati?

In effetti, l'unità di controllo applica l'algoritmo di fetch e decodifica delle istruzioni in modo indiscriminato, sulla base del valore assunto dal registro PC, senza aver alcun modo per sapere se il valore inserito nel registro IR fosse stato effettivamente memorizzato nella cella RAM[PC] con lo scopo di codificare una istruzione oppure di mantenere un valore numerico. É responsabilità del programmatore far si che la cella da cui viene fatto il fetch contenga effettivamente un numero che rappresenta l'istruzione che si vuol eseguire in quel momento e non un qualsiasi altro numero.

Un programma che prima inserisce un dato in una cella di memoria e poi successivamente passa ad interpretare il contenuto di quella stessa cella di memoria come la codifica di una istruzione (accedendovi in fase di fetch) viene detto automodificante. Un programma automodificante non é necessariamente errato, anzi nella macchina di von Neumann alcuni problemi possono essere risolti solo mediante l'uso di programmi automodificanti. Oggi nessun programmatore concepisce programmi automodificanti, anche grazie ai progressi fatti nella organizzazione delle macchine: al contrario, nei moderni sistemi di calcolo la memoria viene partizionata in aree specializzato per contenere i dati distinte da quelle destinate a contenere il codice del programma da eseguire, ed il tentativo di cambiare il contenuto delle celle contenenti il programma provoca normalmente la terminazione del programma stesso con un messaggio di errore.

Un semplice programma di Bootstrap

Fin'ora abbiamo considerato il caso in cui il programma desiderato dall'utente sia stato in qualche modo (magicamente?) inserito nella RAM sotto forma di codifica numerica delle istruzioni. Da un punto di vista pratico si pone però il problema di inserire questi numeri all'interno della RAM prima di far partire l'unità di controllo col fetch della prima istruzione. Per risolvere questo problema la macchina viene normalmente fornita dal costruttore provvista di un insieme di programmi predefiniti, comunemente indicati col termine Sistema Operativo, che hanno lo scopo di facilitare l'uso della macchina stessa fornendo all'utente delle modalità semplici di caricamento dei programmi in memoria e lancio della loro esecuzione.

In particolare nel nostro caso possiamo pensare di risolvere il problema con un semplice programma di " bootstrap "; per brevità chiamiamolo BOOT.

L'input per BOOT è una successione di numeri : a_1, a_2, a_3,...., a_n, 0 ;
a_1, a_2, a_3,...., a_n costituiscono il nostro programma, lo zero finale non è parte del programma, serve a capire che l'input è finito. Notare che 0 è, nella nostra macchina, una istruzione (somma con indirizzo 0), quindi dobbiamo supporre che i nostri programmi non la usino mai; questo è ragionevole, visto che in RAM[0] ci sarà la prima istruzione di BOOT. Complicando un poco BOOT, si può prevedere un terminatore non ambiguo, ad esempio 9999.

L'effetto di BOOT è quello di memorizzare, successivamente: a_1 in RAM[15], a_2 in RAM[16], a_3 in RAM[17],..., a_n in RAM[15+n-1] e poi di " lanciare " il programma memorizzato (eseguendo un salto a RAM[15]). La scelta di 15 non è casuale: è appena un po' sotto la fine di BOOT.

Lo schema di BOOT è semplice:

Il problema del punto SALVA si risolve notando che per salvare in RAM di 15, l'istruzione è 4015, per salvare in RAM[16], l'istruzione è 4016, cioè 4015+1 ..... Quindi la soluzione è: esegui l'istruzione 40xy e poi incrementala di 1. Quindi, BOOT modifica se stesso mentre viene eseguito. Questa soluzione però crea un problema: supponiamo che n sia 10; quando siamo arrivati a FINE, l'istruzione è diventata 4024 (= 4015 + 10 - 1). Quindi, se vogliamo riutilizzare BOOT una seconda volta (dopo aver eseguito il primo programma), il caricamento del secondo programma sarà a partire da RAM[24], e cosi' via, fino a " uscire dalla RAM ". Per risolvere questo problema, bisogna inserire, al punto FINE, prima del salto, una fase di ripristino: riportare l'istruzione di memorizzazione al valore originale: 4015.

Programma BOOT:

RAM[ 0] = 2000 (lettura: a_i viene messo in ACC)
RAM[ 1] = 7009 (se a_i è 0, salta a RAM[9])
RAM[ 2] = 4015 (l'istruzione che memorizza ....)
RAM[ 3] = 5002 (metti in ACC l'istruzione precedente)
RAM[ 4] = 7 (somma all'ACC il contenuto di RAM[7], che è 1)
RAM[ 5] = 4002 (salva l'istruzione modificata in RAM[2])
RAM[ 6] = 6000 (ricomincia da 0)
RAM[ 7] = 1 (valore 1, serve per incrementare)
RAM[ 8] = 4015 (serve per ripristinare ...)
RAM[ 9] = 5008 (per ripristinare: carica 4015 in ACC)
RAM[10] = 4002 (cosi' RAM[2] ritorna al valore iniziale 4015)
RAM[11] = 6015 (lancia il programma memorizzato)

Sottoprogrammi

Una delle tecniche classiche di programmazione é l'estensione procedurale. L'idea consiste nel definire una volta per tutte dei "sottoprogrammi" di utilità generale, che possano essere richiamati da altri programmi per portare a termine parte del compito prefisso. L'esigenza di questi sottoprogrammi nasce dalla limitatezza dell'insieme delle istruzioni base della macchina: la mancanza di una istruzione macchina capace di risolvere immediatamente un sottoproblema stimola l'introduzione di una estensione procedurale che permetta la soluzione di tale sottoproblema una volta per tutte.

Tuttavia la realizzazione di estensioni procedurali è resa difficile dalla rigidità del meccanismo di indirizzamento (solo diretto) e con l'assenza di istruzioni di chiamata e ritorno da sottoprogramma. Per risolvere entrambi i problemi occorre in questo caso sfruttare appropriatamente la possibilità di definire programmi automodificanti.

Consideriamo il caso di una istruzione di moltiplicazione tra due numeri. Tale istruzione non é presente nel repertorio della nostra macchina di Von Neumann, tuttavia possiamo individuare una sequenza di istruzioni macchina esistenti equivalente a tale istruzione mancante. Per semplicità assumiamo che uno dei due operando sia non-negativo. Per esempio, potremmo pensare di caricare una volta per tutte in memoria il sottoprogramma che segue.

L'algoritmo si basa sulla seguente definizione (induttiva) della moltiplicazione, nell'ipotesi y non-negativo:

x * y = ( se y = 0 allora 0 altrimenti [se x = 0 allora 0 altrimenti x + x*(y-1) ] )

Inoltre: supponiamo che prima di eseguire le istruzioni che seguono, x sia stato caricato in RAM[999] e y sia stato caricato nel registro ACC; alla fine della esecuzione, il sottoprogramma lscia il risultato della moltiplicazione nel registro ACC.

RAM[981] = 7995 (se ACC=0, cioe` y=0, salta alla cella 995)
RAM[982] = 4998 (memorizza y nella cella 998)
RAM[983] = 5999 (recupera x dalla cella 999)
RAM[984] = 7995 (se ACC=0, cioe` x=0, salta alla cella 995)
RAM[985] = 4997 (memorizza x nella cella 997)
RAM[986] = 5998 (recupera dalla cella 998; la 1a volta e` y, la seconda e` y-1,...)
RAM[987] = 1996 (sottrai il contenuto della cella 996, cioe` 1)
RAM[988] = 7994 (se ACC=0 salta alla cella 994)
RAM[989] = 4998 (memorizza nella cella 998)
RAM[990] = 5999 (recupera x dalla cella 999)
RAM[991] = 997 (somma il contenuto della cella 997)
RAM[992] = 4997 (memorizza nella cella 997)
RAM[993] = 6986 (salta alla cella 986)
RAM[994] = 5997 (recupera dalla cella 997)
RAM[995] = 6000+i (ritorno al programma principale: vedere oltre)
RAM[996] = 1 (costante 1)
RAM[997] = 0 (valore irrilevante, qui memorizzo risultati intermedi)
RAM[998] = 0 (valore irrilevante qui memorizzo, successivamente: y, y-1, y-2,....)
RAM[999] = 0 (valore irrilevante, sarà compito di chi usa questo sottoprogramma di mettere qui il valore del primo operando, x, vedere oltre)
Questo sottoprogramma può essere sfruttato per esempio per stampare il prodotto tra due numeri letti dal dispositivo di ingresso, mediante l'esecuzione del seguente "programma principale":
RAM[0] = 2000 (leggi operando x)
RAM[1] = 4999 (memorizza nella cella 999)
RAM[2] = 2000 (leggi operando y)
RAM[3] = 6981 (passa ad eseguire il sottoprogramma)
RAM[4] = 3000 (stampa il risultato)
RAM[5] = 8000 (ferma)
a patto che il valore contenuto nella cella 995 sia 6004 (ovvero a patto che i sia stato definito uguale all'indirizzo della cella di memoria che contiene la prima istruzione del programma principale successiva al salto verso il sottoprogramma.

Questa condizione relativa al "salto indietro" verso il programma principale può essere garantita dalla seguente variante di programma principale, che prima di attivare il sottoprogramma provvede a modificarne il codice inserendo l'indirizzo di ritorno corretto:

RAM[0] = 2000 (leggi operando x)
RAM[1] = 4999 (memorizza nella cella 999)
RAM[2] = 5006 (recupera dalla cella 6)
RAM[3] = 4995 (salva nella cella 995)
RAM[4] = 2000 (leggi operando y)
RAM[5] = 6981 (passa ad eseguire il sottoprogramma)
RAM[6] = 6007 (valore costante corrispondente a salto a cella 7)
RAM[7] = 3000 (stampa il risultato)
RAM[8] = 8000 (ferma)

Questo modo di organizzare il passaggio dal programma principale al sottoprogramma e viceversa, non é particolarmente comodo da usare, in quanto costringe a conoscere (oltre all'indirizzo della 1a istruzione del sottoprogramma) anche l'indirizzo della cella di memoria dove inserire il valore x e l'indirizzo della cella contenente l'istruzione di ritorno del sottoprogramma (995, nel nostro caso). Tuttavia permette di richiamare lo stesso sottoprogramma da punti diversi del programma principale, consentendo comunque il ritorno alla esecuzione della istruzione seguente del programma principale.

Esempio: stampa del cubo di un numero letto dall'ingresso

RAM[0] = 2000 (leggi operando)
RAM[1] = 4016 (memorizza nella cella 16)
RAM[2] = 4999 (memorizza nella cella 999)
RAM[3] = 5007 (recupera dalla cella 7)
RAM[4] = 4995 (salva nella cella 995)
RAM[5] = 5016 (recupera dalla cella 16)
RAM[6] = 6981 (passa ad eseguire il sottoprogramma)
RAM[7] = 6008 (valore costante corrispondente a salto a cella 8)
RAM[8] = 4999 (salva nella cella 999)
RAM[9] = 5013 (recupera dalla cella 13)
RAM[10] = 4995 (salva nella cella 995)
RAM[11] = 5016 (recupera dalla cella 16)
RAM[12] = 6981 (passa ad eseguire il sottoprogramma)
RAM[13] = 6014 (valore costante corrispondente a salto a cella 14)
RAM[14] = 3000 (stampa il risultato)
RAM[15] = 8000 (ferma)
RAM[16] = 0 (valore operando)

Un modo piu' elegante e pulito di organizzare la comunicazione tra programmi e sottoprogrammi 7egrave; il seguente : se la prima istruzione della procedura e` all'indirizzo K, allora in K-1 si carica l'istruzione di ritorno 6abc, in K-2 si carica il primo argomento, x, in K-3 si carica il secondo argomento, y, il sottoprogramma deposita il risultato, x*y, in K-4. Questo si generalizza in modo ovvio e sistematico al caso di p argomenti e q risultati e fornisce un protocollo standard di comunicazione tra programmi e sottoprogrammi.

Per esercizio: provare a modificare l'esempio della moltiplicazione seguendo il nuovo schema.