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):

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:

fig.2 Architetura dello Z80

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:

  1. 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;

  2. 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;

  3. IOREQ- (attivo a livello basso) : viene attivato ogni volta che deve essere compiuta un'operazione (lettura/scrittura) in un dispositivo di Input/Output;

  4. WR- (attivo a livello basso): viene attivato quando si deve compiere un'operazione di scrittura (in memoria o su un dispsoitivo di I/O);

  5. RD- (attivo a livello basso) : viene attivato quando si deve compiere un'operazione di lettura (in memoria o su un dispositivo di I/O);

  6. 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.

  7. 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:

  1.  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;

  2. viene attivato l'MREQ in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata

  3. 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;

  4. dal buffer dati il byte del codice della prima istruzione viene portato nell'Instruction Register;

  5. 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:

  1. l'indirizzo nel Program Counter viene copiato nel buffer del bus indirizzi interno e messo sul bus indirizzi esterno

  2. viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata

  3. 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;

  4. 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:

  1. 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)

  2. viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata

  3. 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;

  4. dal buffer dati interno il byte viene portato nel registro temporaneo Z con l'attivazione di segnali di controllo interni

  5. il Program Counter viene incrementato

  6. 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

  7. viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata

  8. 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;

  9.  dal buffer dati interno il byte viene portato nel registro temporaneo W con l'attivazione di segnali di controllo interni

  10. l'indirizzo completo ora nella coppia di registri WZ viene copiato nel buffer del bus indirizzi interno e messo sul bus indirizzi esterno

  11. viene attivato l'MREQ- in seguito al quale viene selezionato il chip di memoria in cui è la locazione indirizzata

  12. 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;

  13. 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.