Nel
primo articolo della
serie "tecnologie dei processori", abbiamo visto il
significato di un approccio RISC contro un approccio CISC alla
realizzazione di un microprocessore. Abbiamo anche scoperto
che i moderni processori per PC (i vari Pentium e Athlon, per
capirci) sono dei processori post-CISC , nel senso che accettano
il vecchio codice ISA x86 , contorto e disomogeneo e lo atomizzano
in una lunga sequenza di istruzioni RISC-like, compatte, omogenee
che possono quindi essere immagazzinate localmente prima di
essere mandate in esecuzione effettiva.
Alcuni lettori mi hanno scritto chiedendomi delucidazioni
sul significato di ISA (Instruction Set Architecture): poichè
parleremo spesso di ISA negli articoli a venire, penso sia il
caso di spendere qualche altra parola su di esso e per farvi
"toccare con mano" la sostanza dell'argomento procederemo
in un modo molto particolare: ripercorreremo in piccolo i passi
che portano alla definizione dell'architettura interna di un
micro-processore.
Quanto
andrete a leggere penso non si trovi praticamente in nessun
altro sito di divulgazione per amanti dell’hardware. Se comunque
alcuni fra di voi, navigando per siti, scovassero dei link non
universitari che trattino quanto sto per dire, fatemelo sapere.
Purtroppo
la materia è "tosta", e se dovessero comparire
qua e là delle lacune, vi esorto a scrivermi per farmelo
sapere. Non è facile rendere "per tutti" (o
quasi) le materie che abbiamo deciso di trattare su Lithium.
Allora
, cominciamo....
Nascita
di un ISA
Vi
ricordo che il nostro pianeta non è Intel-like, per cui
esistono una moltitudine di famiglie di processori creati per
gli scopi più diversi (Alpha, Ultra-Sparc, per fare due
nomi, ma anche il PowerPc che equipaggia i Macintosh) e che
sono internamente assai diversi dai processori di cui siamo
abituati a parlare. Oggi giorno si trovano processori o comunque
unità programmabili (come i micro-controllori o i DSP)
praticamente ovunque: televisioni, telefonini, PDA, HardDisk,
modem, etc... Questi dispositivi vengono creati con funzionalità
specifiche per determinati impieghi (es: i DSP che sono specializzati
per l'esecuzione di calcoli) oppure vengono creati con una impostazione
generica (nel qual caso si parla di processori General Purpose)
di modo da poterli usare nelle situazioni più disparate.
Mi
sveglio una mattina e mi viene voglia di progettare un semplice
microprocessore general-purpose. La prima domanda che mi pongo
è: a che può servire il processore? quali operazioni
deve essere in grado di eseguire? In termini più precisi,
mi sto ponendo il problema di identificare il suo target di
utilizzo, sulla base di alcune specifiche che qualcun altro
magari mi avrà già dato.
Dall’esame
delle specifiche, scopro che, bene o male, esistono tipicamente
4 categorie di operazioni che il mio generico processore è
tenuto a compiere e che riassumo nella tabella sottostante:
|
Tipo
di operazione
|
Esempi
|
|
1)
ALU (op. matematiche)
|
Somma
due interi, sottrai due interi,somma un intero ad un costante...
|
|
2)
TRF (trasferimento)
|
Sposta
il valore di una costante in un registro.
|
|
3)
MEM (operazioni di memoria)
|
leggi
un dato dalla memoria e copialo in un registro, scrivi
in memoria il valore di un registro.
|
|
4)
BRN (oprazioni si salto)
|
Salta
alla istruzione tot se il registro tal dei tali è
0, oppure non saltare se il registro è diverso
da zero.
|
Qualche
commento è d’obbligo: che si intende per costante?
Beh, i programmatori sanno che una variabile è
qualcosa di modificabile durante il programma, mentre una costante
viene definita all’inizio del programma e poi resta sempre quella
(rimane "costante"). Qua
il discorso è analogo, solo che mentre associo alla variabile
il concetto di registo, cioè un posto all’interno del
microprocessore dove è immagazzinata informazione che
cambia di continuo, all costante associo il conectto di registro
cablato , cioè un registro "fittizio",
perchè per esempio contiene, cablato in hardware, uno
specifico valore immodificabile, per esempio uno 0, oppure un
1.
L’operazione
di branch va chiarita; per branch si intende un salto, cioè
una eccezione rispetto al normale flusso del codice. Un
esempio è fornito da questo stralcio di codice assembler,
in cui i numeri cardinali indicano la posizione delle istruzioni
all’interno del listato complessivo :
........
| |
102)
Mov r1,#0 |
sposta nel registro r0 il valore immediato(cioè cablato
da qualche parte
nel processore) |
| Lab1: |
103) Add r3,r2,r1 |
etichetta(label)1:somma
r1 ed r2 e scrivi il risultato in r3 |
| |
104)
Add r8,r7,r3 |
|
| |
105)
Load r10,(r8) |
carica
nel registro r10, il contenuto della cella di memoria il
cui indirizzo
fisico è contenuto in r8 |
| |
106)
Sub r11,r9,r10 |
sottrai
ad r9 il contenuto di r10 e scrivi il risultato in r11 |
| BRGT:
|
107) Lab1,r11,r4 |
salta
a lab1 se r11 è maggiore di r4, altrimenti prosegui |
| |
108)
Add r4,r5,r6 |
|
........
Calma!
Non importa cosa faccia questo programma, anche perchè
me lo sono inventato di sana pianta, il punto importante è
chiarire il significato di salto, che in
questo caso è un salto condizionato: Alla
linea 107 occorre aspettare che sia noto il valora di r11 per
sapere se si può andare avanti o rieseguire il codice
a partire da Lab1 (riga 103). In altri termini, se r11 contiene
un intero superiore a r4, si rieseguono tutte le istruzzioni
a partire dalla somma di r2 ed r1 (riga 103), altrimenti si
passa alla istruzione successiva, che è la somma di r5
ed r6 (riga 108).
Chiaramente
il valore di r11 è noto solo in tempo di esecuzione,
il che in qualche modo penalizza le prestazioni: non posso eseguire
altre istruzioni finchè non conosco il risultato...questo
concetto tornerà utile più avanti.
Un
salto può essere anche incondizionato, cioè
ad un certo punto del listato compare la dicitura "salta
qua e basta!".I salti incondizionati non creano problemi
nell’esecuzione del codice perchè è noto a priori
quando e dove il salto avrà luogo.
Dal
listato si vede pure cosa è un valore immediato,
cioè il registro cablato di cui parlavamo prima (riga
102).