ELEMANIA
Microprocessore - Un semplice µP inventato
Un semplice µP inventato: l'architettura esterna del microcalcolatore

Il modo più semplice per comprendere il funzionamento dei µP è quello di... inventarne uno! Consideriamo dunque un µP semplicissimo e del tutto teorico, che ci servirà come esempio per capire i meccanismi generali di esecuzione delle istruzioni e di scrittura dei programmi.

Il µP che vogliamo qui considerare ha un bus dati e un bus indirizzi entrambi composti da soli 4 bit ciascuno. In effetti un µP così semplice non ha molta utilità pratica, dal momento che è in grado di gestire uno spazio di memoria composto al massimo di 24 parole ciascuna di soli 4 bit. Una configurazione minima è quella mostrata in figura:

I nomi dei pin sono stati scelti in modo da rispecchiare le convenzioni utilizzate dal µP Z80. In particolare si osservino i seguenti segnali di controllo (tutti attivi a livello basso):

Quando il µP vuole leggere dalla memoria ROM, i segnali MREQ e RD vanno a livello basso: di conseguenza viene attivato il segnale CS (Chip Select) della ROM (l'uscita della porta OR va a zero solo quando entrambi gli ingressi sono a zero).

In modo analogo per leggere dalle unità di input output il µP porterà a zero insieme i segnali RD e IOREQ mentre per scrivere sulle unità di input output verranno attivati (portati a zero) i segnali WR e IOREQ.

 

Le unità di ingresso e uscita

Le unità di ingresso e di uscita del microcalcolatore sono quei dispositivi che consentono al microcalcolatore di comunicare con l'esterno, ricevendo e inviando dati. In un Personal Computer tali unità sono tipicamente costituite da una tastiera (input) e da un monitor (output). Nel nostro microcalcolatore molto semplice l'uscita (output) è realizzata mediante un display a 7 segmenti (comprensivo di decoder) che è in grado di visualizzare una cifra esadecimale (da 0 a F), mentre l'ingresso (input) è formato da quattro interruttori (DIP switch) mediante i quali è possibile inserire un numero a 4 bit.

Per quanto riguarda il display a 7 segmenti, si noti il registro (REG) necessario per memorizzare il numero da visualizzare quando questo non è più presente sul bus dati. Il caricamento (LD) del dato nel registro viene attivato quando il µP effettua un'operazione di scrittura sulle unità di uscita (WR e IOREQ a zero).

Il funzionamento dell'unità di ingresso (i quattro DIP switch) è leggermente più complicata. Il problema è che la velocità di esecuzione delle istruzioni da parte del µP è sicuramente molto maggiore di quella dell'utente. Pertanto, quando il µP vuole eseguire una lettura dall'unità di input, è necessario sospendere l'esecuzione per dare il tempo all'utente di digitare il numero da inserire. A tale scopo si osservi il funzionamento del flip flop T, collegato con un LED in uscita e con un pulsante sull'ingresso asincrono di Clear.

Quando il µP vuole effettuare una lettura da input, esso abbassa i due segnali RD e IOREQ e di conseguenza si ha un fronte di discesa sul clock del FF T. Siccome l'ingresso T del FF T è fisso a uno, il FF commuterà ad ogni fronte di discesa del clock. Pertanto una richiesta di lettura da input farà commutare il FF che da zero (stato iniziale) si porterà a uno. Ciò provoca l'accensione del LED per avvisare l'utente che è richiesto l'inserimento di un valore. Contemporaneamente l'uscita Q del FF si porta a zero e dunque viene inviato un segnale di WAIT al µP. Ciò corrisponde a mettere il µP  in uno stato di attesa: fintantoché il segnale di WAIT non tornerà a livello alto (1) il  µP rimarrà bloccato in tale stato.

Si noti il pulsante collegato all'ingresso Clear del FF. L'utente, dopo aver inserito il numero nei DIP swich, premerà il pulsante, il quale azzererà il FF e porterà di nuovo a 1 il segnale di WAIT  sbloccando il µP.

Notiamo infine la funzione del buffer three-state (in realtà si tratta di 4 buffer comandati da un unico ingresso di selezione): esso serve per consentire il collegamento dei DIP switch al bus dati del microcalcolatore solo quando viene effettuata un'operazione di lettura dall'input. Normalmente l'uscita del buffer è in alta impedenza, per scollegare elettricamente i DIP switch dal bus.

 

Architettura interna

Anche la struttura interna del nostro µP inventato è molto semplice. Supponiamo di avere solo tre registri (A, B e C), dove A è l'accumulatore, utilizzato normalmente come sorgente e destinazione dei risultati. Nella figura seguente per semplicità non è mostrato il bus di controllo interno al µP (cioè i segnali di controllo che vanno dalla Control Unit agli altri componenti):

Osserviamo il bus dati interno (Internal Data Bus) sul quale avvengono tutti i trasferimenti di dati all'interno del µP. Il contatore di programma (PC, Program Counter) è connesso direttamente col bus indirizzi esterno (External Address Bus) che va alla memoria. Si notino anche i segnali di controllo (bus di controllo esterno) gestiti direttamente dalla Control Unit (CU): MREQ, IOREQ, WAIT, RD, WR.

 

Set di istruzioni

Per quanto riguarda il set di istruzioni di questo µP, essendo i codici operativi composti da solo 4 bit abbiamo solo 24 istruzioni diverse (ipotizzando per semplicità di riservare una parola per ogni codice operativo). Supponiamo che le istruzioni siano le seguenti (abbiamo indicato il codice operativo in binario dell'istruzione, scelto arbitrariamente fra i 16 disponibili, e la sua traduzione in linguaggio assembly pseudo-Z80):

Codice operativo

Assembly

Spiegazione

0000

HALT

Ferma l'esecuzione

0001

LD A,n

Carica l'operando n in A

0010

LD A, B

Carica B in A

0011

LD A, C

Carica C in A

0100

LD B, A

Carica A in B

0101

LD C, A

Carica A in C

0110

ADD A, B

Somma ad A il contenuto di B e mette il risultato in A

0111

ADD A, C

Somma ad A il contenuto di C e mette il risultato in A

1000

SUB A, C

Sottrae ad A il contenuto di C e mette il risultato in A

1001

INC B

Incrementa di 1 il registro B

1010

DEC B

Decrementa di 1 il registro B

1011

JP addr

Salto incondizionato all'indirizzo addr

1100

INP A

Legge A dall'unità di ingresso

1101

OUT A

Scrive A sull'unità di uscita

1110

JP P, addr

Salta all'indirizzo addr se il risultato dell'ultima op è positivo

1111

JP M, addr

Salta all'indirizzo addr se il risultato dell'ultima op è negativo

 

Un programma per fare le moltiplicazioni

Come molti dei primi µP (fra cui anche lo Z80 di cui parleremo fra poco), anche il nostro non ha un'istruzione per effettuare il prodotto fra interi. Questo significa che occorre scrivere un programma per fare le moltiplicazioni.

Qui sotto mostriamo un semplice programma scritto nel linguaggio macchina del nostro µP che fa la moltiplicazione fra due numeri interi caricati nei registri B e C. Ogni riga della tabella rappresenta una locazione di memoria ROM (a partire dalla locazione iniziale 0000) dove si trova caricato il codice operativo di un'istruzione oppure un operando.

Poiché nel nostro set di istruzioni non esiste nessuna istruzione per effettuate l'input direttamente in B e in C, occorre prima acquisire il numero in A (INP A) e quindi trasferirlo nel rispettivo registro. Questo tipo di limitazioni è tipica della programmazione in linguaggio macchina. Sebbene nessun µP reale sia così limitato come quello del nostro esempio, resta il fatto che i linguaggi macchina sono sempre più limitati dei linguaggi ad alto livello (come per esempio il C) e dunque spesso è necessario ricorrere a vari "trucchi" per compiere operazioni anche relativamente elementari.

Indirizzo Contenuto Assembly
0000 1100 INP A
0001 0100 LD B, A
0010 1100 INP A
0011 0101 LD C, A
0100 0001 LD A,
0101 0000 0 (operando)
0110 0111 ADD A,C
0111 1010 DEC B
1000 1110 JP P,
1001 0110 6 (indirizzo)
1010 1101 OUT A
1011 0000 HALT

Per rendere ancora più semplice la comprensione dell'algoritmo, ne riportiamo qui la traduzione in linguaggio C:

    int A, B ,C;
    
    cin>> B;     // INP A e LD B, A
    cin>> C;	 // INP A e LD C, A
    A =0;        // LD A, 0
ini:
    A = A + C;   // ADD A, C
    B--;         // DEC B
    if (B>0)     // JP P, ini
       goto ini;
    cout<< A;	// OUT A 

Si osservi l'uso dell'etichetta ini con l'istruzione goto, per indicare l'indirizzo di destinazione del salto incondizionato. In linguaggio macchina l'etichetta viene sostituita dall'indirizzo della locazione di memoria a cui bisogna saltare (0110 nel nostro esempio), ma, come vedremo meglio fra poco, anche il linguaggio assembly consente l'uso di etichette al posto degli indirizzi per semplificare il lavoro del programmatore.

Un'altra cosa importante da notare è che in C le variabile (A, B e C) vengono dichiarate all'inizio del programma: si tratta infatti di nomi "astratti" che fanno riferimento a locazioni di memoria che in generale il programmatore non conosce. E' inoltre possibile dichiarare liberamente tutte le variabili di cui si ha bisogno e assegnare loro dei nomi a piacere. Programmando in linguaggio macchina invece A, B e C sono dei registri, cioè dei componenti fisici integrati dentro il µP (non è possibile aggiungere un registro a un programma, come si fa con le variabili in C!).

 

Il programma per le moltiplicazioni eseguito istruzione per istruzione

Proviamo ora ad esaminare passo passo l'esecuzione del nostro programma. Osserviamo che ogni istruzione viene prima prelevata dalla memoria (fetch del codice operativo), quindi viene decodificata (decode), quindi vengono prelevati eventuali operandi (fetch degli operandi) e infine viene eseguita (execute). Il funzionamento del µP può essere descritto in estrema sintesi come la ripetizione ciclica di: fetch codice operativo, decodifica, fetch (eventuale) operandi, esecuzione.

0) Reset

Quando il µP viene resettato (cosa che viene fatta tramite un pin di ingresso di RESET) o al momento dell'accensione, esso inizia ad eseguire l'istruzione caricata nella prima locazione della memoria (nel nostro caso 0000).

1a) Fetch della prima istruzione (INP A)

Pertanto il contatore di programma interno (Program Counter, PC) viene resettato al valore PC = 0000 e tale valore viene inviato sul bus indirizzi esterno insieme a un comando di lettura dalla memoria (RD e MREQ). Conseguentemente il CS della ROM viene abilitato e la ROM invia sul bus dati esterno il contenuto della locazione 0000, cioè nel nostro caso il valore 1100.

Dopo tale operazione, il contenuto del PC viene automaticamente incrementato di 1 e dunque diventa 0001.

1b) Decodifica della prima istruzione

Tale valore viene passato all'unità di controllo (CU, Control Unit) interna al µP, la quale provvede a decodificarlo, riconoscendo che si tratta (nel nostro esempio) di un'istruzione INP A.

1c) Esecuzione della prima istruzione

Avendo decodificato l'istruzione, la Control Unit invia all'esterno i segnali di RD e IOREQ per segnalare l'intenzione di leggere dal dispositivo di input. Conseguentemente si accende il LED che indica una richiesta di input e viene generato un segnale di WAIT che mette in attesa il µP.

Dopo che l'utente avrà inserito il numero con i DIP switch, egli premerà il pulsante che manderà spegne il LED e disabilita il segnale di WAIT sbloccando il µP.

Il valore presente sul bus dati esterno viene quindi trasferito nell'accumulatore A.

2) Fetch, decodifica ed esecuzione della seconda istruzione (LD B,A)

In modo analogo a quanto descritto precedentemente, viene prelevato dalla ROM il codice operativo della seconda istruzione (LD B,A) e questa viene eseguita, trasferendo in B il contenuto dell'accumulatore A.

3,4) Fetch, decodifica ed esecuzione della terza e quarta istruzione

La terza e la quarta istruzione del programma (INP A e LD C, A) sono perfettamente analoghe alle due precedenti. 

5) Fetch, decodifica ed esecuzione della quinta istruzione (LD A,0)

La quinta istruzione (LD A, n) differisce dalle precedenti in quanto prevede anche un operando n, cioè il valore che si deve caricare in A.

In questo caso, dopo aver prelevato dalla memoria il codice operativo dell'istruzione (0001) la Control Unit lo decodifica e riconosce che si tratta di un'istruzione di caricamento con operando.

Pertanto l'unità di controllo provvede a comandare il fetch (il prelievo) dalla memoria dell'operando. Ciò avviene esattamente come il prelievo del codice operativo visto prima: il PC manda il suo contenuto sul bus indirizzi (dopodiché si incrementa come sempre) e viene inviato un nuovo comando di lettura in memoria. L'operando 0000 contenuto nella locazione 01001 viene dunque caricato nel µP e quindi nell'accumulatore A.

6) Prelievo, decodifica ed esecuzione della sesta istruzione (ADD A,C)

A questo punto il PC contiene l'indirizzo 0110 della sesta istruzione ADD A, C. Questa istruzione viene dunque caricata dalla memoria, decodificata ed eseguita. Si tratta di una istruzione senza operandi: contenuto di A viene aggiunto il contenuto di C.

7) Prelievo, decodifica ed esecuzione della settima istruzione (DEC B)

Anche questa istruzione non ha operandi e prevede il decremento del contenuto del registro B. Si noti che nello schema interno del µP il registro B è collegato direttamente con un piccolo circuito di incremento/decremento. In pratica B viene decrementato senza dover passare attraverso la ALU e il registro accumulatore A.

8) Prelievo, decodifica ed esecuzione della ottava istruzione (JP P)

La ottava istruzione è un'istruzione di salto condizionato. Essa prevede il salto all'indirizzo specificato come operando solo se il risultato dell'ultima operazione logico/aritmetica è positivo. All'interno del µP il registro dei flag (Flag Register) tiene memoria dello stato dell'ultima operazione eseguita (zero, non zero, positivo, negativo). Nel nostro caso l'ultima operazione aritmetica eseguita è DEC B e dunque il salto verrà eseguito se il risultato di DEC B è positivo (cioè in pratica se B, dopo il decremento, è positivo).

Si noti che questa operazione prevede che l'operando (0110 nel nostro caso) venga caricato nel PC (il cui precedente contenuto viene dunque perso) e che dunque costituisca l'indirizzo in memoria da cui il programma deve continuare (nel caso ovviamente che la condizione del salto sia verificata, cioè B sia positivo).

In pratica finché B non diventa zero (a forza di essere decrementato), l'istruzione JP continua a saltare alla locazione di memoria contenente l'istruzione ADD A,C. In questo modo viene realizzato un ciclo (in linguaggio macchina non esiste un'istruzione apposita, come il while, per realizzare i cicli e questi devono essere fatti utilizzando le istruzioni di salto, come nel nostro esempio.

9) Prelievo, decodifica ed esecuzione della nona istruzione (OUT A)

Questa è un'istruzione di output. Pertanto il µP metterà il contenuto di A sul bus dati esterno e attiverà i segnali di scrittura sull'unità di output (WR e IOREQ). Conseguentemente il valore verrà caricato sul registro associato al display e visualizzato su quest'ultimo.

10) Prelievo, decodifica ed esecuzione dell'ultima istruzione (HALT)

Quando B diventa zero, la condizione per il salto diventa falsa e dunque il salto non viene eseguito. In pratica viene scartato l'operando della JP P e l'esecuzione procede con l'istruzione contenuta all'indirizzo 1011, cioè la HALT.

Questa istruzione, come suggerisce il nome, ferma l'esecuzione del µP (che, si dice, entra in "stato di halt"). Per "risvegliare" il µP dallo stato di halt occorre fornirgli un segnale di RESET dall'esterno.

 

Un programma per fare le divisioni

Come ulteriore semplice esempio si consideri il seguente programma per fare la divisione fra due numeri interi (es. 12/3). Si noti che i due numeri (dividendo e divisore) devono essere inseriti in ordine inverso: prima il divisore in C (3 nel nostro esempio) e poi il dividendo in A (12).

Indirizzo Contenuto Assembly
0000 0001 LD A,
0001 0000 0
0010 0100 LD B, A
0011 1100 INP A
0100 0101 LD C, A
0101 1100 INP A
0110 1000 SUB A,C
0111 1111 JP M
1000 1100 12 (indirizzo)
1001 1001 INC B
1010 1011 JP
1011 0110 6 (indirizzo)
1100 0100 LD A, B
1101 1010 OUT A
1110 0000 HALT

Qui c'è la traduzione in C:

    int A, B ,C;
 
    A = 0;	 // LD A, 0
    B = A; 	 // LD B, A
    cin>> C;     // INP A e LD C,A 
    cin>> A;	 // INP A
ini:
    A = A - C;   // SUB A, C
    if (A<0)	 // JP M, fine
       goto fine;

    B++;         // INC B
    goto ini;    // JP ini
       
fine:
    A = B;       // LD A, B
    cout<<A ;    // OUT A

Alla fine dell'esecuzione, B contiene il risultato della divisione. Si noti che per poter visualizzare il risultato sul display occorre trasferire prima B in A.

Si noti la differenza fra il salto condizionato JP M (M sta per negativo), che avviene solo il risultato dell'ultima operazione eseguita è negativo, e il salto incondizionato JP: quest'ultimo, come suggerisce il nome, avviene sempre in ogni caso e non dipende da nessuna condizione.

 

precedente - successiva

Sito realizzato in base al template offerto da

http://www.graphixmania.it