Introduzione ai Microprocessori
Il primo microprocessore della storia nasce nel 1971, è prodotto dalla INTEL ed è il risultato di un progetto a cui partecipò l'italiano F.Faggin insieme a M.Hoff e S.Mazer. I primi processori (più specificatamente microprocessori o CPU, Central Processing Unit) erano circuiti integrati al cui interno si potevano distinguere, in base alle loro funzioni essenziali, le seguenti parti (vedi fig.1):
alcuni registri per la memorizzazione temporanea dei dati da elaborare e dei risultati;
Verso l'esterno il processore presenta, dal punto di vista logico, 3 bus dedicati: 1) bus dati, alla trasmissione dei dati 2) bus indirizzi, alla trasmissione degli indirizzi 3) bus di controllo che prevede un certo numero di segnali necessari al colloquio con le parti esterne, memorie e dispositivi di input/output.
Il microprocessore Z80
L'analisi di uno dei primi processori di largo utilizzo può servire a comprendere, almeno parzialmente, anche le complesse architetture dei processori odierni ed il loro modo di operare. Il processore scelto per questa analisi è lo Z80, prodotto alla fine degli anni '70 dalla Zilog, casa fondata da quel F.Faggin che aveva dato il suo importante contributo all'ideazione del primo microprocessore. In diretta competizione con l'8085 di Intel, lo Z80 ebbe una larga diffusione, essendo uno dei primi processori utilizzati per la realizzazione dei primi computer casalinghi (home computer) fra i quali ricordo l'antesignano di tutti lo ZX80 della Sinclair. Anche quando la sua architettura fu superata dai successivi processori di Intel, lo Z80 rimase un utilizzatissimo processore per la costruzione di schede di controllo ed apparati a microprocessore, come ad esempio schede di controllo del movimento degli ascensori e antifurti. La scelta è comunque dettata dalla sua semplice architettura che rispecchia l'elencazione dei componenti fatta e dall'altrettanto semplice linguaggio assembly con cui si poteva programmare.
Guardando la figura 1 possiamo individuare:
il gruppo di registri a 8 bit per i dati indicati come B, C, D, E, H, L, a cui dobbiamo anche affiancare il registro A, detto Accumulatore, che aveva un ruolo speciale in tutte le operazioni aritmetiche-logiche; questo insieme di registri risulta duplicato in altrettanti registri B', C', D', E', H', L', A', cosiddetti alternativi, che potevano essere utilizzati in particolari circostanze;
fig.2 Architetura dello Z80
la Control Unit è la sezione sulla sinistra, racchiusa in un rettangolo tratteggiato ed indicata come Control Section, in cui si distinguono: 1) un Instruction Register, dove viene temporaneamente memorizzato il codice binario dell'istruzione da eseguire 2) un Instruction Decoder che ha il compito di "comprendere" ciò che l'istruzione deve fare 3) una Control Logic che genera la serie di segnali, detti di controllo, diretti sia verso l'interno della CPU che verso l'esterno, che mettono in atto l'istruzione decodificata;
I segnali dello Z80
Per comprendere bene il funzionamento di un processore, occorre affiancare all'analisi della sua struttura anche quella dei segnali che lo collegano all'esterno alle altre parti di un sistema a microprocessore, che lo ricordiamo, sono essenzialmente memorie e dispositivi di I/O. Analizzando il pinout (schema dei piedini dell'integrato) dello Z80 è possibile avere una visuale completa dei segnali e delle linee utilizzate. Guardando la fig.2 si notano:
RESET- (attivo a livello basso): mette il processore nelle condizioni di partenza, inizializzando il Program Counter con l'indirizzo della cella di memoria dove è memorizzata la prima istruzione del programma di avvio;
MREQ- (attivo a livello basso) : viene attivato ogni volta che deve essere compiuta un'operazione (lettura/scrittura) in memoria; è il segnale che in pratica determina l'attivazione del Chip Select delle memorie;
IOREQ- (attivo a livello basso) : viene attivato ogni volta che deve essere compiuta un'operazione (lettura/scrittura) in un dispositivo di Input/Output;
WR- (attivo a livello basso): viene attivato quando si deve compiere un'operazione di scrittura (in memoria o su un dispsoitivo di I/O);
RD- (attivo a livello basso) : viene attivato quando si deve compiere un'operazione di lettura (in memoria o su un dispositivo di I/O);
INT- (attivo a livello basso): è il segnale di Interupt (Interruzione); questo segnale viene utilizzato in genere dalle periferiche quando richiedono attenzione da parte della CPU, ad esempio dalla tastiera quando viene premuto un tasto il cui codice deve essere di conseguenza letto dalla CPU. L'Interupt causa un abbondono temporaneo del programma in corso per passare all'esecuzione di una cosiddetta "routine di servizio" che espleta quanto richiesto dal dispositivo che ha interrotto; terminata la routine di servizio la CPU riprende il programma sospeso dal punto in cui era stato lasciato. La tecnica dell'interruzione è pesantemente utilizzata dal Sistema Operativo, anche se noi non ce ne accorgiamo, perchè ogni servizio richiede in genere un tempo brevissimo. La richiesta di Interrupt può essere comunque ignorata momentaneamente e messa in attesa, quando più dispositivi richiedono contemporaneamente attenzione.
NMI- (attivo a livello basso): è il segnale che attiva un altro tipo di interruzione, detta Non Mascherabile, in quanto a differenza della precedeente non può essere ignorata e messa in attesa, deve essere sempre accolta subito dalla CPU.
Esecuzione di una istruzione
Ora che abbiamo un'idea della struttura interna del processore e dei suoi segnali, possiamo vedere in che modo viene eseguita una istruzione. Avviene in due fasi la prima è detta fase di Fetch e consiste nel reperimento del codice dell'istruzione da eseguire in memoria e nel suo trasporto nell'Instruction Register; a questo punto inizia la seconda fase, detta di Execute, in cui l'istruzione viene decodificata ed portata a termine. Vediamo in dettaglio mediante quali passi avviene la fase di fetch. Supponiamo di partire dal Reset del calcolatore ovvero dall'avvio; in questo caso il Program Counter viene inizializzato con l'indirizzo () di una particolare locazione di memoria. Con una scansione dei tempi determinata dal segnale di clock in sostanza vengono compiute queste operazioni:
il contenuto del Program Counter, ovvero l'indirizzo in cui leggere la prima istruzione, viene inviato al buffer del bus indirizzi interno in modo tale che sia presente sulle linee di indirizzo esterne;
viene attivato l'MREQ in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata
viene attivato il RD e dopo qualche ciclo di clock il byte della locazione indirizzata viene immesso dalla memoria sul bus dati esterno e di qui portato nel buffer del bus dati interno;
dal buffer dati il byte del codice della prima istruzione viene portato nell'Instruction Register;
subito dopo il Program Counter viene incrementato (+1) in modo da contenere già l'indirizzo della locazione di memoria successiva alla prima in cui prosegue (ad eccezione in cui siano previsti salti di esecuzione), in codice binario, il programma che deve essere eseguito.
Qui termina la fase di fetch, che come si vede, richiede l'attivazione di alcuni segnali di controllo e l'intervento di alcuni registri della CPU, nonchè dei vari bus.
I passi che deve fare la CPU per portare a termine l'istruzione durante la fase di execute dipendono fortemente dal tipo di istruzione da eseguire. Riportiamo ad esempio nella seguente tabella tre istruzioni in linguaggio assembly dello Z80, con la loro traduzione in codice binario ed il loro significato.
Assembly |
Cod. binario |
Tipo di byte |
Significato |
|
1 |
LD B, A |
01001000 |
Cod.operativo |
Carica (scrivi, copia) in nel registro B il byte contenuto nel registro A |
2 |
LD D,43 |
00010110 01000011 |
Cod.operativo Byte di dato |
Carica nel registro D il byte 43 |
3 |
LD A,(0210) |
00111110 00010000 00000010 |
Cod.operativo Byte di dato Byte di dato |
Carica nel registro A il byte memorizzato nella cella di memoria di indirizzo 0210H |
Dati ed indirizzi sono espressi in forma esadecimale. Si osserva che un'istruzione in linguaggio macchina risulta composta da uno (o due in altri casi) byte di codice operativo seguito da 1 o più byte di dato; la massima dimensione di un'istruzione Z80 è di 4 byte, di cui fino a tre di codice operativo.
La prima di queste istruzioni richiede un solo byte di codice operativo, cioè di codice binario che indica cosa deve fare l'istruzione, e pertanto una volta portato questo byte nell'Instruction Register dalla fase di fetch, la sua esecuzione richiede l'attivazione di segnali interni alla CPU, non meglio identificati, che mettano in collegamento il registro B in uscita con il registro A in entrata in modo da copiare ciò che è in B in A.
Per la seconda istruzione, a compimento della fase di fetch, il codice operativo 00010110 sarà nell'Instruction Register e la sua decodifica "avviserà" il processore che deve essere letto in memoria un byte alla locazione successiva al codice operativo per essere portato nel registro D. Program Counter, che è stato incrementato subito dopo il caricamento del codice operativo nell'Instruction Register, contiene a questo punto già l'indirizzo della locazione in cui si deve legger il byte per cui:
l'indirizzo nel Program Counter viene copiato nel buffer del bus indirizzi interno e messo sul bus indirizzi esterno
viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata
viene attivato il RD- e dopo qualche ciclo di clock il byte della locazione indirizzata viene immesso dalla memoria sul bus dati esterno e di qui portato nel buffer del bus dati interno;
dal buffer dati interno il byte viene portato nel registro D con l'attivazione di segnali di controllo interni, come ad esempio quelli che consentono al Mux di collegare il bus dati al registro.
L'esecuzione della terza istruzione è ancora più complessa. Infatti la decodifica del codice operativo 00111110 indica al processore che deve essere letto un indirizzo nelle locazioni successive a quella del codice operativo e successivamente dalla locazione di memoria con quell'indirizzo deve essere preso un byte e portato nel registro A. Pertanto la fase di execute prevede i seguenti passi:
l'indirizzo nel Program Counter viene copiato nel buffer del bus indirizzi interno e messo sul bus indirizzi esterno per l'accesso alla parte bassa dell'indirizzo a 16 bit (si noti che l'indirizzo è scritto in memoria in modo inverso a quello che è abituale nella nostra scrittura, cioè prima gli 8 bit meno significativi, poi quelli pià significativi)
viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata
viene attivato il RD- e dopo qualche ciclo di clock il byte della locazione indirizzata viene immesso dalla memoria sul bus dati esterno e di qui portato nel buffer del bus dati interno;
dal buffer dati interno il byte viene portato nel registro temporaneo Z con l'attivazione di segnali di controllo interni
il Program Counter viene incrementato
l'indirizzo nel Program Counter viene copiato nel buffer del bus indirizzi interno e messo sul bus indirizzi esterno per il secondo accesso alla parte alta dell'indirizzo
viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata
viene attivato il RD- e dopo qualche ciclo di clock il byte della locazione indirizzata viene immesso dalla memoria sul bus dati esterno e di qui portato nel buffer del bus dati interno;
dal buffer dati interno il byte viene portato nel registro temporaneo W con l'attivazione di segnali di controllo interni
l'indirizzo completo ora nella coppia di registri WZ viene copiato nel buffer del bus indirizzi interno e messo sul bus indirizzi esterno
viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata
viene attivato il RD- e dopo qualche ciclo di clock il byte della locazione indirizzata viene immesso dalla memoria sul bus dati esterno e di qui portato nel buffer del bus dati interno;
dal buffer dati interno il byte viene portato nel registro A con l'attivazione di segnali di controllo interni.
Come si vede la fase di execute può essere rapida o lunga e quanto delineato qui è comunque una visione ad alto livello che si frammenta in ulteriori passi analizzando le operazioni con maggior dettaglio. Quello che deve essere chiaro è che una volta decodificato il codice operativo dell'istruzione la Control Logic deve generare in sequenza la serie di segnali che determinano l'esecuzione dell'istruzione. Per questo modo di operare talvolta la Control Logic è indicata anche come Sequencer; in effetti come realizzazione si tratta di un particolare circuito logico sequenziale che appartiene alla categoria degli automi. Dovrebbe essere anche chiaro che, poichè tutti i passi che un processore compie sono scanditi dal segnale di clock, maggiore è il numero di passi richiesto e più grande è il tempo necessario per l'esecuzione dell'istruzione.
Alcune istruzioni dello Z80
Riportiamo nella tabella seguente alcune istruzioni in linguaggio assembly previste dal set dello Z80. Nonostante sia solo una piccola parte dell'insieme, sono indicative del modo di operare di un processore e della sostanziale semplicità di quanto possono fare; nonostante l'enorme ampliamento del set di istruzione nei processori odierni, occorre mantenere l'idea che essi sostanzialmente sappiano fare cose semplici e che è dovuto alla capacità dei programmi di sfruttare l'enorme velocità con cui le operazioni sono fatte, se si ottengono gli stupefacenti risultati che sperimentiamo.
Alcune di queste istruzioni vanno commentate, altrimenti non sarebbero chiare.
Le istruzioni di Input/Output sono quelle che permettono lo scambio dei dati fra il computer e le sue periferiche; nella situazione più semplice questo avviene attraverso un'interfaccia di tipo porta parallela; poichè il computer ha solitamente più porte parallele (o comunque dispositivi di interfaccia) ognuna di esse viene contraddistinta attraverso un indirizzo ad 8 bit.
Tipo di istruzione |
Sintassi generica |
Esempi |
Significato |
Di caricamento con indirizzamento immediato |
LD Registro, N |
LD A,08 LD C,89 LD E,12 |
Carica (copia) nel registro specificato il byte N |
Di caricamento con indirizzamento a registro |
LD Reg1,Reg2 |
LD B,A LD A,C LD E,H |
Carica (copia) nel Registro1 il byte contenuto nel Registro2 |
Di caricamento con indirizzamento diretto |
LD A,(aabb) |
LD A,(0201) LD A,(1020) |
Carica nel registro A il byte contenuto nella locazione di memoria il cui indirizzo è aabb |
Di caricamento con indirizzamento diretto |
LD (aabb), A |
LD (0230),A LD (4400),A |
Carica nella locazione di indirizzo aabb il byte contenuto nel registro A |
Aritmetiche |
ADD A, n |
ADD A,80 ADD A,23 |
Somma al contenuto del registro A il byte n e rimetti il risultato in A |
Aritmetiche |
ADD A, Registro |
ADD A,B ADD A,D |
Somma al contenuto del registro A il byte contenuto nel Registro specificato e rimetti il risultato in A |
Aritmetiche |
SUB A,n |
SUB A,08 SUB A,77 |
Sottrai al contenuto del registro A il byte n e rimetti il risultato in A |
Aritmetiche |
SUB A, Registro |
SUB A,H SUB A,E |
Sottrai al contenuto del registro A il byte contenuto nel Registro specificato e rimetti il risultato in A |
Aritmetiche |
INC R |
INC A INC L |
Aumenta di 1 il byte contenuto nel registro specificato |
Aritmetiche |
DEC R |
DEC B DEC E |
Diminuisce di 1 il byte contenuto nel registro specificato |
Di Input/Output |
IN A,(bb) |
IN A,(08) IN A,(12) |
Porta nel registro A il byte presente sulla porta di ingresso di indirizzo bb |
Di Input/Output |
OUT (bb),A |
OUT (09),A OUT (13),A |
Invia il byte contenuto nel registro A alla porta di uscita di indirizzo bb |
Di salto incondizionato |
JP aabb |
JP 0330 JP 0400 |
Prosegui l'esecuzione del programma dall'istruzione alla locazione di memoria di indirizzo aabb; in pratica carica nel Program Counter l'indirizzo aabb |
Di salto condizionato |
JR condizione,ss |
Se la condizione è vera, prosegui l'esecuzione del programma dall'istruzione alla locazione di memoria il cui indirizzo si ottiene sommando al contenuto del Program Counter lo spiazzamento ss |
|
JR Z,ss |
JR Z,06 |
Se il risultato dell'operazione precedente è stato Zero, prosegui l'esecuzione del programma dall'istruzione alla locazione di memoria il cui indirizzo si ottiene sommando al contenuto del Program Counter lo spiazzamento 06 |
|
JR NZ,ss |
JR NZ,24 |
Se il risultato dell'operazione precedente è stato diverso da Zero, prosegui l'esecuzione del programma dall'istruzione alla locazione di memoria il cui indirizzo si ottiene sommando al contenuto del Program Counter lo spiazzamento 06 |
|
JR P,ss |
JR P,12 |
Se il risultato dell'operazione precedente è stato positivo, prosegui l'esecuzione del programma dall'istruzione alla locazione di memoria il cui indirizzo si ottiene sommando al contenuto del Program Counter lo spiazzamento 12 |
|
JR N,ss |
JR N,24 |
Se il risultato dell'operazione precedente è stato negativo, prosegui l'esecuzione del programma dall'istruzione alla locazione di memoria il cui indirizzo si ottiene sommando al contenuto del Program Counter lo spiazzamento 24 |
Le istruzioni di salto sono quelle che interrompono la normale esecuzione sequenziale delle istruzioni e che realizzano a livello di codice binario le strutture di tipo scelta condizionata o ciclo iterativo. Possono essere di tipo incondizionato, nel qual caso il salto viene sicuramento eseguito o di tipo condizionato, nel qual caso il alto viene eseguito solo se si è verificata una determinata condizione, di solito in seguito ad una precedente istruzione aritmetica o logica. Il verificarsi di queste condizioni vine annotato nel Registro di stato attraverso particolari bit detti Flag (bandierine); ad esempio esiste un flag di zero Z, che viene posto a zero se il risultato di un'opeazione è nullo, il flag di segno S, che viene posto a zero se il risultato di un'operazione è positivo oppure ad 1 se è negativo. Gli altri flag, che non descriviamo, sono: flag di Carry C, flag di Overflow O, flag di Parità P, flag di Half Carry H.
L'altra osservazione da fare sulle istruzioni di salto riguarda l'indirizzo della locazione da cui proseguire il programma, che è indicato per intero nei salti incondizionati, mentre è inidcato con un cosiddetto spiazzamento, nei salti condizionati. Lo spiazzamento è la differenza fra l'indirizzo della locazione che deve essere raggiunta ed l'indirizzo contenuto nel Program Counter quando viene decodificata l'istruzione di salto. Comunque l'individuazione dello spiazzamento o dell'indirizzo di salto viene compiuta dall'Assemblatore o Assembler, che ha il compito di tradurre il listato in linguaggio macchina; come negli ambienti di sviluppo dei linguaggi evoluti, la lavorazione di un programma in linguaggio asssembly avviene passando per editor, traduttore e linker per arrivare alla creazione del file .EXE. Un listata in linguaggio Assembly prevede allo scopo l'uso di etichette per definire la destinazione di un salto, etichette che l'Assembler si incaricherà di tradurre in indirizzi di locazione. Il seguente esempio di spezzone di programma può servire a chiarire i concetti:
LD A,(0130) ; carica in A il byte contenuto nella locazione di memoria di indirizzo 0130
LD B,A ; trasferisce il byte in B
IN A,(08) ; prende un byte dalla porta di ingresso con indirizzo 08 e lo porta in A
SUB A,B ; esegue la sottrazione A-B
J NZ NONE ; se il risultato è diverso zero salta all'istruzione all'etichetta NONE
LD A,FF ; carica in A il byte FF
OUT (09),A ; lo invia sulla porta di uscita di indirizzo 09
JP PROS ; salta all'istruzione all'etichetta PROS
NONE: LD A,00 ; carica in A il byte 0
OUT(09),A ; lo invia alla porta di uscita di indirizzo 09
PROS: LD H,35 ; .....
....
Uno studente attento comprenderà che questo spezzone di programma sviluppa una struttura a scelta condizionata e precisamente: controlla se un byte prelevato dalla porta di ingresso è eguale ad un byte preso in memoria, nel caso i due byte siano eguali invia alla porta di uscita un byte FF fatto di tutti 1,
mentre se i due byte sono diversi invia alla porta di uscita un byte nullo. Quata è un piccola prova che in assembly si possono costruire tutte le strutture logiche previste dalla programmazione strutturata; tuttavia non esamineremo questo aspetto.