Sei sulla pagina 1di 16

FLAG

ADDRES BUS
ALU
ADDRESS BUS
LATCH/BUFFER

INTERNAL
REGISTERS POINTERS
BUS

DATA BUS
DATA BUS
PROGRAM COUNTER
LATCH/BUFFER

STACK POINTER

INSTRUCTION
REGISTER

CLOCK
DECODER
SEQUENCER

CONTROL BUS

Questo schema è lo schema di principio della CPU di tutti i computer. Nella storia ed in commercio
sono state fatte migliaia di varianti a questo schema, ma il principio di funzionamento è sempre rimasto
quello inventato da Neuman all’inizio degli anni 1940.

Fetch ed Execute

Il Microprocessore, o meglio la CPU lavora in due fasi fondamentali: Fetch ed Execute.


Nella fase di Fetch la CPU preleva l’istruzione da eseguire in memoria. Le istruzioni si trovano
disposte in successione in memoria, e sono stivate in memoria nello stesso ordine in cui vanno eseguite. Il
Program Counter deve contenere all’inizio dell’esecuzione l’indirizzo della prima istruzione del
programma da eseguire. Per avere prelevare l’istruzione si deve fare un’operazione di READ. L’indirizzo è
contenuto nel program counter che andrà quindi portato sull’ADDRESS BUS attraverso l’ADDRESS
BUFFER. Il dato letto in memoria è l’istruzione da eseguire e sarà oggetto delle fase di Execute.
L’istruzione letta dalla memoria viene caricata nell’ISTRUCTION REGISTER e quindi eseguita dal
DECODER SEQUENCER.
Program Counter

Il Program Counter è un vero è proprio contatore che contiene, o meglio parlando tecnicamente
“punta” l’indirizzo dell’istruzione da prelevare.
All’inizio del programma il Program Counter viene precaricato (preset) con l’indirizzo della prima
istruzione del programma
In ogni fase di Fetch il contenuto del Program Counter viene mandato sull’Address Bus mentre il
contenuto del Program Counter viene incrementato di uno, mediante un impulso che gli arriva a fine Fetch
dal Sequencer. Il Program Counter è quindi un vero e proprio contatore da cui il nome.

Address Latch/Buffer

È un latch buffer tristate unidirezionale che manda sull’ADDRESS BUS i dati che si trovano nel
bus interno della CPU.
È unidirezionale verso l’uscita, la CPU scrive gli indirizzi, non li riceve mai.
Il buffer è comunque tristate perché potrebbero esserci altre CPU o altre unità che potrebbero
mettere degli indirizzi sull’ADDRESS BUS.
Deve anche essere latch, perché deve memorizzare l’indirizzo che gli arriva dal bus interno in
modo che l’INTERNAL BUS sia disponibile per il trasferimento del dato da o verso l’esterno..

Data bidirectional Latch/Buffer

È un buffer latch bidirezionale tristate che mette in comunicazione l’INTERNAL BUS ed il


DATA BUS.
Deve essere latch per memorizzare il dato sia dal bus interno sia dal bus dei dati proveniente
dall’esterno.
Deve essere tristate perché deve essere in grado di scrivere e leggere e comunque si tratta di un
bus.
Deve essere bidirezionale perché ovviamente i dati entrano ed escono.

Instruction Register

Il Program Counter contiene l’indirizzo dell’istruzione da prelevare in memoria. Questa istruzione


una volta prelevata viene scritta nell’Instruction Register per essere decodificata ed eseguita.
La fase di Fetch appunto è il prelievo dell’istruzione da eseguire in memoria e l’immagazzinamento
nell’Instruction Register.
Fetch

1) Program Counter su Internal bus


2) Internal bus su Address Buffer latch
3) Read
4) Attesa fine Wait
5) Data bus passa in internal bus attraverso data bus buffer latch
6) Data bus latch passa nell’Instruction Register
7) Incremento del Program Counter

Come si vede per fare un Fetch occorrono 5 comandi e l’attesa di un consenso, passo 4, attesa di
fine Wait.
Ognuno di questi comandi è un comando elementare. Questi comandi sono generati dal sequencer
dopo l’esecuzione di ogni istruzione per prelevare l’istruzione successiva, in automatico. Tutti questi
comandi si chiamano microistruzioni.
Mentre il Fetch è un ciclo sempre uguale, la fase successiva dipenderà da quale istruzione si è
prelevata in memoria e va eseguita.

DECODER

inizio istruzione
flip flop flip flop flip flop flip flop flip flop

CLOCK

WAIT
Decoder Sequencer

Consideriamo i 5 comandi dell’esecuzione del FETCH di cui sopra. Occorre un dispositivo che li
generi in successione e con una certa temporizzazione. Il DECODER SEQUENCER genera questi
comandi e al passo 4 si ferma fino a che non arriva il consenso che il dato è pronto sul Data Bus dalla
memoria.
Quando l’istruzione è disponibile nell’Istruction Register deve iniziare la sequenza di comandi,
“Microistruzioni” che servono a compiere l’istruzione. Questa catena di istruzioni si trova nel Sequencer,
anzi nel Sequencer ci sono tante catene di microistruzioni quante sono le istruzioni ASSEMBLER della
CPU. Il decoder attiva la catena del Sequencer relativa all’istruzione che contiene, da cui il nome decoder.
Il Sequencer da comandi sia all’interno della CPU, sia all’esterno (read, write, riconoscimento di
interrupt, ecc.). Il Sequencer riceve consensi dall’esterno, come il WAIT, richieste come Interrupt, e
consensi dall’interno dai FLAG.
L’Istruzione può essere più o meno lunga a secondo di quanti dati contiene. Quando arriva la prima
parte di essa nell’Istruction Register, il Decoder può stabilire che se è necessario proseguire con altri Fetch
e questi vengono eseguiti in automatico prima di passare all’Execute. Ad ogni Fetch si incrementa il
Program Counter ovviamente, perché è una istruzione che occupa più parole di memoria e quindi più
numeri di Program Counter.

ADDRESS BUS
È un bus in uscita per la CPU, è la CPU che stabilisce quale indirizzo debba essere scritto o letto in
memoria. Il numero dei fili di questo bus determina la capacità massima della memoria fisica della CPU, il
numero massimo di locazioni che la CPU può avere è 2N dove N è il numero dei fili del bus. La memoria
montata con la CPU può essere inferiore alla capacità della CPU. A questo punto il programma dovrà
essere scritto per una memoria fisica ridotta.
I sistemi operativi di grandi dimensioni riconoscono in automatico la dimensione della memoria
montata con la CPU con un programma che controlla fino a quale indirizzo arrivano segnali dalla
memoria.
Se si aggiunge un filo al bus degli indirizzi si raddoppia la capacità della memoria fisica.
L’address bus può essere utilizzato anche da altre CPU o dalle periferiche stesse per accedere a
segmenti di memoria, quindi l’uscita sull’Address Bus deve essere tristate.
Prima dell’Address bus c’è un Latch per memorizzare l’indirizzo quando è disponibile sull’Internal
Bus che deve essere lasciato libero per i successivi trasferimenti.

Data Bus

Ovviamente come si capisce dal nome è il Bus da cui arrivano i dati e su cui escono i dati. Il
numero dei fili di questo bus determina l’architettura della memoria ossia da quante celle di un bit deve
essere costituita la locazione di un indirizzo di memoria. Ovviamente più sono numerosi i fili del Data Bus
e maggiore sarà la velocità di trasferimento dei dati e di lettura delle istruzioni. Oggigiorno si sta arrivando
ai limiti fisici teorici delle possibilità del clock, quindi non succede come un tempo quando ogni anno le
velocità del clock raddoppiavano o triplicavano. I margini di incremento della velocità del clock sia della
CPU che del bus sono minimi e quindi si sta già agendo aumentando il numero di fili del bus dei dati. Un
bus a 64 fili consente tempi quasi dimezzati per la fase di Fetch di tutte le istruzioni e per le istruzioni di
Move di dati.

Control Bus

Vediamo prima i comandi che vanno all’esterno della CPU e i consensi che arrivano dall’esterno
alla CPU.
Read : comando di lettura ai banchi di memoria
Write: comandi di scrittura ai banchi di memoria
Wait: di solito è un comando attivo basso, ad open collector. Quando la memoria ha finito
l’operazione di lettura o scrittura rilascia questo comando che va alto e la CPU sa che può leggere il dato
sul bus in caso di lettura, oppure in caso di scrittura sa che può continuare e fare le microistruzioni
successive.
Interrupt: le periferiche possono generare un segnale di richiesta di essere servite alla CPU. Ci
possono essere uno o più fili di interrupt. Un segnale si questo filo fa sapere alla CPU che c’è una
periferica che richiede dei dati o offre dei dati.
Interrupt aknowledge: la CPU accetta la richiesta di Interrupt della periferica quando è in
condizioni di farlo e secondo un certo ordine di priorità. Quando accetta la richiesta emette un segnale sul
Control Bus che avverte la periferica che può iniziare la procedura di Interrupt. Anche in questo caso si
possono avere uno o più fili a seconda della CPU e dei sistemi scelti.
Bus Request: in certi casi può essere molto più veloce lasciare l’accesso della memoria libero ad
una periferica che dovrà essere di tipo “intelligente” in modo che la periferica possa leggere o scrivere
direttamente sulla memoria senza passare nella CPU i dati che deve trasferire. Questo può avvenire mentre
la CPU elabora internamente dei dati, quindi si possono trasferire dati con consumo si tempo zero per la
CPU. Questa tecnica si chiama DMA, Direct Memory Access.
Bus Aknowledge: quando la CPU lascia il bus libero lo comunica sul Control Bus e la periferica sa
di avere Address Bus, Data bus, e parte del Control Bus disponibile. Per il control Bus sono i fili di Read,
Write e implicitamente di Wait. Il Wait è in ogni caso comandato dalla memoria.
Input/Output: la CPU può sempre prendere ed inviare dati direttamente dalle o alle periferiche.
Per fare questo usa sempre il Data Bus per il movimento dei dati e parte dell’Address Bus per indirizzare le
periferiche. Solo una parte perché le periferiche sono ovviamente in numero molto inferiore alle locazioni
di memoria. Il comando di Input Output comunica alle periferiche che l’indirizzo sull’Address Bus è un
indirizzo di periferica e non di locazione di memoria, e quindi la periferica indirizzata si attiva. La
memoria viene disattivata o mediante un comando Enable/Disable oppure non vengono attivati i segnali
Read/Write a seconda della CPU.

Vediamo ora i comandi e consensi del Control Bus all’interno della CPU.
Quando siamo all’interno non ci sono problemi di Wait, sia perché essendo gli spazi molto ridotti
non ci sono problemi di attesa, sia perché nel caso che ci siano tempi di attesa, come nel caso di operazioni
matematiche, essi sono noti e determinati, quindi la CPU sa già quanti colpi di clock sono necessari perché
venga determinata una operazione matematica per esempio.
I comandi all’interno sono del tipo:
Trasferisci dal registro al bus interno
Trasferisci dal bus interno al registro
Trasferisci dal latch al bus esterno (Address Bus e Data Bus)
Trasferisci dal bus esterno(Address Bus e Data Bus) al latch
Fai operazione matematica
Fai operazione logica
Condizionamento di esecuzione: è previsto che la CPU faccia o non faccia certe operazioni a
seconda dello stato di certi bit che di solito sono i Flag. Se si osserva lo schema il registro dei Flag manda
comandi sul control bus oltre che riceverne, perché i suoi Bit condizionano l’esecuzione di certe
operazioni.

Microistruzioni

Tutti i comandi che partono dal Sequencer e vanno sul Control Bus sia all’interno della CPU che
all’esterno sono microistruzioni. Ogni microistruzione è cablata ad hardware.
L’Execute di ogni istruzione è una sequenza di microistruzioni che possono essere condizionate o
meno dai Flag all’interno o dai consensi dall’esterno, il Wait è l’esempio più immediato.
Quindi il Sequencer ha N sequenze cablate tante quante sono le istruzioni Assembler della CPU.
Ognuna di queste avrà un certo numero di passi, di comandi e di condizionamenti.
Il Decoder mette in funzione la catena di microistruzioni del caso.
Ogni istruzione avrà un suo numero di passi, un passo ad ogni colpo di clock, mentre certi passi per
procedere avranno bisogno di avere consensi dall’esterno e quindi in questo caso si potranno perdere dei
colpi di clock in attesa.
Alcune istruzioni hanno delle ramificazioni e fanno una certa sottocatena di istruzioni o un’altra
sottocatena a seconda dello stato di un Flag.
Alcuni processori hanno delle istruzioni libere che possono essere programmate con varie
microistruzioni dal cliente. Questo tipo di programmazione si chiama Firmware.
Il Fetch dell’istruzione è sempre uguale ma ci possono essere istruzioni per cui basta un prelievo di
istruzione per avere tutto il necessario mentre alcune istruzioni hanno necessità di più letture in memoria.
In questo secondo caso la catena di microistruzioni dell’Execute dell’istruzione comprende fasi
ulteriori di Fetch con relatici incrementi del Program Counter prima di avere l’Execute vero e proprio.

Register/Pointer

I registri sono Array di Flip-Flop con possibilità di parallel-in, parallel-out, shift a destra e sinistra,
rotate destro e sinistro. Vengono usati come memoria temporanea o come elementi base per le operazioni
della ALU. Possono essere usati anche come contatori.
Di solito è possibile testare il singolo bit, metterlo ad 1, metterlo a 0.
I pointer sono invece fatti a contatore con la possibilità di incrementare o decrementare il valore di
uno senza dovere passare all’unita ALU. Vengono utilizzati per muovere blocchi di dati, come puntatori
dell’indirizzo corrente.
Stack e Stack Pointer

Un’esigenza comune di qualsiasi programmatore è quella di potere fare dei sottoprogrammi, delle
routine. Se una funzione viene usata più volte la si mette in un sottoprogramma e si richiama il
sottoprogramma tutte le volte che è necessario senza starlo a riscrivere ogni volta. Vedremo poi che in
effetti un programma senza sottoprogrammi ma in un corpo unico è più veloce ma diventerebbe ingestibili,
molto più grande in memoria. A volte per esigenze di tempo di esecuzione si fa proprio questo ma si tratta
per lo più di casi sporadici con programmi relativamente brevi.
Dal punto di vista della CPU invece, succede che si salta dal programma principale ad un
sottoprogramma che è scritto in un altro punto della memoria, e poi al termine di questo si torna
all’istruzione successiva a quella da cui si è partiti per fare il salto.
La prima operazione è un CALL ad un sottoprogramma, mentre l’ultima è un RETURN.
Quando si fa un CALL si deve quindi memorizzare il punto da cui si è partiti, anzi meglio il punto
cui si deve tornare che sarà l’istruzione successiva al CALL. Questo indirizzo viene memorizzato nello
STACK che è una zona di memoria destinata solo a questo scopo, in cui non devono essere scritti o dati o
parti del programma altrimenti tutto il sistema delle routine fallisce.
Può succedere che il primo sottoprogramma ne richiami un altro e questo ne richiami un terzo e
così via fino a profondità di sottoprogrammi molto elevate. A questo punto il numero di indirizzi da
memorizzare per i vari RETURN è molto elevato. Per gestire tutto questo si usa lo STACK POINTER.
Lo STACK POINTER contiene l’indirizzo dell’ultima locazione di STACK che è stata scritta.
Quando si fa un CALL quindi si incrementa lo STACK POINTER di uno perché punti una
posizione dello STACK libera, si salva il contenuto del PROGRAM COUNTER nella relativa posizione
dello STACK e si mette nel PROGRAM COUNTER l’indirizzo del CALL. Questa operazione si chiama
PUSH .
Quando si arriva ad un RETURN il contenuto della locazione di STACK puntata dallo STACK
POINTER viene caricato nel PROGRAM COUNTER, e lo STACK POINTER viene decrementato di 1.
questa operazione si chiama POPE.
Esempi di lavoro dello Stack, Stack Pointer, Program Counter

……………….. 1000 ………………. 2000………………


……………….. 1001……………… 2001………………
100 CALL 1000 ……………………. ……………………
101 ………….. 1100 ……………….. 2150 RETURN
………………… 1101 RETURN
200 CALL 2000
201……………
………………..
………………..

Le istruzioni fino al passo 100 vengono eseguite sequenzialmente in ordine come sono scritte, ma
al passo 100 abbiamo un CALL al passo 1000 dove si ha un sottoprogramma. Si deve quindi aggiornare
lo Stack Pointer su una nuova posizione incrementandolo di 1, si salva il contenuto del Program Counter
nella locazione dello Stack puntata dallo Stack Pointer e si carica l’indirizzo segnato nel CALL nel
Program Counter.
Se lo Stack Pointer indica per convenzione 0 perché fino ad ora non è stato aperto nessun
sottoprogramma. Lo stato dei registri, dopo l’esecuzione 100 sarà quindi:
PROGRAM COUNTER STACK POINTE R STACK 1
1000 0001 101

Si fa notare come lo Stack contenga 101 ossia l’indirizzo cui si deve tornare una volta finita
l’esecuzione del sottoprogramma. Il Program Counter comunque al momento del PUSH contiene già 101
perché appena finito il Fetch il Program Counter viene aggiornato all’indirizzo dell’istruzione successiva
automaticamente.

Dal passo 1000 in poi il Progam Counter viene incrementato automaticamente fino al passo 1100
quando si ha il RETURN. A questo punto si esegue un POPE. La locazione dello Stack puntata dallo
Stack Pointer viene caricata nel Program Conter e lo Stack Pointer viene decrementato.

PROGRAM COUNTER STACK POINTE R STACK 1


101 0000 101

Si fa notare come il contenuto della locazione 0 dello Stack non venga cancellata perchè è
un’operazione inutile, essa verrà riscritta quando si farà un altro CALL ed è esattamente quello che
succederà al passo 200. dopo il passo 200 avremo esattamente questa situazione:

PROGRAM COUNTER STACK POINTE R STACK 1


2000 0001 201

mentre dopo l’esecuzione del passo 2150 avremo:

PROGRAM COUNTER STACK POINTE R STACK 1


201 0000 201
esempio n.2

……………… 1000 ………………… 2000………….


100 CALL 1000 1001…………………. 2001…………
101…………… ……………………… …………..
102…………….. 1100 CALL 2000 2200 CALL 4000
1101……………. 2201 ………….
……………………. ………………..
1140 RETURN 2400 RETURN

4000………….
4001…………
…………..
4500 CALL 8000
4501 ………….
………………..
4530 RETURN

8000………….
8001…………
…………..
………………..
8400 RETURN

Al passo 100 si ha il primo CALL e quindi in primo PUSH:

PROGRAM COUNTER STACK POINTER STACK 1


1000 0001 101

al passo 1100 si ha il secondo CALL senza che sia chiuso il primo quindi:

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2


2000 0002 101 1101

Al passo 2200 si ha il terzo CALL senza che siano chiusi i due precedenti :

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2 STACK 3


4000 0003 101 1101 2201

Al passo 4200 si ha il quarto CALL senza che siano chiusi i precedenti

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2 STACK 3 STACK 4


8000 0004 101 1101 2201 4501
Al passo 8400 si incomincia a chiudere l’ultimo sottoprogramma quindi si fa il primo POPE:

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2 STACK 3 STACK 4


4501 0003 101 1101 2201 4501

Al passo 4530 si chiude il terzo sottoprogramma:

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2 STACK 3 STACK 4


2201 0002 101 1101 2201 4501

Al passo 2400 si chiude il secondo sottoprogramma:

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2 STACK 3 STACK 4


2201 0001 101 1101 2201 4501

Al passo 1140 si chiude il primo sottoprogramma:

PROGRAM COUNTER STACK POINTER STACK 1 STACK 2 STACK 3 STACK 4


101 0000 101 1101 2201 4501

come si vede quando si torna al programma principale lo Stack Pointer contiene 0, non punta
nessuna locazione.

I FLAG
Sono registri di un solo bit che vengono scritti dalla ALU in seguito ad operazioni matematiche o
logiche. Il loro numero varia a secondo delle CPU e della loro complessità.
Elenchiamo i principali:

SIGN indica il segno del risultato dell’ultima operazione matematica, se è positivo o negativo se
il numero è scritto nella forma complemento a 2.

ZERO indica se il risultato dell’ultima operazione matematica o logica è 0, o se il test di un bit di


un registro è 1

NONZERO indica se il risultato dell’ultima operazione matematica è diverso da 0, o se il risultato


di un’operazione logica è 1, o se il test di un bit di un registro è 1

CARRY indica se il risultato dell’ultima operazione matematica ha generato riporto, può valere sia
per l’addizione che per la sottrazione.

BORROW indica se l’ultima sottrazione ha generato riporto.


HALF CARRY indica se c’è stato riporto dai 4 bit più leggeri serve per la gestione dei numeri in
BCD

PARITY indica se c’è stato errore di parità nei dati che arrivano alla CPU

OVERFLOW indica se l’operazione matematica ha generato un risultato più grande del numero di
bit a disposizione.

I FLAG condizionano l’esecuzione di certe istruzioni, è per questo che nello schema generale c’è la
freccia che indica movimento dal registro dei FLAG al Sequencer. Sono generati direttamente dalla ALU.

INTERRUPT

La CPU può interpellare la periferica mettendo il suo indirizzo sull’Address Bus e il comando di
I/O. Se ci sono molte periferiche la CPU dovrà fare ciclicamente una verifica dello stato delle periferiche
per controllare se hanno dati da trasmettere o se sono pronte a riceverne. Questo modo di procedere si
chiama POLLING. Ogni volta la CPU dialoga con una periferica si deve espletare un certo dialogo
preliminare che varia da caso a caso in cui le due parti si accordano su cosa si sta trasmettendo o ricevendo
e con quale protocollo. Questi preliminari che si chiamano HANDSHAKING (stretta di mano) portano via
un certo tempo e sovente può succedere che si faccia l’Handshaking e poi non ci sia nulla da trasferire
oppure che la periferica non sia pronta per l’Handshaking stesso. Questo comporta una perdita di tempo ed
un rallentamento delle prestazioni della CPU.
Per rimediare a queste perdite di tempo si è inventato l’INTERRUPT. L’Interrupt è una richiesta
che la periferica invia alla CPU o quando ha qualcosa da mandare o quando è pronta a ricevere ulteriori
dati. In questo modo si verifica l’Handshaking solo quando è necessario e la CPU dedica molto meno
tempo al dialogo con le periferiche.
I modi con cui si realizza l’Interrupt sono svariati.

Interrupt vettoriale
Quando una periferica ha da comunicare con la CPU emette un segnale di Interrupt. La CPU
quando è in grado di iniziare il dialogo con la periferica risponde con un segnale di accettazione
dell’Interrupt: INTERRUPT AKNOWLEDGE. La periferica sa di potere essere servita e mette sul Data
Bus un vettore che è suo caratteristico che la identifica altrimenti la CPU non può sapere chi ha generato
l’Interrupt. La CPU salva l’indirizzo contenuto nel Program Counter come se fosse arrivato un CALL
ossia fa una PUSH, e mette nel Program Counter un indirizzo che di solito è formato in parte dal vettore
mandato dalla periferica ed una parte da un vettore interno che è la pagina di Interrupt.
Ovviamente ogni sottoprogramma si Interrupt deve terminare con una istruzione di Return o
equivalente. Di solito il Return da Interrupt è diverso da quello di Return da un CALL.
Ossia i bit più leggeri arrivano sul Data Bus dalla periferica e quelli più pesanti sono una costante
interna che è la pagina degli Interrupt. Ovviamente questa zona di memoria non deve essere scritta da
nessuna parte del programma. La pagina di Interrupt può essere una costante della CPU in questione o può
invece essere stabilita con il software all’inizio del programma stesso.
Rimane il problema che c’è un filo solo su cui tutte le periferiche chiedono l’Interrupt, e quindi c’è
una situazione conflittuale. Per ovviare questo ci sono diverse soluzioni che fanno in modo che esista una
gerarchia per fare la richiesta di Interrupt.
Daisy chain
Tutte le periferiche sono collegate tra di loro con sue fili: con un filo la periferica disabilita
l’Interrupt le periferiche con priorità inferiore, sull’altro filo viene lei disabilitata da quelle a priorità più
alta. Quando una periferica è disabilitata da quella superiore, a sua volta disabilita quelle di livello
inferiore, quando chiede lei stessa l’Interrupt disabilita le periferiche di livello inferiore.
Una periferica di livello superiore può interrompere l’Interrupt di una periferica di livello inferiore
e si fa un ulteriore PUSH.

Priorità impostata a software


Un'altra via è quella di impostare il livello di Interrupt a software. La CPU in fase iniziale comunica
ad ogni periferica il suo livello di Interrupt. Quando una periferica emette un Interrupt, tutte le altre sono
inibite dall’emettere Interrupt. Quando questo viene riconosciuto e la periferica mette il vettore di Interrupt
sul Data bus, le periferiche lo riconoscono e sanno se si sta eseguendo un Interrupt con priorità più alta o
bassa della loro.
Con questo sistema il livello di priorità può essere modificato a software a seconda delle esigenze,
ma le periferiche devono essere più evolute.

Interrupt con più fili


Alcune CPU hanno più fili, per cui ogni periferica ha il suo comando individuale di Interrupt. La
CPU quindi non ha bisogno di ulteriori informazioni quando riceve un segnale di Interrupt, sarà lei a
decidere con quale periferica mettersi in comunicazione.

Interrupt con più fili codificati


Nel caso di molte periferiche questo sistema imporrebbe di produrre delle CPU con numeri molto
elevati di fili. Per ridurre il numero di fili, si invia alla CPU non la richiesta diretta ma la sua codificazione
ossia con N fili si hanno 2N periferiche. Ogni periferica ha un filo solo, tutti i fili assieme entrano in un
codificatore che trasmette il codice della periferica a priorità più alta che sta richiedendo l’Interrupt.

Mascheratura dell’Interrupt
Quando la periferica riceve un Interrupt, lo serve, in condizioni normali al termine dell’EXECUTE
dell’istruzione in corso. Ci sono delle parti di programma o dei sottoprogrammi che fanno ampio uso dei
registri della CPU che se venissero interrotti da un Interrupt darebbero poi risultati sbagliati. Esiste quindi
nelle CPU quasi sempre la possibilità di rendere a software la CPU insensibile alle richieste di Interrupt.
Prima di iniziare la parte di programma critica si disabilita a software l’Interrupt, per riabilitarlo
quando questa parte del programma è terminata. Questa operazione si chiama mascheratura dell’Interrupt.

Maskable Interrupt , non Maskable Interrupt


Generalmente nelle CPU c’è anche un Interrupt che non può essere ignorato dalla CPU, che si
chiama NON MASKABLE INTERRUPT. Quando arriva questo segnale di solito viene messo nel
Program Counter l’indirizzo di partenza all’accensione. Quindi questo Interrupt viene usato come Reset
spesso, per avere un Reboot del sistema o per crash o per mancanza di alimentazione.

I miglioramenti della CPU

La CPU è rimasta sempre concettualmente quella inventata da NEUMANN negli anni ’40, ma sono
state apportate varie modifiche per avere prestazioni migliori.
PREFETCH
Abbiamo visto che ci sono due operazioni fondamentali: il prelievo dell’istruzione e la sua
esecuzione, ossia FETCH ed EXECUTE.
Se si riuscisse a prelevare l’istruzione durante la fare di EXECUTE i tempi di esecuzione totale
dimezzerebbero all’incirca.
Per fare questo si è inventato il Prefetch.
Durante l’Execute di alcune istruzione il bus sia interno che esterno sono liberi, perché il tutto
avviene tra la ALU ed i registri ad essa direttamente collegati. Questo è il caso di operazioni matematiche
complesse. In questo tempo si può benissimo fare il Fetch dell’istruzione successiva ma si deve avere il
posto in cui mettere questa istruzione all’interno della CPU. A questo scopo si deve raddoppiare
l’Instruction Register in modo che a fine Execute si possa subito passare all’Execute successivo.
Nella pratica poi si usano Instruction Register non solo raddoppiati ma anche con 10 , 20
Instruction Register completi, per avere il maggior numero di possibilità di non dovere fare dei Fetch soli.
Questo perché alcune istruzioni sono di esecuzione interna molto lunga e permettono di fare molti Fetch,
mentre altre non ne permettono nessuno. Si pensi ad una istruzione che carica dei dati dalla memoria ai
registri interni: non lascia mai i Bus sia interno che esterno liberi. Avendo più Instruction Register la
probabilità di avere tempi di solo Fetch si riducono ovviamente.
Per potere fare il Prefetch occorre che ci sia un’ulteriore intelligenza di gestione di esso. In effetto
oltre agli Instruction Register supplementari si deve avere un minimo processore che comanda il Prefetch.
Quando ci sono istruzioni ci CALL o di salto, JUMP, il Prefetch viene vanificato perché si salta
quasi sicuramente ad istruzioni che sono fuori di quelle contenute nell’ARRAY di Instruction Registers.

Memorie CACHE

Attualmente i processori più veloci hanno il clock con un periodo di 300 picosecondi, 300 x10-12 ,
mentre le memorie più veloci hanno tempi di accesso di 3 nanosecondi, 3 x10-9. Inoltre oltre al tempo di
accesso della memoria stessa si deve tenere conto del tempo di percorrenza del Bus esterno che so prende
almeno un nanosecondo. Se la CPU legge o scrive un dato in memoria avrà quindi un WAIT che dura
almeno 12 colpi di clock. Per ridurre questo tempo di attesa che rallenta enormemente la potenzialità della
CPU si mette tra la CPU una memoria CACHE. La memoria CACHE è una memoria più piccola ma
moltom più veloce e disposta fisicamente vicino alla CPU per contenere in tempi di attraversamento del
Bus esterno. In questa memoria si trovano le istruzioni ed i dati più in uso in quel momento.
Ovviamente le istruzioni di CALL e di Jump possono portare fuori CACHE ma si sa che se la
Cache è abbastanza grande la probabilità di un salto che porti fuori di essa è molto ridotta e quindi
l’esecuzione dei programmi è molto accelerata. Negli anni scorsi sono state fatte alcune versioni
economiche di processori che non avevano la cache ma con risultati disastrosi: i tempi di esecuzione
diventavano lunghissimi.
Per quanto la si faccia piccola e la si metta vicino al processore non si riesce a raggiungere la
velocità che si ha internamente al processore, per cui oltre alla Cache esterna molti processori hanno una o
anche due Cache interne, una più grande e più lenta ed una più piccola che va alla velocità del processore
esterno. Tutto questo per ridurre il più possibile i tempi di WAIT.
Ovviamente per gestire le varie Cache occorre una ulteriore “intelligenza” che tenga conto di dove
si debba leggere un’istruzione o un dato: in quale livello di CACHE, o nella RAM, o nelle memorie di
massa.
Estensione dell’uso delle memorie cache

Il problema della diversa velocità è presente per quasi tutte le periferiche, infatti esse hanno una
velocità di accesso che è spesso molto più bassa di quella del Bus. Attualmente tutte le periferiche sono
provviste di memoria Cache più o meno grande, anzi la dimensione della Cache è uno dei fattori di
valutazione delle prestazioni della periferica.
Le stampanti hanno una Cache in cui accumulano i dati da stampare, le porte di ingresso/uscita
hanno una Cache in cui accumulano o i dati da trasmettere o quelli ricevuti.
Le memorie di massa, hard disk, lettori di dischi ottici, hanno un Cache in cui vengono caricati le
parti di memoria adiacenti ai dati richiesti, mentre ormai la Cache del floppy disk memorizza il floppy disk
per intero, oppure questo viene memorizzato sulla RAM stessa per intero. Questo ultimo sistema si chiama
RAM disk.

Memoria fisica e memoria virtuale

La memoria fisica è quella indirizzabile direttamente ossia memoria RAM.


La memoria virtuale è la memoria globale che può indirizzare la CPU, quindi comprende
ovviamente la memoria fisica e le memorie di massa, ed ovviamente più grande.
Per convenzione la memoria virtuale è divisa in pagine, anche queste di dimensione convenzionale.
La memoria fisica stessa di solito è in grado di contenere più pagine. Quando la CPU lavora vengono
caricate nella memoria fisica le pagine su cui sta lavorando, ossia i File aperti, quelli su con cui si sta
lavorando. La memoria fisica quindi viene “segmentata” in varie parti di programmi diversi. Quando
occorrono nuove pagine di memoria virtuale e non c’è più memoria fisica disponibile avviene uno
“Swap”, ossia un segmento di memoria fisica viene rimesso, aggiornato se si tratta di dati, sull’hard disk e
viene al suo posto caricato il nuovo segmento utilizzato dalla CPU. Questa segmentazione e scambio viene
gestita dal sistema operativo, e le prestazioni di questa gestione sono un indice importante di funzionalità e
precisione del sistema operativo.

Le operazioni matematiche

Sommatore semplice

* ***
11001101 +
10011100 =
------------
101101001

Se noi sommiamo due numeri binari abbiamo una cifra di risultato e una di riporto per la somma
da aggiungere alla somma delle due cifre successive. quindi praticamente dobbiamo essere in grado di fare
una somma di tre cifre alla volta, tre cifre che possono essere 1 o 0. Se abbiamo tre 0, il totale è 0 con
riporto 0; se c’è un solo 1, il totale è 1 con riporto 0; se ci sono due 1, il totale è 0 con riporto 1; se ci sono
tre 1, il totale è 1 con riporto 1. Da questo consegue la allegata tavola di verità per un rete combinatoria
con tre ingressi: due addendi ed il riporto dello stadio precedente, e due uscite: il totale ed il riporto per lo
stadio successivo.

INGRESSI USCITE
riporto riporto per
addendo addendo stadio stadio
totale
1 2 precedent successiv
e o
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1

Da questa tavola di verità si possono ricavare le due relative mappe di Karnaugh e le relative reti logiche
semplificate.

mappa per il totale


0 1 11 10
0 0 1 0 1

1 1 0 1 0

mappa per il riporto


0 1 11 10
0 0 0 1 0
1 0 1 1 1

Come si vede per fare la somma bastano due reti combinatorie molto semplici.
clock

SHIFT REGISTER
SHIFT REGISTER
RETE LOGICA
SOMMATORE
SHIFT REGISTER

FLIP FLOP

Per fare la somma di due addendi ad n bit occorrono 3 shift register a n bit. Ad ogni colpo di clock
si fa la somma di due bit e del riporto dello stadio precendente. Occorrono quindi tanti colpi di clock quanti
sono i bit degli addendi. il risultato potrà avere n+1 a bit a seconda se l’ultimo stadio generi riporto o no.
Questo tipo di hardware viene utilizzato solo nei processori di fascia bassa, infatti è il tipo di
sommatore più lento ed economico. Lo si trova quindi solo nei processori molto economici o nelle
calcolatrici con le sole 4 operazioni: non si riuscirebbe a pigiare i tasti ad una velocità tale da intasarle.
Negli altri casi si usano sommatori che fanno la somma di 4, 8, 16, 24 bit, a seconda dei casi, ad
ogni colpo di clock.
Per ottenere questo risultato si fa una serie di reti logiche come in figura: