Filosofia software 2
(ovvero "nelle buche ci sono solo sassolini")
Un'altra perplessità di chi si avvicina al mondo del software a basso livello, cioè molto vicino all'hardware, nasce dal doversi rappresentare cosa siano e come funzionino a livello fisico le istruzioni e i dati.

Cominciamo dal principio: una piccola buca vuota nella sabbia. Non è nient'altro che questo, solo una buca. Io però posso decidere di metterci dentro un sassolino durante i giorni pari, e toglierlo durante quelli dispari.

Dal punto di vista fisico non è nient'altro che una buca con o senza sassolino. Se però ci si mette tutti daccordo nel ritenere la presenza del sassolino come l'indicazione che è un giorno pari, allora si può dire che la buca "contiene" quell'informazione. Quella buca diventa una memoria leggibile, e tutti possono sapere se è un giorno pari o dispari. Tutti possono dire "li c'è scritto che è un giorno pari".

E'evidente che dire che un sassolino in una buca indica i giorni pari è una convenzione, è una rappresentazione di un'informazione che viene in qualche modo codificata a livello fisico.

Prendiamo adesso 8 buche. Sono solo 8 buche, e possono essere piene o vuote. Tra tutte vuote e tutte piene possiamo avere in totale 256 combinazioni, e a ciascuna di queste combinazioni possiamo attribuire un significato ben preciso di nostra scelta. Le prime tre buche piene potrebbero ad esempio voler dire che sul Monte Bianco nevica, le ultime due piene potrebbero voler dire che gli spaghetti sono pronti, e tutto il resto che ci può venire in mente.

Il punto sta proprio qui, l'attribuzione di un significato a un certo stato fisico, o combinazione di stati fisici, è solo una convenzione mentale su cui ci si deve mettere daccordo.

I numeri stessi sono solo delle convenzioni della nostra mente, non esiste un oggetto "numero" in natura, come fisicamente non esiste un oggetto "la pasta è pronta". Però possiamo decidere di "codificare" i nostri significati interiori con una forma fisica convenzionale, che deve essere considerata uguale da tutti.

Il valore numerico ventisei lo possiamo scrivere nella forma usuale 26, oppure nel formato esadecimale 1A, o ancora nel formato binario 00011010. La scelta di un formato o dell'altro dipende da noi e dalla nostra convenzione, resta il fatto che 26, 1A, 00011010 o XXVI sono tutti modi diversi per rappresentare graficamente il concetto di "ventisei".

Il formato binario è particolarmente adatto ad essere usato nel sistema a buche, basta mettere un sassolino per rappresentare gli 1 e lasciare la buca vuota per rappresentare gli 0. Ecco quindi che se siamo tutti daccordo su questa convenzione, possiamo dire che quelle 8 buche contengono un numero compreso tra 0 e 255.

Convenzionalmente si è deciso che le singole cifre di un numero espresso in formato binario si chiamano bit, e che 8 bit messi assieme sono un byte. Quindi quelle buche possono essere definite come un byte, e si può dire che in quel byte c'è un numero.... eppure ci sono solo delle buche....

Se invece 8 di buche nella sabbia si creano 8 flip flop su un chip di silicio il discorso non cambia, invece di sassolini verranno riempiti di cariche elettriche, ma la struttura "rappresentazionale" rimane la stessa. Possiamo dire che abbiamo realizzato una memoria da un byte, e che questa memoria può contenere un numero. Possiamo dirlo e a tutti gli effetti pratici considerarlo vero. Ma resta il fatto che li dentro non c'è nessun numero, ci sono una serie di stati fisici che per noi rappresenta un numero.

Anzi, è ancora più vero che siamo stati noi a costruire quell'insieme di stai fisici per poter rappresentare fisicamente il nostro concetto mentale. Ma questa rappresentazione passa attraverso delle convenzioni comuni che devono essere comprese e accettate da tutti, altrimenti ciascuno potrebbe rappresentare lo stesso numero in un modo diverso, il che non lo renderebbe diverso dal non averlo rappresentato affatto.

Qualcuno si chiede: La codifica delle istruzioni visibile sulla documentazione è scritta in esadecimale, allora il linguaggio macchina è esadecimale? In memoria devo caricare numeri esadecimali o binari?

La domanda non ha senso! Decimale, binario, ottale, esadecimale e tutte le altre basi che si vogliono usare sono anch'esse solo rappresentazioni convenzionali usate da noi. Non ci sono neppure i numeri, figuriamoci se possiamo dire che sono esadecimali. In memoria ci sono solo sassolini... pardon, cariche elettriche. Il fatto che la rappresentazione binaria coincida 1 a 1 con la presenza o assenza dei sassolini in un byte di buche ci autorizza a dire che in memoria sono rappresentate delle informazioni codificate in formato binario. L'esadecimale è solo una rappresentazione su carta per "noi umani". Resta comunque il fatto che in natura non c'è un oggetto "formato binario" come non c'è un oggetto "numero", anche questo è un significato che noi diamo a una certa struttura... o costruiamo questa struttura affinchè rappresenti il nostro significato, la sostanza non cambia.

Comunque, se ci può essere comodo, possiamo pure pensare che in memoria ci sono dei numeri esadecimali, così da lavorare in modo più semplice. I valori da 0 a 255 vengono rappresentati da due digit esa da 00 a FF, i valori rappresentabili da due byte (0..65535) vengono rappresentati da 4 digit esa 0000..FFFF. Ma è importante comprendere che la memoria non contiene queste FFFF, contiene la rappresentazione elettrica convenzionale di un valore numerico che noi possiamo per comodità rappresentare su carta o a video come FFFF.

Fatte le premesse precedenti, accettiamo da questo punto in poi di dire che in un byte può essere immagazzinato un numero e facciamo un passo avanti. Sappiamo che nella memoria di un PC possiamo memorizzare anche dei testi, delle lettere e delle frasi... di cosa sono fatte queste lettere se in un byte possiamo immagazzinare solo un numero?

Questo è un passo successivo di astrazione. Adesso che abbiamo deciso che dei sassolini ci rappresentano un valore (fino al punto da poter dire che in quelle buche c'è un numero) possiamo anche dire che un numero ci rappresenta una lettera, e questa associazione è un'altra convenzione su cui si deve essere tutti daccordo. Il codice ASCII stabilisce ad esempio che la lettera A maiuscola è rappresentata dal numero 65.

Possiamo quindi scavare tanti gruppi di 8 buche, riempirle opportunamente di sassolini, e convenzionalmente possiamo affermare che li c'è scritta una frase di significato importantissimo composta da tante lettere... eppure ci sono solo delle buche...

Questo significa che non si possono memorizzare direttamente numeri o lettere o significati. Si possono invece memorizzare stati fisici che "rappresentano" numeri, lettere e significati. Un byte dal punto di vista informativo potrà convenzionalmente contenere quello che vogliamo, anche se dal punto di vista fisico tutti i byte sono uguali, sono solo buche e sassolini.

Ed ecco che, percorsa la parabola discendente, possiamo cominciare a vedere "di cosa sono fatte" le istruzioni della CPU per poi, percorrendo la parabola ascendente, tornare al nostro mondo di istruzioni, numeri e significati, fino ad arrivare al culmine con il "processo", l'idea del programmatore che diventa funzionante.

Le istruzioni, proprio come i numeri, non hanno un'esistenza fisica propria e sono dei concetti astratti. Possiamo però "rappresentarle" con sequenze di bit stabiliti convenzionalmente.

Per fare un esempio possiamo pensare a un robottino che può muoversi nelle 4 direzioni. Noi possiamo impartire le istruzioni "avanti" "indietro" "destra" "sinistra" e "fermo", e queste sono il set di istruzioni che il robottino deve "conoscere" ed eseguire.

Ora, visto che non c'è un oggetto "avanti" dobbiamo rappresentarlo in una forma convenzionale. Abbiamo 5 istruzioni in tutto e decidiamo di usare tre buche, chiamate A B ed M, dove A e B "conterranno l'informazione" sulla direzione e M "il comando" di muoversi in quella direzione. Possiamo stabilire che A=0 B=0 significhi "direzione avanti", A=0 B=1 "direzione indietro", A=1 B=0 "direzione a sinistra" e A=1 B=1 "direzione a destra", con in più il comando di muoversi (M=1) o stare fermo (M=0).

A questo punto progettiamo la rete logica del robottino per farlo muovere nelle direzioni volute quando riceve questi codici, e a tutti gli effetti possiamo dire che ora il robottino "conosce" queste istruzioni, o che queste sono il suo "set di istruzioni".

Passiamo al nostro microprocessore: quella che per noi è l'istruzione "carica nel registro HL il valore venticinquemila", la rappresentiamo graficamente su carta con il formato mnemonico "LD HL,25000", e fisicamente in memoria con il numero binario "001000011010100001100001" (che per comodità possiamo scrivere su carta come 21A861 esadecimale).

La rete logica interna del processore è stata costruita per reagire a questa sequenza di bit realizzando proprio la funzione da essa rappresentata. In questi termini si può dire che lo Z80 "conosce" questa istruzione e la "esegue", ma è più corretto dire che è costruito per reagire correttamente alla rappresentazione convenzionale di questa istruzione.

A questo punto le cose diventano chiare, i progettisti hanno scelto di rappresentare un certo insieme di funzioni sotto forma di sequenze di bit ben precise. Poi hanno progettato le reti logiche in grado di "elaborarle" nel modo voluto, e il risultato è un circuito che "sembra conoscere" un certo insieme di istruzioni ed è in grado di "eseguirle".

In realtà la cosa è molto meccanica, il processore non contiene le istruzioni, la conoscenza sembra esserci perchè è stato costruito per reagire nel modo voluto a un certo insieme di sequenze di bit che per noi rappresentano queste istruzioni.

Per questo mi piace definire un processore uno stupido "tritabyte": lo alimenti, i flip flop interni si resettano, il registro degli indirizzi di programma (PC) parte da 0, questo valore viene inviato sul BUS indirizzi e vengono attivati i segnali di lettura da memoria. Dalla memoria arrivano i primi 8 bit e la rete logica interna reagisce in uno degli oltre 700 modi previsti "eseguendo l'istruzione", il PC incrementa, la memoria viene letta di nuovo e avanti di seguito.

Il risultato pratico di tutto questo è che il processore dispone di un set di istruzioni, o per meglio dire le mette a disposizione del programmatore per essere "pilotato". E' capace di eseguire un certo gruppo di comandi, e sta al programmatore scriverli nella sequenza opportuna per realizzare il processo voluto.

Tutte queste "istruzioni per noi", "buche con sassolini per lui", "eseguite" una dopo l'altra, portano avanti un processo di controllo sensato che realizza una funzione di livello ancora più astratto rispetto alle singole istruzioni. Proprio come il senso astratto di una frase che è rappresentata convenzionalmente con dei numeri che a loro volta sono rappresentati convenzionalmente con dei sassolini.

Detto tutto questo, e quindi accettando per semplicità che celle di memoria e registri contengono valori espressi in formato binario, è possibile capire cosa si intenda con "formato dei dati". E'cioè un modo strutturato convenzionale di considerare il significato dei bit in memoria o in un registro della CPU.

Fermo restando che il formato a livello fisico è per forza di cose binario, e che i registri lavorano con valori a 8 o 16 bit, il significato che può essere attribuito a un gruppo di bit è anch'esso una convenzione. Ad esempio la presenza del formato binario "01000001" in una cella di memoria può essere intesa a seconda del contesto come valore numerico "sessantacinque", o come lettera A, come istruzione LD B,C o come valore "quarantuno" espresso in BCD.

In altre parole sta a noi decidere di volta in volta cosa rappresentano queste 8 buche, possiamo decidere che vanno prese tutte assieme e allora contengono un numero da 0 a 255. Oppure possiamo considerarne separatemente i singoli bit, come nel caso del registro dei flag F, che va visto come 8 buche isolate di cui non ha senso considerarne il valore complessivo inteso come byte. Così possiamo decidere se gli 8 bit sono la rappresentazione di un numero positivo o negativo in complemento a 2... per il processore non fa alcuna differenza, sono solo 8 buche, e le può trattare in uno dei 700 modi previsti, sta a noi segliere cosa fargli fare per raggiungere il nostro scopo.

Le stesse istruzioni del programma in memoria sono del tutto indistinguibili dai dati, sono solo gruppi di bit. E questo perchè "dati" "valori" "caratteri" "istruzioni" "flag" sono tutti concetti astratti dipendenti dal contesto e dall'interpretazione del programmatore.

Si sa che all'accensione lo Z80 cerca la prima istruzione all'indirizzo 0 della memoria, allora si farà in modo di scrivere l'inizio del programma proprio li, e quei byte conterranno istruzioni. Un'altra area di memoria può invece contenere dei valori di lavoro letti da delle porte di ingresso, o risultati di calcoli aritmetici, questi valori allora sono dei dati gestiti dal programma.

Alla fine si devono in realtà ricordare pochi indirizzi: quello di partenza del programma che è 0, la partenza dei gestori di interrupt INT (in IM 1) e NMI che sono a 38H e 66H, l'inizio e la fine della ROM e della RAM, e dove si è deciso di allocare lo stack. Dopo di che tutte le altre celle sono usabili a discrezione per memorizzare dei byte che rappresentano quello che vogliamo, nell'ordine che vogliamo, strutturati come vogliamo... con l'unico vincolo che possono essere manipolati solo con il set di istruzioni disponibile.

Dal punto di vista del programmatore lo spazio di memoria RAM e le porte di I/O sono le cose su cui agire, le istruzioni sono gli strumenti elementari per agire, il processore è l'esecutore preciso e stupido delle istruzioni.... e le esegue perchè è strutturalmente costruito per andarsele a cercare ed eseguirle.

Chi è il vero soggetto dell'azione? Il processore che con la sua attività crea il processo e realizza fisicamente le operazioni, o il processo che esegue la funzione prevista dal programmatore comandando il processore?


Pagina e disegni realizzati da Claudio Fin
Ultimo aggiornamento 23-9-2000