Assembly Base con MASM
Capitolo 6: Reti combinatorie per funzioni matematiche
Nel Capitolo 4 abbiamo visto che, dal punto di vista degli esseri umani, elaborare delle
informazioni codificate sotto forma di numeri binari significa eseguire su questi numeri
una serie di operazioni matematiche che devono produrre dei risultati sempre in formato
binario; in base allora a quanto è stato esposto nel Capitolo 5, possiamo dire che dal
punto di vista del computer, elaborare informazioni codificate sotto forma di segnali
logici significa eseguire su questi segnali una serie di operazioni che devono produrre
dei risultati sempre sotto forma di segnali logici. In questo capitolo vengono analizzate
una serie di R.C. che permettono alla CPU di eseguire elaborazioni sui
segnali logici; per noi esseri umani queste elaborazioni effettuate dal computer assumono
proprio l'aspetto di operazioni matematiche eseguite sui numeri binari.
6.1 Circuiti Multiplexer e Demultiplexer
Prima di illustrare le R.C. per le funzioni matematiche, analizziamo due circuiti
che in elettronica digitale trovano un impiego veramente massiccio; si tratta dei circuiti
chiamati Multiplexer e Demultiplexer.
Il Multiplexer o MUX rappresenta un vero e proprio commutatore elettronico;
questo circuito, infatti, riceve in ingresso n segnali logici ed è in grado di
selezionare in uscita uno solo di essi.
Osserviamo subito che, per selezionare una tra n possibili linee in ingresso,
abbiamo bisogno di m linee di selezione con:
2m = n
Da questa relazione si ricava:
log2 2m = log2 n
e quindi:
m = log2 n
Nel caso, ad esempio, di un MUX a n=4 ingressi, abbiamo bisogno di
m=2 linee di selezione; infatti, con 2 bit possiamo gestire
22=4 configurazioni diverse (4 numeri binari differenti).
Se colleghiamo ora i 4 ingressi del MUX a 4 porte AND,
grazie alle 2 linee di selezione possiamo abilitare solo una delle porte
AND; di conseguenza, la porta AND abilitata trasferisce in uscita
l'ingresso ad essa collegato. Le 4 uscite delle porte AND vengono
collegate ad una porta OR a 4 ingressi per ottenere il segnale che
abbiamo selezionato; la Figura 6.1 illustra la struttura della R.C. che
implementa un MUX a 4 ingressi e una uscita (chiamato anche MUX
da 4 a 1).
L'ingresso E (Enable) permette di attivare o disattivare l'intero
MUX; portando l'ingresso E a livello logico 1, in uscita dal
NOT otteniamo un livello logico 0 che disattiva tutte le 4 porte
AND. In questo modo, l'uscita Y del MUX si trova sempre a livello
logico basso indipendentemente dai 4 segnali in ingresso; se, invece, E=0,
il MUX viene attivato, ed è in grado di selezionare uno solo tra i 4
ingressi a0, a1, a2, a3.
Le due linee S0 e S1 rappresentano gli ingressi di selezione; ponendo
ora E=0 (MUX attivo), si può constatare dalla Figura 6.1 che:
- Se S0 = 0 e S1 = 0 si ottiene Y = a0
- Se S0 = 1 e S1 = 0 si ottiene Y = a1
- Se S0 = 0 e S1 = 1 si ottiene Y = a2
- Se S0 = 1 e S1 = 1 si ottiene Y = a3
Il compito opposto rispetto al caso del MUX viene svolto da un altro circuito
chiamato Demultiplexer o DMUX; il DMUX riceve in ingresso un
unico segnale logico, ed è in grado di trasferirlo su una sola tra le n linee
di uscita. Come al solito, per la selezione delle n linee di uscita abbiamo
bisogno di:
m = log2 n
linee di selezione.
Attraverso queste m linee di selezione possiamo attivare solo una delle
n linee di uscita, disattivando tutte le altre n-1; si presenta
allora il problema di stabilire il livello logico da assegnare alle n-1
linee di uscita disabilitate. Chiaramente, se vogliamo trasferire sull'uscita
abilitata un segnale a livello logico 1, dobbiamo portare a livello
logico 0 tutte le n-1 uscite disabilitate; viceversa, se vogliamo
trasferire sull'uscita abilitata un segnale a livello logico 0, dobbiamo
portare a livello logico 1 tutte le n-1 uscite disabilitate.
Per ottenere queste caratteristiche, gli m ingressi di selezione agiscono
su n porte NAND ciascuna delle quali fornisce una delle n
uscite; la Figura 6.2 illustra la struttura della R.C. che implementa un
DMUX a 1 ingresso e 4 uscite (chiamato anche DMUX
da 1 a 4).
In questo circuito, S0 e S1 sono gli ingressi di selezione, D
è l'unico ingresso per i dati, mentre E è come al solito l'ingresso di
abilitazione; le 4 uscite del DMUX sono Y0, Y1, Y2
e Y3.
Il DMUX di Figura 6.2 permette di trasferire su una delle 4 uscite un
segnale a livello logico 0; di conseguenza, le 3 uscite disabilitate
si portano a livello logico 1. Per ottenere questo trasferimento bisogna
porre E=0 e D=1; in queste condizioni possiamo constatare che:
- Se S0 = 0 e S1 = 0 si ottiene Y0 = 0,
Y1 = 1, Y2 = 1, Y3 = 1
- Se S0 = 1 e S1 = 0 si ottiene Y0 = 1,
Y1 = 0, Y2 = 1, Y3 = 1
- Se S0 = 0 e S1 = 1 si ottiene Y0 = 1,
Y1 = 1, Y2 = 0, Y3 = 1
- Se S0 = 1 e S1 = 1 si ottiene Y0 = 1,
Y1 = 1, Y2 = 1, Y3 = 0
6.2 Comparazione tra numeri binari
Tra le istruzioni più importanti messe a disposizione dai linguaggi di programmazione
di alto livello, troviamo sicuramente quelle denominate istruzioni per il controllo
del flusso, così chiamate perché permettono ad un programma di prendere delle
decisioni in base al risultato di una operazione appena eseguita; si possono citare,
ad esempio, istruzioni come IF, THEN, ELSE, DO WHILE,
REPEAT UNTIL, etc. Dal punto di vista della CPU tutte queste istruzioni
si traducono in una serie di confronti tra numeri binari; proprio per questo motivo,
tutte le CPU sono dotate di apposite R.C. che permettono di effettuare
comparazioni tra numeri binari.
Supponiamo, ad esempio, di avere due numeri binari A e B a 8 bit che
rappresentano i codici
ASCII
di due lettere dell'alfabeto; consideriamo ora un programma che elabora questi due
numeri attraverso le pseudo-istruzioni mostrate in Figura 6.3:
Come si può notare, questa porzione del programma effettua una scelta basata sul
risultato del confronto tra A e B; se A è uguale a B,
l'esecuzione salta al blocco di istruzioni denominato Procedura 1, mentre
in caso contrario (A diverso da B), l'esecuzione salta al blocco di
istruzioni denominato Procedura 2.
Per comparare A con B, abbiamo bisogno di una funzione booleana
che riceve in input i due numeri binari da confrontare e restituisce in output un
risultato espresso sempre in forma binaria; possiamo usare, ad esempio, il valore
0 per indicare che i due numeri sono diversi tra loro e il valore 1
per indicare che i due numeri sono uguali. Una volta scritta la funzione booleana, si
può passare alla sua realizzazione pratica tramite una R.C..
Nel precedente capitolo, abbiamo visto che una porta EX-OR a due ingressi
restituisce 0 se i due livelli logici in ingresso sono uguali tra loro e
restituisce, invece, 1 in caso contrario; analogamente, una porta EX-NOR
a due ingressi restituisce 1 se i due livelli logici in ingresso sono uguali
tra loro e restituisce, invece, 0 in caso contrario. Le porte EX-OR
oppure le porte EX-NOR appaiono quindi particolarmente adatte a risolvere il
nostro problema; per capire come si debba effettuare il confronto tra due numeri binari,
bisogna fare innanzi tutto alcune considerazioni importanti:
- Il confronto ha senso solo se i due numeri binari hanno lo stesso numero di
bit.
- Due numeri binari sono uguali quando hanno i bit corrispondenti (di pari
posizione) uguali tra loro; quindi, il bit in posizione 0 del numero
A deve essere uguale al bit in posizione 0 del numero B,
il bit in posizione 1 del numero A deve essere uguale al bit in
posizione 1 del numero B e così via.
In base a queste considerazioni, vediamo subito come si deve procedere utilizzando, ad
esempio, le porte EX-NOR che restituiscono 1 se i due livelli logici in
ingresso sono uguali tra loro e 0 se sono diversi; utilizziamo una porta
EX-NOR per ogni coppia di bit (corrispondenti) da confrontare. La prima porta
confronta il bit in posizione 0 del primo numero con il bit in posizione 0
del secondo numero; la seconda porta confronta il bit in posizione 1 del primo
numero con il bit in posizione 1 del secondo numero e così via. Ogni porta
EX-NOR produce un risultato che vale 1 se i due bit sono uguali tra loro
e vale, invece, 0 in caso contrario; i due numeri binari sono uguali tra loro solo
quando tutte le porte EX-NOR producono 1 come risultato, cioè quando
tutti i bit corrispondenti dei due numeri sono uguali tra loro. Se almeno una porta
EX-NOR produce 0 come risultato, i due numeri binari sono diversi tra loro.
Per sapere se tutte le porte EX-NOR hanno prodotto 1, basta confrontare
con un AND tutte le loro uscite; l'AND ci restituisce 1 solo se
tutti i suoi ingressi valgono 1 e ci restituisce 0 se anche un solo
ingresso vale 0. Facendo riferimento per semplicità a numeri a 4 bit
(1 nibble), la nostra funzione booleana sarà allora.
Y = (A0 EX-NOR B0) AND (A1 EX-NOR B1) AND (A2 EX-NOR B2) AND (A3 EX-NOR B3)
dove A0, A1, A2, A3 rappresentano i 4 bit del primo
numero e B0, B1, B2, B3 rappresentano i 4 bit del
secondo numero.
La Figura 6.4 mostra la R.C che implementa in pratica questa funzione.
Si capisce subito che il numero di porte EX-NOR da utilizzare è pari al numero
di coppie di bit da confrontare; per confrontare, ad esempio, due numeri a 8 bit
(8 coppie di bit) occorreranno 8 porte EX-NOR. Le uscite di tutte
le porte EX-NOR impiegate vanno collegate agli ingressi di una porta AND
che restituisce 1 solo quando tutti i suoi ingressi valgono 1; ma questo
accade solo quando tutti i confronti hanno prodotto 1 come risultato (bit
corrispondenti uguali tra loro). In definitiva, la R.C. di Figura 6.4 fornisce in
output il livello logico 1 se i due numeri da confrontare sono uguali tra loro
e il livello logico 0 in caso contrario.
Come si può notare, ogni porta EX-NOR confronta un bit del primo numero con il
bit che nel secondo numero occupa la stessa posizione; questo tipo di confronto viene
definito bit a bit (in inglese bitwise).
Tutti i concetti appena esposti, si estendono immediatamente al confronto tra numeri
binari di qualunque dimensione; se vogliamo confrontare numeri binari a 16 bit, ci
occorreranno 16 porte EX-NOR e una porta AND a 16 ingressi;
se vogliamo confrontare numeri binari a 32 bit ci occorreranno 32 porte
EX-NOR e una porta AND a 32 ingressi e così via. Queste
considerazioni comunque sono solamente teoriche perché in realtà, nella realizzazione
pratica dei comparatori (e di altre R.C.) si segue un'altra strada; può capitare,
infatti, che in commercio si trovino solo i comparatori a 4 bit illustrati in
Figura 6.4. Come si può facilmente intuire, in questo caso basta utilizzare uno o più
comparatori a 4 bit collegati in parallelo. Se, ad esempio, vogliamo confrontare
numeri a 16 bit, utilizzeremo 4 comparatori a 4 bit in parallelo
(4x4=16); se, invece, vogliamo confrontare numeri a 32 bit, utilizzeremo
8 comparatori a 4 bit in parallelo (8x4=32) e così via.
Ogni comparatore ha una sola uscita e tutte queste uscite vanno collegate agli ingressi di
una porta AND che produrrà il risultato finale. Si ricorda che le porte AND
con più di due ingressi, producono in uscita un livello logico alto solo se tutti i
livelli logici in ingresso sono alti; in caso contrario verrà prodotto un livello logico
basso.
Un caso che si presenta molto più di frequente consiste nel voler conoscere la relazione
esatta che esiste tra due numeri binari; dati cioè i due numeri binari A e B,
vogliamo sapere se A è minore di B o se A è uguale a B o se
A è maggiore di B. In Figura 6.5 vediamo un esempio relativo sempre al confronto
tra nibble.
Prima di tutto osserviamo in Figura 6.5a la struttura elementare del circuito che confronta
il bit Ai del numero A con il corrispondente bit Bi del numero
B; come si può facilmente constatare, in base al risultato del confronto solo
una delle tre uscite sarà a livello logico alto, mentre le altre due saranno a livello
logico basso. In particolare, notiamo che la prima uscita (quella più in alto) presenta
un livello logico alto solo se Ai è maggiore di Bi (cioè Ai=1 e
Bi=0); la seconda uscita presenta un livello logico alto solo se Ai è uguale
a Bi (cioè Ai=0 e Bi=0, oppure Ai=1 e Bi=1). La terza
uscita infine presenta un livello logico alto solo se Ai è minore di Bi
(cioè Ai=0 e Bi=1).
Partendo dal circuito elementare di Figura 6.5a si può facilmente ottenere un comparatore
completo a 4 bit che ci permette di sapere quale relazione esiste tra il nibble
A e il nibble B; il blocco che rappresenta simbolicamente questo comparatore,
viene mostrato in Figura 6.5b. I comparatori a 4 bit che si trovano in commercio, sono
dotati, come si può notare, di 3 uscite indicate con Y1, Y2 e Y3;
l'uscita Y1 è associata alla condizione A maggiore di B, l'uscita
Y2 è associata alla condizione A uguale a B e l'uscita Y3 è
associata alla condizione A minore di B.
Possiamo dire quindi che se A è maggiore di B si ottiene:
Y1 = 1, Y2 = 0, Y3 = 0
Se A è uguale a B si ottiene:
Y1 = 0, Y2 = 1, Y3 = 0
Se A è minore di B si ottiene:
Y1 = 0, Y2 = 0, Y3 = 1
La tecnica che si utilizza per il confronto è molto semplice e si basa su una proprietà
fondamentale dei sistemi di numerazione posizionali; come sappiamo, infatti, i numeri
rappresentati con questo sistema sono formati da una sequenza di cifre il cui peso dipende
dalla posizione della cifra stessa nel numero. La cifra di peso maggiore è quella più a
sinistra e quindi, per confrontare due numeri nel modo più rapido possibile, è necessario
partire dal confronto tra le cifre più significative dei due numeri stessi (MSB per
i numeri binari); ancora una volta bisogna ricordare che l'operazione di confronto ha senso
solo se i due numeri hanno lo stesso numero di cifre.
Tornando allora al caso del comparatore a 4 bit di Figura 6.5b, vediamo come si svolge
il confronto tra i due nibble A e B:
- Se il bit A3 è maggiore del bit B3 è inutile continuare perché
sicuramente il nibble A è maggiore del nibble B; di conseguenza
Y1 sarà a livello logico 1 mentre le altre due uscite saranno a
livello logico 0.
- Se il bit A3 è minore del bit B3 è inutile continuare perché
sicuramente il nibble A è minore del nibble B; di conseguenza
Y3 sarà a livello logico 1 mentre le altre due uscite saranno
a livello logico 0
- Se il bit A3 è uguale al bit B3 si deve necessariamente passare
ai bit in posizione 2 ripetendo i passi 1 e 2 appena
descritti; se anche il bit A2 è uguale al bit B2, si passa ai
bit in posizione 1 e così via, sino ad arrivare eventualmente ai bit
in posizione 0 (LSB). Solo dopo quest'ultimo confronto possiamo
dire con certezza se i due numeri sono uguali tra loro e in questo caso avremo
Y2 a livello logico 1 e le altre uscite a livello logico 0.
Nella Figura 6.5b si nota anche la presenza di tre connessioni I1, I2 e
I3; queste connessioni servono per il collegamento in parallelo di due o più
comparatori quando si ha la necessità di confrontare tra loro numeri con più di 4
bit. La Figura 6.6 illustra un esempio che si riferisce al confronto tra numeri binari a
8 bit; vengono utilizzati a tale proposito due comparatori a 4 bit e cioè,
C1 che confronta il nibble basso e C2 che confronta il nibble alto.
Il confronto parte come al solito dai bit più significativi che in questo caso si trovano
in posizione 7; indichiamo con Aa e Ba i nibble alti, e con Ab
e Bb i nibble bassi dei due numeri A e B. Se il comparatore C2
confrontando i nibble alti verifica che Aa è maggiore di Ba o che Aa
è minore di Ba, non ha bisogno di consultare C1; in questo caso C2
ignora gli ingressi I1, I2 e I3 e termina il confronto fornendo i
risultati sulle sue uscite Y1, Y2 e Y3.
Se, invece, il comparatore C2 verifica che Aa è uguale a Ba, allora
legge i suoi ingressi I1, I2, I3 che gli forniscono il risultato del
confronto tra i nibble bassi effettuato da C1; come si può notare dalla Figura 6.6,
l'ingresso I1 è associato a Ab maggiore di Bb, l'ingresso I2
è associato a Ab uguale a Bb e l'ingresso I3 è associato a Ab
minore di Bb. In base ai livelli logici assunti da I1, I2 e I3,
il comparatore C2 fornisce il risultato finale sempre attraverso le sue uscite
Y1, Y2, Y3; anche in questo caso si nota che la condizione A
uguale a B può essere verificata con certezza solo dopo l'ultimo confronto che
coinvolge i bit A0 e B0 (cioè gli LSB).
Naturalmente, nell'esempio di Figura 6.6, gli ingressi I1, I2 e I3
del comparatore C1 non vengono utilizzati; appare anche evidente il fatto che,
attraverso il collegamento in parallelo di più comparatori a 4 bit, si possono
confrontare tra loro numeri binari di qualunque dimensione.
Un computer con architettura a 8 bit sarà dotato di comparatori a 8 bit;
un computer con architettura a 16 bit sarà dotato di comparatori a 16 bit
e così via. Quindi, su un computer con architettura a 8 bit, non sarà possibile
confrontare via hardware numeri a 16 bit; se si vuole effettuare ugualmente il
confronto tra numeri a 16 bit si dovrà procedere via software scrivendo un
apposito programma che naturalmente non potrà raggiungere le prestazioni del confronto
via hardware. Molti linguaggi di programmazione di alto livello mettono a disposizione
delle funzioni (sottoprogrammi) che permettono di effettuare via software diverse operazioni
su numeri formati da un numero di bit maggiore di quello dell'architettura del computer
che si sta utilizzando.
6.3 Addizione tra numeri binari
Come abbiamo visto nei precedenti capitoli, per sommare due numeri espressi nel sistema
posizionale arabo bisogna incolonnarli in modo da far corrispondere sulla stessa colonna
le cifre aventi peso identico nei due numeri stessi; sappiamo anche che la somma viene
eseguita a partire dalla colonna più a destra che contiene le cifre meno significative
dei due addendi. Questa prima somma non deve tenere conto di alcun riporto precedente;
nel sommare, invece, le colonne successive alla prima, si dovrà tenere conto dell'eventuale
riporto proveniente dalla somma della colonna precedente.
Bisogna ricordare inoltre che in binario, nel sommare la prima colonna, la situazione
più critica che si può presentare è:
1 + 1
che dà come risultato 0 con riporto di 1; nel sommare le colonne successive
alla prima, in presenza di un riporto pari a 1, la situazione più critica che si
può presentare è:
1 + 1 + 1
che dà come risultato 1 con riporto di 1. In base 2 quindi e in
qualsiasi altra base, il riporto massimo non può superare 1.
Da quanto abbiamo appena detto, si può capire che la R.C. che dovrà eseguire la
somma della prima colonna, sarà molto più semplice della R.C. che somma le colonne
successive tenendo conto di eventuali riporti; per questo motivo, i circuiti che sommano le
coppie di bit si suddividono in due tipi chiamati: Half Adder e Full Adder.
La Figura 6.7a illustra la tabella della verità di un Half Adder (H.A.) o
semisommatore binario; la Figura 6.7b illustra la R.C. che permette di implementare
la somma di due bit.
L'H.A. esegue la somma relativa ai due bit che nell'addizione occupano la prima
colonna (LSB) e produce quindi un valore a 2 bit compreso tra 00b
e 10b; nella tabella della verità indichiamo con S il bit più a destra
e con C il bit più a sinistra della somma appena effettuata dall'H.A..
Il bit S rappresenta una delle cifre del risultato finale (si tratta per la
precisione del LSB); il bit C (Carry) rappresenta il riporto
(0 o 1) di cui si dovrà tenere conto nella somma relativa alla colonna
successiva dell'addizione.
Osservando la tabella della verità dell'H.A. in Figura 6.7a, si vede subito che il
contenuto della colonna S coincide esattamente con i livelli logici in uscita da
una porta EX-OR in seguito alla operazione:
A EX-OR B
Notiamo poi che il contenuto della colonna C (riporto destinato alla colonna
successiva), coincide esattamente con i livelli logici forniti in uscita da una porta
AND in seguito alla operazione:
A AND B
In base a questi risultati, siamo in grado di definire le due funzioni che caratterizzano
l'H.A. e che sono:
S = A EX-OR B
e
C = A AND B
La Figura 6.7b illustra la R.C. che soddisfa la tabella della verità di Figura
6.7a; possiamo dire quindi che l'H.A. ha bisogno di due ingressi che rappresentano
i due bit da sommare e di due uscite che rappresentano una delle cifre del risultato finale
e il riporto destinato alla colonna successiva dell'addizione.
La Figura 6.8a illustra la tabella della verità di un Full Adder (F.A.) o
sommatore binario; la Figura 6.8b illustra la R.C. che permette di implementare
la somma di due bit più il riporto.
Il F.A. esegue la somma relativa ai due bit che nell'addizione occupano una tra
le colonne successive alla prima e deve tenere conto quindi anche del bit di riporto
proveniente dalla colonna precedente; questa volta la somma produce un valore a 2
bit che sarà compreso tra 00b e 11b. Nella tabella della verità indichiamo
con S il bit più a destra e con C il bit più a sinistra della somma appena
effettuata dal F.A.; il bit S rappresenta una delle cifre del risultato finale,
mentre il bit C (Carry) rappresenta il riporto (0 o 1) destinato
alla colonna successiva dell'addizione.
Supponiamo di dover sommare i bit della seconda colonna di una addizione e indichiamo con
C0 il riporto precedente, con A1 e B1 i due bit da sommare, con
S1 la cifra meno significativa della somma A1+B1 e con C1 il riporto
di A1+B1 destinato alla colonna successiva; la tabella della verità di Figura 6.8a è
formata da 8 righe in quanto, per ciascuno dei 4 possibili risultati della
somma A1+B1, dobbiamo considerare i due casi C0=0 e C0=1 per un totale
di 4x2=8 casi distinti.
Le funzioni booleane che realizzano il F.A. possono essere ottenute facilmente
ricordando la tecnica che si utilizza per eseguire le addizioni con carta e penna; in
relazione alle colonne successive alla prima, sappiamo che prima di tutto si sommano le
due cifre in colonna, dopo di che si aggiunge al risultato ottenuto il riporto precedente.
Queste due addizioni consecutive suggeriscono l'idea di utilizzare per il F.A.,
due H.A. in serie come si vede in Figura 6.8b; il primo H.A. somma i due bit
in colonna (A1+B1) producendo in uscita i risultati parziali S' e C',
mentre il secondo H.A. somma S' con il riporto precedente C0 producendo
a sua volta i risultati parziali S'' e C''. Alla fine S'' rappresenta
la cifra S1 del risultato finale, mentre i due riporti C' e C'' devono
fornirci il riporto C1 destinato alla colonna successiva.
Come si verifica facilmente dal circuito di Figura 6.8b, i due riporti C' e C''
non possono mai essere entrambi a livello logico 1 (la somma massima, infatti, è
11b) e quindi basta applicarli ai due ingressi di una porta OR per ottenere
il riporto C1 destinato alla colonna successiva.
Possiamo dire quindi che il F.A. ha bisogno di tre ingressi che rappresentano i
due bit da sommare e il riporto proveniente dalla colonna precedente; le due uscite del
F.A. forniscono, invece, una delle cifre del risultato finale e il riporto
destinato alla colonna successiva.
Una volta definiti l'H.A. e il F.A., possiamo realizzare circuiti per
sommare numeri binari di qualsiasi dimensione; lo schema a blocchi di Figura 6.9 mostra
una R.C. per la somma tra numeri binari a 4 bit.
La R.C. di Figura 6.9 utilizza un H.A. e tre F.A.; l'H.A. somma
i bit della prima colonna (LSB), mentre i tre F.A. sommano i bit delle tre
colonne successive. Il riporto finale C3 contiene il valore (0 o 1)
che verrà assegnato al Carry Flag; come già sappiamo, grazie a CF possiamo
sommare via software numeri binari di qualsiasi ampiezza in bit.
Anche nel caso dell'addizione (e di tutte le altre operazioni), bisogna ricordare che un
computer con architettura, ad esempio, a 16 bit, sarà dotato di circuiti in grado di
sommare via hardware numeri a 16 bit; se vogliamo sommare tra loro numeri a 32
bit, dobbiamo procedere via software suddividendo i bit degli addendi in gruppi da 16.
Nei precedenti capitoli abbiamo anche appurato che grazie all'aritmetica modulare, una
R.C. come quella di Figura 6.9 può sommare indifferentemente numeri con o senza
segno; alla fine dell'addizione, se stiamo operando sui numeri senza segno dobbiamo
consultare CF per conoscere il riporto finale, mentre se stiamo operando sui
numeri con segno dobbiamo consultare OF e SF per sapere se il risultato è
valido e per individuarne il segno. Come si può facilmente verificare attraverso la
Figura 6.9, si ha:
CF = C3, SF = S3, OF = (A3 EX-NOR B3) AND (A3 EX-OR B3 EX-OR S3)
6.4 Sottrazione tra numeri binari
Per la sottrazione valgono considerazioni analoghe a quelle svolte per l'addizione;
sottraendo i bit della colonna più a destra non si deve tenere conto di eventuali
prestiti chiesti dalle colonne precedenti; sottraendo, invece, i bit delle colonne
successive alla prima, bisogna tenere conto degli eventuali prestiti chiesti dalle
colonne precedenti. Nel primo caso, la situazione più critica che può presentarsi è:
0 - 1
che dà come risultato 1 con prestito di 1 dalle colonne successive; nel
secondo caso, in presenza di un prestito pari a 1 richiesto dalla colonna
precedente, la situazione più critica che può presentarsi è:
0 - 1 - 1
che dà come risultato 0 con prestito di 1 dalle colonne successive.
In qualsiasi base numerica quindi, il prestito massimo non può superare 1; per
gestire questi due casi possibili vengono impiegati due circuiti chiamati Half
Subtractor e Full Subtractor.
La Figura 6.10a illustra la tabella della verità di un Half Subtractor
(H.S.) o semisottrattore binario; la Figura 6.10b illustra la R.C. che
permette di implementare la sottrazione tra due bit.
L'H.S. calcola la differenza tra i due bit che nella sottrazione occupano la prima
colonna (LSB) e produce quindi come risultato un valore a 1 bit compreso
tra 0b e 1b; nella tabella della verità indichiamo questo bit con D.
Il bit D rappresenta una delle cifre del risultato finale (in questo caso si tratta
del LSB); indichiamo poi con P il bit che rappresenta il prestito (0
o 1) richiesto alle colonne successive.
Osservando la tabella della verità dell'H.S. in Figura 6.10a, si vede subito che il
contenuto della colonna D coincide esattamente con i livelli logici in uscita da
una porta EX-OR in seguito alla operazione:
A EX-OR B
Notiamo anche che il contenuto della colonna P (prestito richiesto alle colonne
successive), coincide esattamente con i livelli logici forniti in uscita da una porta
AND in seguito alla operazione:
NOT(A) AND B
In base a questi risultati, siamo in grado di definire le due funzioni che caratterizzano
l'H.S. e che sono:
D = A EX-OR B
e
P = NOT(A) AND B
La Figura 6.10b illustra la R.C. che soddisfa la tabella della verità di Figura
6.10a; possiamo dire quindi che l'H.S. ha bisogno di due ingressi che rappresentano
i due bit da sottrarre e di due uscite che rappresentano una delle cifre del risultato
finale (in questo caso si tratta dell'LSB) e il prestito richiesto alle colonne
successive della sottrazione.
La Figura 6.11a illustra la tabella della verità di un Full Subtractor (F.S.)
o sottrattore binario; la Figura 6.11b illustra la R.C. che permette di implementare
la differenza tra due bit tenendo conto della presenza di un eventuale prestito.
Il F.S. calcola la differenza tra i due bit che nella sottrazione occupano una
tra le colonne successive alla prima e produce quindi come risultato un valore a 1
bit compreso tra 0b e 1b; nella tabella della verità indichiamo questo bit
con D. Il bit D rappresenta una delle cifre del risultato finale; indichiamo
poi con P il bit che rappresenta il prestito (0 o 1) richiesto alle
colonne successive.
Supponiamo di dover calcolare la differenza tra i bit della seconda colonna di una sottrazione
e indichiamo con P0 il prestito precedente, con A1 e B1 i due bit da
sottrarre, con D1 la cifra meno significativa della sottrazione A1-B1 e con
P1 il prestito richiesto da A1-B1 alle colonne successive; la tabella della
verità di Figura 6.11a è formata da 8 righe in quanto per ciascuno dei 4 possibili
risultati della differenza A1-B1, dobbiamo considerare i due casi P0=0 e
P0=1 per un totale di 4x2=8 casi distinti.
Anche in questo caso le funzioni booleane che realizzano il F.S. possono essere
ottenute facilmente ricordando la tecnica che si utilizza per eseguire le sottrazioni con
carta e penna; in relazione alle colonne successive alla prima, possiamo iniziare dalla
sottrazione tra le due cifre in colonna, dopo di che sottraiamo al risultato ottenuto il
prestito precedente.
Queste due sottrazioni consecutive suggeriscono l'idea di utilizzare per il F.S.,
due H.S. in serie come si vede in Figura 6.11b; il primo H.S. sottrae i due bit
in colonna (A1-B1) producendo in uscita i risultati parziali D' e P',
mentre il secondo H.S. sottrae D' con il prestito precedente P0
producendo a sua volta i risultati parziali D'' e P''. Alla fine D''
rappresenta la cifra D1 del risultato finale, mentre i due prestiti P' e
P'' devono fornirci il riporto P1 richiesto alle colonne successive.
Come si verifica facilmente dal circuito di Figura 6.11b, i due prestiti P' e
P'' non possono mai essere entrambi a livello logico 1 (il prestito massimo,
infatti, è 1) e quindi basta applicarli ai due ingressi di una porta OR per
ottenere il prestito P1 richiesto alle colonne successive.
Possiamo dire quindi che il F.S. ha bisogno di tre ingressi che rappresentano i
due bit da sottrarre e il prestito richiesto dalla colonna precedente; le due uscite del
F.S. forniscono, invece, una delle cifre del risultato finale e il prestito richiesto
alle colonne successive.
Una volta definiti l'H.S. e il F.S., possiamo realizzare circuiti per
sottrarre numeri binari di qualsiasi dimensione; lo schema a blocchi di Figura 6.12 mostra
una R.C. per la sottrazione tra numeri binari a 4 bit.
La R.C. di Figura 6.12 utilizza un H.S. e tre F.S.; l'H.S. sottrae
i bit della prima colonna (LSB), mentre i tre F.S. sottraggono i bit delle tre
colonne successive. Il prestito finale P3 contiene il valore (0 o 1)
che verrà assegnato al Carry Flag; come già sappiamo, grazie a CF possiamo
sottrarre via software numeri binari di qualsiasi ampiezza in bit.
Un computer con architettura, ad esempio, a 16 bit, sarà dotato di circuiti in grado
di sottrarre via hardware numeri a 16 bit; se vogliamo sottrarre tra loro numeri a
32 bit, dobbiamo procedere via software suddividendo i bit del minuendo e del
sottraendo in gruppi da 16.
Nei precedenti capitoli abbiamo anche appurato che grazie all'aritmetica modulare, una
R.C. come quella di Figura 6.12 può sottrarre indifferentemente numeri con o senza
segno; alla fine della sottrazione, se stiamo operando sui numeri senza segno dobbiamo
consultare CF per conoscere il prestito finale, mentre se stiamo operando sui
numeri con segno dobbiamo consultare OF e SF per sapere se il risultato è
valido e per individuarne il segno. Come si può facilmente verificare attraverso la
Figura 6.12, si ha:
CF = P3, SF = D3, OF = (A3 EX-NOR B3) AND (A3 EX-OR B3 EX-OR D3)
6.5 Circuiti complementatori
Nella precedente sezione sono stati descritti i circuiti che eseguono la sottrazione tra
numeri binari; nella realizzazione pratica di questi circuiti può essere seguita anche
un'altra strada. Osserviamo, infatti, che dati i due numeri binari n1 e n2,
possiamo scrivere:
n1 - n2 = n1 + (-n2)
In sostanza, la differenza tra n1 e n2 è pari alla somma tra n1
e l'opposto di n2; come già sappiamo, l'opposto di un numero binario si ottiene
effettuando il complemento a 2 del numero stesso. Il complemento a 2
(NEG) di un numero binario n si ottiene invertendo tutti i suoi bit
(NOT) e sommando 1 al risultato ottenuto; possiamo scrivere quindi:
NEG(n) = NOT(n) + 1
Per realizzare nel modo più semplice il complemento a 1 di un numero, possiamo
utilizzare delle semplici porte NOT che come sappiamo producono in uscita un livello
logico che è l'opposto di quello in ingresso; se entra un segnale alto, esce un segnale
basso, mentre se entra un segnale basso, esce un segnale alto. Quindi se vogliamo
complementare, ad esempio, numeri binari a 16 bit, possiamo utilizzare 16
porte NOT; ciascuna di queste porte inverte uno dei bit del numero da complementare
e alla fine otteniamo in uscita il numero in ingresso con tutti i bit invertiti.
Il circuito complementatore appena descritto è molto semplice; nella pratica però si
segue un'altra strada legata come al solito alla semplificazione circuitale. Invece di
realizzare una R.C. per ogni funzione booleana, si preferisce realizzare delle
R.C. leggermente più complesse, che permettono però di svolgere diverse funzioni
con lo stesso circuito; in Figura 6.13, ad esempio, vediamo un dispositivo a 4 bit che
svolge 4 funzioni differenti a seconda dei livelli logici assunti dai cosiddetti
ingressi di selezione che sono S0 e S1.
Dalla tabella della verità di Figura 6.13 si vede che attribuendo ai due ingressi S0
e S1 i 4=22 possibili valori binari 00b, 01b,
10b e 11b, si possono ottenere in uscita 4 differenti elaborazioni
dello stesso nibble in ingresso. In particolare:
- Per S0 = 0 e S1 = 0 si ottiene in uscita il complemento a
1 del nibble in ingresso
- per S0 = 0 e S1 = 1 si ottiene in uscita lo stesso nibble
in ingresso
- per S0 = 1 e S1 = 0 si ottiene in uscita il nibble 1111b
- per S0 = 1 e S1 = 1 si ottiene in uscita il nibble 0000b
In questi ultimi due casi, il nibble in ingresso è ininfluente; con lo stesso circuito
quindi realizziamo 4 funzioni differenti che altrimenti avrebbero richiesto 4
circuiti diversi.
I circuiti complementatori sono molto importanti in quanto vengono largamente utilizzati
dalla CPU in molte operazioni; ricordiamo, ad esempio, che la CPU esegue
moltiplicazioni e divisioni solo sui numeri senza segno. Eventuali numeri negativi
vengono prima di tutto convertiti nei corrispondenti numeri positivi; per poter
effettuare queste conversioni vengono appunto utilizzati i circuiti complementatori.
6.6 Moltiplicazione tra numeri binari
Nei precedenti capitoli è stato detto che la CPU effettua la moltiplicazione
attraverso un metodo fortemente basato sullo stesso procedimento che utilizziamo noi
esseri umani quando eseguiamo questa operazione con carta e penna; abbiamo anche visto
che la moltiplicazione provoca un cambiamento di modulo, per cui la CPU ha
bisogno di sapere se vogliamo operare nell'insieme dei numeri senza segno o nell'insieme
dei numeri con segno.
Moltiplicando tra loro due numeri binari a n bit, otteniamo un risultato
interamente rappresentabile attraverso un numero binario a 2n bit; partiamo
allora dai numeri interi senza segno a 4 bit (1 nibble), e supponiamo di
voler calcolare, ad esempio:
10 x 13 = 130
Traducendo tutto in binario e applicando lo stesso procedimento che si segue con
carta e penna, otteniamo la situazione mostrata in Figura 6.14.
Osserviamo che moltiplicando tra loro due numeri a 4 bit, otteniamo un risultato
che richiede al massimo 8 bit; la CPU tiene conto di questo aspetto e
quindi è impossibile che la moltiplicazione possa provocare un overflow.
Notiamo anche l'estrema semplicità del calcolo dei prodotti parziali; considerando il
primo fattore 1010b, si possono presentare solamente i due casi seguenti:
a) 1010b x 0b = 0000b
b) 1010b x 1b = 1010b
La Figura 6.15 mostra in forma simbolica la moltiplicazione tra i due nibble A3A2A1A0
e B3B2B1B0.
Cominciamo con l'osservare che le singole cifre dei prodotti parziali non sono altro
che moltiplicazioni tra singoli bit; nel precedente capitolo abbiamo visto che il
prodotto binario è ottenibile attraverso una porta AND a 2 ingressi.
Nel caso di Figura 6.15 abbiamo, ad esempio:
A3 x B2 = A3 AND B2
Osserviamo ora che la cifra P0 del prodotto finale (cifra meno significativa)
si ottiene direttamente da A0xB0; abbiamo quindi:
P0 = A0 AND B0
La cifra P1 del prodotto finale è data da:
P1 = (A0 AND B1) + (A1 AND B0)
Per svolgere questa somma utilizziamo un H.A. che produce in uscita la cifra
P1 e il riporto C1 destinato alla colonna successiva.
La cifra P2 del prodotto finale è data da:
P2 = (A0 AND B2) + (A1 AND B1) + (A2 AND B0) + C1
Per svolgere la prima somma (A0 AND B2) + (A1 AND B1) utilizziamo un H.A.
che produce in uscita la somma parziale P2' e il riporto C2' destinato alla
colonna successiva; per svolgere la seconda somma tra P2' e (A2 AND B0)
utilizziamo un F.A. che riceve in ingresso anche il riporto C1 derivante
dal calcolo di P1 e produce in uscita la cifra P2 e il riporto C2''
destinato alla colonna successiva.
La cifra P3 del prodotto finale è data da:
P3 = (A0 AND B3) + (A1 AND B2) + (A2 AND B1) + (A3 AND B0) + C2' + C2''
Per svolgere la prima somma (A0 AND B3) + (A1 AND B2) utilizziamo un H.A.
che produce in uscita la somma parziale P3' e il riporto C3' destinato alla
colonna successiva; per svolgere la seconda somma tra P3' e (A2 AND B1)
utilizziamo un F.A. che riceve in ingresso anche il riporto C2' derivante
dal calcolo di P2, e produce in uscita la somma parziale P3'' e il riporto
C3'' destinato alla colonna successiva. Per svolgere la terza somma tra P3''
e (A3 AND B0) utilizziamo un F.A. che riceve in ingresso anche il riporto
C2'' derivante dal calcolo di P2 e produce in uscita la cifra P3 e
il riporto C3''' destinato alla colonna successiva.
La cifra P4 del prodotto finale è data da:
P4 = (A1 AND B3) + (A2 AND B2) + (A3 AND B1) + C3' + C3'' + C3'''
Per svolgere la prima somma (A1 AND B3) + (A2 AND B2) utilizziamo un F.A.
che riceve in ingresso anche il riporto C3' derivante dal calcolo di P3 e
produce in uscita la somma parziale P4' e il riporto C4' destinato alla
colonna successiva; per svolgere la seconda somma tra P4' e (A3 AND B1)
utilizziamo un F.A. che riceve in ingresso anche il riporto C3'' derivante dal
calcolo di P3 e produce in uscita la somma parziale P4'' e il riporto
C4'' destinato alla colonna successiva. A questo punto utilizziamo un H.A.
per sommare P4'' con il riporto C3''' derivante dal calcolo di P3;
in questo modo otteniamo in uscita dall'H.A. la cifra P4 e il riporto
C4''' destinato alla colonna successiva.
La cifra P5 del prodotto finale è data da:
P5 = (A2 AND B3) + (A3 AND B2) + C4' + C4'' + C4'''
Per svolgere questa somma utilizziamo un F.A. che riceve in ingresso anche il riporto
C4' derivante dal calcolo di P4 e produce in uscita la somma parziale
P5' e il riporto C5' destinato alla colonna successiva. A questo punto
utilizziamo un F.A. per sommare P5' con i riporti C4'' e C4'''
derivanti dal calcolo di P4; in questo modo otteniamo in uscita dal F.A. la
cifra P5 e il riporto C5'' destinato alla colonna successiva.
La cifra P6 del prodotto finale è data da:
P6 = (A3 AND B3) + C5' + C5''
Utilizziamo allora un F.A. che riceve in ingresso anche i riporti C5'
e C5'' derivanti dal calcolo di P5; in questo modo otteniamo in uscita dal
F.A. la cifra P6 e il riporto C6 destinato alla colonna successiva.
Naturalmente, il riporto C6 non è altro che la cifra P7 del prodotto
finale; quest'ultimo passaggio conferma che nella moltiplicazione l'overflow è
impossibile.
Applicando alla lettera i concetti appena esposti, otteniamo la R.C. di Figura 6.16
che permette di moltiplicare numeri interi senza segno a 4 bit.
La R.C. appena descritta, pur essendo perfettamente funzionante, ha un valore
puramente teorico; nella pratica, infatti, si utilizzano R.C. che pur essendo
basate sul circuito di Figura 6.16, presentano notevoli ottimizzazioni che hanno lo scopo
di aumentare la velocità di calcolo. In ogni caso appare chiaro il fatto che queste
ottimizzazioni, per quanto spinte possano essere, danno luogo ad R.C. nettamente
più complesse di quelle utilizzate dalla CPU per le addizioni e per le
sottrazioni; proprio per questo motivo, la moltiplicazione impone alla CPU un
tempo di calcolo nettamente superiore a quello necessario per l'esecuzione delle
addizioni e delle sottrazioni.
La R.C. di Figura 6.16 può essere utilizzata anche per la moltiplicazione tra
numeri interi con segno; a tale proposito bisogna prima di tutto cambiare di segno gli
eventuali fattori negativi. In teoria questo metodo funziona, ma la R.C. che ne
deriva tende a diventare molto più complessa di quella di Figura 6.16; proprio per
questo motivo, molte CPU utilizzano R.C. appositamente concepite per
operare direttamente sui numeri interi con segno.
6.7 Divisione tra numeri binari
Anche la divisione viene effettuata dalla CPU attraverso un metodo basato sullo
stesso procedimento che utilizziamo noi esseri umani quando eseguiamo questa operazione
con carta e penna; come viene mostrato in seguito, l'algoritmo utilizzato dalla
CPU comporta un cambiamento di modulo, per cui è necessario specificare se
vogliamo operare nell'insieme dei numeri senza segno o nell'insieme dei numeri con segno.
Dividendo tra loro due numeri binari a n bit, otteniamo un quoziente minore o
uguale al dividendo e quindi interamente rappresentabile attraverso un numero binario
a n bit; il resto, inoltre, è minore o uguale al divisore, per cui anch'esso
è interamente rappresentabile attraverso un numero binario a n bit.
Partiamo come al solito dai numeri interi senza segno a 4 bit (1 nibble)
e supponiamo di voler calcolare, ad esempio:
14 / 5 = [Q = 2, R = 4]
Traducendo tutto in binario e applicando lo stesso procedimento che si segue con
carta e penna, otteniamo la situazione mostrata in Figura 6.17.
Analizzando la Figura 6.17 possiamo riassumere i passi necessari per il classico algoritmo
di calcolo della divisione:
- A partire da sinistra si seleziona nel dividendo un numero di bit pari al
numero di bit del divisore; la sequenza di bit così ottenuta rappresenta il
resto parziale della divisione.
- Se è possibile sottrarre il divisore dal resto parziale senza chiedere un
prestito, si aggiunge un bit di valore 1 alla destra del quoziente
parziale; il risultato della sottrazione rappresenta il nuovo resto parziale.
Se, invece, non è possibile sottrarre il divisore dal resto parziale senza
chiedere un prestito, si aggiunge un bit di valore 0 alla destra del
quoziente parziale.
- Se è possibile, si aggiunge alla destra del resto parziale un nuovo bit del
dividendo e si ricomincia dal punto 2; se, invece, non ci sono altri
bit del dividendo da aggiungere al resto parziale, la divisione è terminata
con quoziente finale pari all'ultimo quoziente parziale e resto finale pari
all'ultimo resto parziale.
Per poter realizzare un circuito logico capace di effettuare l'operazione di divisione,
dobbiamo elaborare il precedente algoritmo in modo da renderlo generale e ripetitivo;
dobbiamo cioè fare in modo che la CPU calcoli la divisione ripetendo (iterando)
uno stesso procedimento per un determinato numero di volte. Per raggiungere questo
obiettivo si utilizza un algoritmo perfettamente equivalente a quello appena illustrato;
in riferimento come al solito ai numeri interi senza segno a 4 bit, i passi da
compiere sono i seguenti:
- Si aggiungono quattro 0 alla sinistra del dividendo che viene così
convertito in un numero a 8 bit; il numero così ottenuto rappresenta
il resto parziale della divisione.
- Si aggiungono quattro 0 alla destra del divisore che viene così
convertito in un numero a 8 bit; le cifre del divisore in sostanza
vengono "shiftate" a sinistra di quattro posti.
- Le cifre del divisore vengono "shiftate" a destra di una posizione.
- Si prova a sottrarre il divisore dal resto parziale senza chiedere un
prestito e se ciò è possibile si aggiunge un bit di valore 1 alla
destra del quoziente parziale, mentre il risultato della sottrazione
rappresenta il nuovo resto parziale; se, invece, la sottrazione non è
possibile, si aggiunge un bit di valore 0 alla destra del quoziente
parziale.
- Si salta al punto 3 e si ripete questo procedimento per 4
volte, pari cioè al numero di bit dei due operandi; l'ultimo quoziente parziale
ottenuto rappresenta il quoziente finale della divisione, mentre l'ultimo resto
parziale ottenuto rappresenta il resto finale della divisione.
Proviamo ad applicare questo algoritmo alla precedente divisione di 1110b per
0101b; il risultato che si ottiene viene mostrato dalla Figura 6.18.
In riferimento quindi ai numeri interi senza segno a 4 bit, possiamo dire che
l'algoritmo appena illustrato consiste nel ripetere per 4 volte uno stesso
procedimento che comprende uno shift di un posto verso destra delle cifre del
divisore e un tentativo di eseguire una sottrazione tra il resto parziale e lo
stesso divisore.
Nel caso della moltiplicazione, abbiamo risolto il problema attraverso una R.C.
relativamente semplice; nel caso della divisione, invece, la situazione si presenta più
complessa. La necessità di dover stabilire se effettuare o meno la sottrazione, rende
necessario un apposito dispositivo capace di prendere una tale decisione; ci serve cioè
un dispositivo derivato dal F.S. e dotato di una linea di controllo attraverso
la quale si possa abilitare o meno la sottrazione. Questo dispositivo prende il nome di
Conditional Subtractor (C.S.) o sottrattore condizionale; lo schema
circuitale del C.S. viene mostrato in Figura 6.19a, mentre lo schema simbolico
viene mostrato in Figura 6.19b.
Come si può notare, la struttura del C.S. è del tutto simile alla struttura
del F.S.; sono presenti, infatti, l'ingresso Ai per il bit del minuendo,
l'ingresso Bi per il bit del sottraendo, l'ingresso Cin (Carry In) per
il prestito richiesto dalla colonna precedente, l'uscita Cout (Carry Out) per
il prestito richiesto alla colonna successiva e l'uscita D per il risultato
finale che nel caso del F.S. è dato da:
D = Ai - (Bi + Cin)
In Figura 6.19 notiamo però anche la presenza della linea Enable che attraversa
il C.S. da Ein a Eout; proprio attraverso questa linea è possibile
modificare il comportamento del C.S.. Come si può vedere in Figura 6.19a,
l'ingresso Ai e l'uscita D' del F.S. rappresentano i 2
ingressi di un MUX da 2 a 1; analizzando allora questo circuito
possiamo dire che:
a) se la linea Enable è a livello logico 0, viene abilitato
l'ingresso D' del MUX, per cui il C.S. calcola:
D = Ai - (Bi + Cin)
b) se la linea Enable è a livello logico 1, viene abilitato
l'ingresso Ai del MUX, per cui il C.S. calcola:
D = Ai
Possiamo dire quindi che se la linea Enable è a livello logico 0,
il C.S. effettua normalmente la sottrazione; se, invece, la linea Enable
è a livello logico 1, si ottiene sull'uscita D il bit Ai del
minuendo. Si tenga presente che in Figura 6.19 la linea Enable non ha niente
a che vedere con il segnale di abilitazione E del MUX di Figura 6.1.
A questo punto si presenta il problema di come pilotare la linea Enable; in
sostanza, la linea Enable deve essere portata a livello logico 0
solo quando il minuendo è maggiore o uguale al sottraendo. Il metodo che viene
utilizzato, consiste nello sfruttare l'uscita Cout che viene collegata
all'ingresso Ein; per capire il funzionamento di questo sistema, analizziamo
in dettaglio il funzionamento del C.S. di Figura 6.19:
- Il C.S. effettua la normale sottrazione D=Ai-(Bi+Cin) e
ottiene, come sappiamo, Cout=0 se non c'è alcun prestito (Ai
maggiore o uguale a Bi); se, invece, si verifica un prestito (Ai
minore di Bi), il C.S. ottiene Cout=1.
- A questo punto, come è stato detto in precedenza, l'uscita Cout
viene collegata a Ein, per cui Ein=Cout=0 conferma il risultato
della sottrazione, mentre Ein=Cout=1 lo annulla e pone D=Ai.
In base alle considerazioni appena esposte, si intuisce facilmente che per ottenere
la sottrazione condizionale tra due numeri binari a 4 bit, basta collegare in
serie 4 C.S.; il circuito che si ottiene viene mostrato in Figura 6.20.
Supponiamo, ad esempio, di avere A=1110b e B=0101b; in questo caso si
vede subito che:
- Il primo C.S. (quello più a destra) pone Cout=1
- Il secondo C.S. pone Cout=0
- Il terzo C.S. pone Cout=0
- Il quarto C.S. pone Cout=0
La linea Enable si porta quindi a livello logico 0 e il risultato
della sottrazione (1001b) viene confermato; se il quarto C.S. avesse
prodotto Cout=1, la linea Enable si sarebbe portata a livello logico
1 annullando il risultato della sottrazione e producendo quindi in uscita
il nibble A=1110b. In quest'ultimo caso è come se il minuendo venisse
ripristinato (restored); proprio per questo motivo, l'algoritmo di calcolo
della divisione con i C.S. prende il nome di restoring division
(divisione con ripristino).
Applicando le considerazioni appena esposte, possiamo implementare finalmente il
circuito per la divisione tra numeri interi senza segno a 4 bit; ricordando
che l'algoritmo esposto in Figura 6.18 prevede un procedimento ripetitivo costituito
da uno shift di un posto verso destra delle cifre del divisore e da un tentativo di
sottrazione tra resto parziale e divisore stesso, si ottiene il circuito mostrato
in Figura 6.21.
Gli ingressi a0, a1, a2, a3, a4, a5 e
a6 contengono i 4 bit del dividendo e rappresentano anche il resto
parziale; naturalmente gli ingressi a4, a5 e a6 vengono portati
a livello logico basso. Gli ingressi b0, b1, b2 e b3
contengono i 4 bit del divisore; come si può notare, il divisore si trova
shiftato a sinistra di 3 posizioni anziché di 4 in quanto (Figura 6.18)
4 shift a sinistra e uno a destra equivalgono a 3 shitf a sinistra.
La riga più in alto dei C.S. esegue la sottrazione tra a6a5a4a3 e
b3b2b1b0; se alla fine non si verifica alcun prestito il risultato della
sottrazione viene confermato, mentre in caso contrario viene ripristinato il
minuendo e il risultato prodotto in uscita è a6a5a4a3. Si nota subito
che l'uscita q3 vale 1 se il minuendo è maggiore o uguale al
sottraendo e 0 in caso contrario; possiamo dire quindi che q3 non è
altro che uno dei 4 bit del quoziente finale.
Le considerazioni appena esposte si applicano naturalmente anche alle altre tre
righe dei C.S.; terminata la divisione, sulle quattro uscite q0,
q1, q2 e q3 preleviamo i 4 bit del quoziente finale,
mentre sulle quattro uscite r0, r1, r2 e r3 preleviamo
i 4 bit del resto finale.
Come al solito, il circuito di Figura 6.21 pur essendo perfettamente funzionante ha un
valore puramente teorico; nella pratica, infatti, si utilizza una versione elaborata e
notevolmente ottimizzata del circuito appena descritto. Come è facile intuire però,
la divisione si presenta come una operazione particolarmente complessa per la
CPU; infatti, la divisione comporta per la CPU un tempo di calcolo
superiore persino a quello necessario per la moltiplicazione (soprattutto nel caso
di restoring delle varie sottrazioni).
Il circuito di Figura 6.21 può essere utilizzato anche per la divisione tra numeri
interi con segno; a tale proposito bisogna prima di tutto cambiare di segno gli
eventuali operandi negativi.
In relazione alla divisione la CPU ha bisogno di distinguere tra numeri con
o senza segno in quanto abbiamo visto che per poter utilizzare il circuito di Figura
6.21, è necessario modificare l'ampiezza in bit del dividendo e del divisore; tutto
ciò dimostra che il metodo di calcolo della divisione utilizzato dalla CPU
provoca un cambiamento di modulo.
6.8 Scorrimento dei bit di un numero binario
Le considerazioni esposte in precedenza ci hanno permesso di constatare che la
moltiplicazione e la divisione sono operazioni particolarmente impegnative per la
CPU e richiedono tempi di calcolo notevolmente superiori a quelli necessari
per l'addizione o per la sottrazione; purtroppo nel caso generale non è possibile
ovviare a questo problema. Nei precedenti capitoli però abbiamo visto che per la
moltiplicazione e la divisione esiste un caso particolare che ci permette di
velocizzare notevolmente queste operazioni; esaminiamo allora questo caso particolare
che è legato ancora una volta alla struttura dei sistemi di numerazione posizionali.
6.8.1 Moltiplicazione di un numero intero binario per una potenza intera del 2
Nel caso, ad esempio, della base 10, abbiamo visto che moltiplicare un numero
intero n per 10 (cioè per la base), equivale ad aggiungere uno zero alla
destra del numero stesso. Tutte le cifre di n scorrono verso sinistra di una
posizione aumentando quindi il loro peso; infatti, la moltiplicazione di n per
10 trasforma le unità di n in decine, le decine in centinaia, le centinaia
in migliaia e così via.
Analogamente, moltiplicare n per 100 (102) equivale ad
aggiungere due zeri alla destra di n facendo scorrere tutte le sue cifre di due
posizioni verso sinistra; moltiplicare in generale n per 10m
equivale ad aggiungere m zeri alla destra di n facendo scorrere tutte le
sue cifre di m posizioni verso sinistra.
Nel caso ancora più generale di un numero intero n espresso in base b
qualunque, moltiplicare n per bm equivale ad aggiungere m
zeri alla destra di n, facendo scorrere tutte le sue cifre di m posizioni
verso sinistra. Possiamo dire allora che, ogni volta che uno dei due fattori può essere
espresso sotto forma di potenza intera della base, la moltiplicazione si traduce in uno
scorrimento verso sinistra delle cifre dell'altro fattore, di un numero di posizioni pari
all'esponente della base; risulta evidente che in questo caso, i calcoli diventano
estremamente semplici e veloci rispetto al metodo tradizionale.
Proprio per questo motivo, abbiamo visto che le CPU della famiglia 80x86
forniscono l'istruzione SHL (Shift Logical Left) per gli scorrimenti verso
sinistra dei bit di un numero intero senza segno e l'istruzione SAL (Shift
Arithmetic Left) per gli scorrimenti verso sinistra dei bit di un numero intero con
segno; siccome però il segno viene codificato nel MSB di un numero binario, le
due istruzioni SHL e SAL sono assolutamente equivalenti (e quindi
interscambiabili).
Per chiarire questi concetti molto importanti, è necessario tenere presente che le
considerazioni appena esposte hanno un valore puramente teorico; in teoria, infatti,
possiamo prendere un numero n e far scorrere i suoi bit di migliaia di
posizioni verso sinistra o verso destra. Nel caso del computer però il discorso
cambia radicalmente; non bisogna dimenticare, infatti, che la CPU gestisce i
numeri binari assegnando a ciascuno di essi una ampiezza fissa in bit. Ogni numero
binario inoltre viene sistemato in una locazione ad esso riservata; consideriamo,
ad esempio, la situazione mostrata in Figura 6.22.
Questa figura mostra una locazione da 8 bit riservata al numero binario
n=00100111b; il numero binario n si trova confinato all'interno della
sua locazione da 8 bit e tutte le modifiche compiute su di esso, devono
produrre un risultato interamente rappresentabile attraverso questi 8 bit.
Analizzando allora la Figura 6.22 si intuisce subito che eccedendo nello scorrimento
verso sinistra dei bit di n, si rischia di ottenere un risultato sbagliato;
il problema che si verifica è dovuto chiaramente al fatto che un numero eccessivo
di scorrimenti può provocare la fuoriuscita (dalla parte sinistra della locazione
di Figura 6.22) di cifre significative per il risultato finale.
Supponiamo, ad esempio, di far scorrere (con SHL o con SAL) ripetutamente
di un posto verso sinistra i bit del numero binario di Figura 6.22; in questo modo
otteniamo la seguente successione:
01001110b, 10011100b, 00111000b, 01110000b, ...
Tutti gli scorrimenti si applicano agli 8 bit della locazione di Figura 6.22 e
devono quindi produrre un risultato interamente contenibile nella locazione stessa;
è chiaro quindi che dopo un certo numero di scorrimenti, comincerà a verificarsi
il trabocco da sinistra di cifre significative del risultato.
Nell'insieme dei numeri senza segno il numero binario 00100111b equivale a
39; il primo scorrimento produce il risultato 78 che è corretto in
quanto rappresenta il prodotto della moltiplicazione di 39 per 2. Il
secondo scorrimento produce il risultato 156 che è corretto in quanto
rappresenta il prodotto della moltiplicazione di 39 per 22;
il terzo scorrimento produce il risultato 56 che è chiaramente sbagliato.
L'errore è dovuto al fatto che moltiplicando 39 per 23
si ottiene un numero a 9 bit che non è più contenibile nella locazione di
Figura 6.22; il bit più significativo del risultato (di valore 1) trabocca
da sinistra e invalida il risultato finale.
Anche nell'insieme dei numeri con segno il numero binario 00100111b equivale
a +39; il primo scorrimento produce il risultato +78 che è corretto in
quanto rappresenta il prodotto della moltiplicazione di +39 per 2. Il
secondo scorrimento produce il risultato -100 che è chiaramente sbagliato;
l'errore questa volta è dovuto al fatto che moltiplicando +39 per
22 si ottiene un numero positivo (+156) più grande di
+127 che è il massimo numero positivo rappresentabile con 8 bit.
Osserviamo, infatti, che il bit di segno che inizialmente valeva 0, è
traboccato da sinistra e il suo posto è stato preso da un bit di valore 1;
il numero n quindi si è trasformato da positivo in negativo.
Le considerazioni appena esposte ci fanno capire che se intendiamo utilizzare
SHL o SAL per calcolare rapidamente il prodotto di un numero binario
per una potenza intera del 2, dobbiamo essere certi della validità del
risultato che verrà fornito da queste istruzioni; in sostanza, dobbiamo verificare
che nella parte sinistra del numero n ci sia un certo "spazio di sicurezza".
Nel caso dei numeri senza segno dobbiamo verificare che nella parte sinistra di
n ci sia un numero sufficiente di zeri; nel caso dei numeri con segno dobbiamo
verificare che nella parte sinistra di n ci sia un numero sufficiente di bit
di segno (0 per i numeri interi positivi e 1 per quelli negativi).
6.8.2 Divisione di un numero intero binario per una potenza intera del 2
Passando alla divisione sappiamo che in base 10, dividendo un numero intero
positivo n per 10, si provoca uno scorrimento di una posizione verso
destra delle cifre di n; la cifra meno significativa di n (cioè quella
più a destra) in seguito allo scorrimento, "trabocca" da destra e viene persa. Tutte
le cifre di n scorrono verso destra di una posizione riducendo quindi il loro
peso; infatti, la divisione di n per 10 trasforma le decine di n in
unità, le centinaia in decine, le migliaia in centinaia e così via. La cifra che
trabocca da destra rappresenta il resto della divisione; abbiamo, ad esempio:
138 / 10 = [Q = 13, R = 8]
Quindi le tre cifre di 138 scorrono verso destra di una posizione facendo uscire
l'8 che rappresenta proprio il resto della divisione.
Analogamente:
138 / 100 = [Q = 1, R = 38]
Le tre cifre di 138 quindi scorrono verso destra di due posizioni facendo uscire
il 38 che anche in questo caso, rappresenta il resto della divisione.
Generalizzando, nel caso di una base b qualunque, dividendo un numero intero
positivo n per bm si provoca uno scorrimento di m
posizioni verso destra delle cifre di n facendo così traboccare le m cifre
più a destra; le cifre rimaste rappresentano il quoziente, mentre le m cifre
traboccate da destra rappresentano il resto.
In definitiva, se il divisore può essere espresso sotto forma di potenza intera della
base, utilizzando questa tecnica si esegue la divisione in modo nettamente più rapido
rispetto al metodo tradizionale; proprio per questo motivo, abbiamo già visto che le
CPU della famiglia 80x86 forniscono l'istruzione SHR (Shift
Logical Right) per gli scorrimenti verso destra dei bit di un numero intero senza
segno e l'istruzione SAR (Shift Arithmetic Right) per gli scorrimenti
verso destra dei bit di un numero intero con segno. Questa volta però le due istruzioni
SHR e SAR non sono equivalenti; la differenza di comportamento tra
SHR e SAR è legata al fatto che nello scorrimento verso destra dei bit di
un numero, è necessario distinguere tra numeri con segno e numeri senza segno.
Osserviamo, infatti, che:
+138 / 10 = [Q = +13, R = +8]
mentre:
-138 / 10 = [Q = -13, R = -8]
Vediamo allora come viene gestita questa situazione da SHR e da SAR; a
tale proposito consideriamo la situazione di Figura 6.23 che rappresenta come al solito
un numero binario contenuto in una locazione da 8 bit:
Nell'insieme dei numeri interi senza segno, il numero binario 10110000b
equivale a 176; utilizziamo allora SHR per far scorrere ripetutamente
di un posto verso destra i bit del numero binario di Figura 23. In questo modo
otteniamo la seguente successione:
01011000b, 00101100b, 00010110b, 00001011b, 00000101b, ...
Il primo scorrimento produce il risultato 88 (con resto 0b) che è
corretto in quanto rappresenta il quoziente della divisione di 176 per
2; il secondo scorrimento produce il risultato 44 (con resto
00b) che è corretto in quanto rappresenta il quoziente della divisione di
176 per 22. Il terzo scorrimento produce il risultato
22 (con resto 000b) che è corretto in quanto rappresenta il quoziente
della divisione di 176 per 23; il quarto scorrimento produce
il risultato 11 (con resto 0000b) che è corretto in quanto rappresenta
il quoziente della divisione di 176 per 24. Il quinto
scorrimento produce il risultato 5 (con resto 10000b) che è corretto
in quanto rappresenta il quoziente della divisione di 176 per
25.
Nel caso generale possiamo dire allora che l'utilizzo di SHR per la divisione
rapida di un numero intero senza segno per una potenza intera del 2, produce
sempre il risultato corretto; la sequenza di cifre che trabocca da destra rappresenta
sempre il resto della divisione appena effettuata. Osserviamo poi che SHR opera
sui numeri interi positivi, per cui riempie con degli zeri i posti che si liberano
a sinistra in seguito allo scorrimento verso destra dei bit di un numero binario; in
questo modo si ottiene sempre un quoziente positivo. È chiaro allora che dopo un
certo numero di scorrimenti verso destra dei bit di un numero positivo n, il
quoziente diventa 0; nel caso, ad esempio, di un numero binario a 8 bit,
dopo 8 scorrimenti verso destra delle cifre di questo numero si ottiene
sicuramente un quoziente nullo.
Nell'insieme dei numeri interi con segno, il numero binario 10110000b di
Figura 6.23 equivale a -80; utilizziamo allora SAR per far scorrere
ripetutamente di un posto verso destra i bit di questo numero binario. In questo
modo otteniamo la seguente successione:
11011000b, 11101100b, 11110110b, 11111011b, 11111101b, ...
Osserviamo subito che SAR riempie con degli 1 i posti che si liberano
a sinistra del numero 10110000b in seguito allo scorrimento delle sue cifre
verso destra; questo perché il MSB di 10110000b è 1 e quindi
SAR deve preservare il bit di segno.
Il primo scorrimento produce il risultato -40 (con resto 0b) che è
corretto in quanto rappresenta il quoziente della divisione di -80 per
2; il secondo scorrimento produce il risultato -20 (con resto
00b) che è corretto in quanto rappresenta il quoziente della divisione di
-80 per 22. Il terzo scorrimento produce il risultato
-10 (con resto 000b) che è corretto in quanto rappresenta il quoziente
della divisione di -80 per 23; il quarto scorrimento produce
il risultato -5 (con resto 0000b) che è corretto in quanto rappresenta
il quoziente della divisione di -80 per 24. Il quinto
scorrimento produce il risultato -3 (con resto 10000b) che è però
sbagliato in quanto dividendo -80 per 25 si dovrebbe
ottenere il quoziente -2 e il resto -16!
Dall'analisi dei risultati ottenuti con SHR e con SAR emerge allora
che:
- Nel caso dei numeri interi senza segno, SHR produce sempre il
quoziente e il resto corretti.
- Nel caso dei numeri interi con segno positivi, SAR produce
sempre il quoziente e il resto corretti.
- Nel caso dei numeri interi con segno negativi, SAR produce
il quoziente e il resto corretti solamente quando il resto è zero; se, invece, il
resto è diverso da zero, il quoziente ottenuto appare sommato a -1.
Per capire il perché di questa situazione, bisogna tenere presente che la
CPU, nell'eseguire una divisione intera tra due numeri interi con o
senza segno, arrotonda sempre il quoziente verso lo zero; questo comportamento
è corretto in quanto rispetta le regole matematiche della divisione intera.
Ad esempio, il quoziente +4.9 deve essere arrotondato a +4 e
non a +5; analogamente, il quoziente -6.8 deve essere
arrotondato a -6 e non a -7!
Con l'istruzione SHR, questa regola matematica viene sempre rispettata;
ciò è dovuto al metodo utilizzato dalla CPU, per codificare i numeri
interi senza segno.
In riferimento, ad esempio, ai numeri interi senza segno a 8 bit,
sappiamo che la CPU utilizza le codifiche binarie comprese tra
00000000b e 11111111b, per rappresentare i numeri interi senza
segno compresi, rispettivamente, tra 0 e 255; come si può
notare, i numeri interi senza segno crescenti (0, 1, 2,
etc) hanno una codifica binaria crescente (00000000b, 00000001b,
00000010b, etc).
La conseguenza pratica di tutto ciò è data dal fatto che, utilizzando
l'istruzione SHR per far scorrere di n posti verso destra le
cifre di un numero intero senza segno m, si ottiene sempre un risultato
arrotondato verso lo zero; tale risultato, coincide quindi con il quoziente
della divisione intera tra m e 2n e inoltre, le
n cifre traboccate da destra, coincidono con il resto della divisione
stessa!
Con l'istruzione SAR, questa regola matematica non viene sempre
rispettata; ciò è dovuto al metodo utilizzato dalla CPU, per
codificare i numeri interi con segno (codifica binaria in complemento a
2).
In riferimento, ad esempio, ai numeri interi con segno a 8 bit,
sappiamo che:
- Le codifiche binarie comprese tra 00000000b e 01111111b,
rappresentano i numeri interi positivi compresi, rispettivamente, tra 0
e +127.
- Le codifiche binarie comprese tra 10000000b e 11111111b,
rappresentano i numeri interi negativi compresi, rispettivamente, tra -128
e -1.
Come si può notare, i numeri interi positivi crescenti (+0, +1,
+2, etc), hanno una codifica binaria crescente (00000000b,
00000001b, 00000010b, etc); invece, i numeri interi negativi
crescenti in valore assoluto (|-1|, |-2|, |-3|, etc), hanno
una codifica binaria decrescente (11111111b, 11111110b,
11111101b, etc)!
La conseguenza pratica di tutto ciò è data dal fatto che, utilizzando
l'istruzione SAR per far scorrere di n posti verso destra le
cifre di un numero intero positivo m, si ottiene sempre un risultato
arrotondato verso lo zero; tale risultato, coincide quindi con il quoziente
della divisione intera tra m e 2n e, inoltre, le
n cifre traboccate da destra, coincidono con il resto della divisione
stessa.
Utilizzando l'istruzione SAR per far scorrere di n posti verso
destra le cifre di un numero intero negativo m, si ottiene sempre un
risultato arrotondato verso l'infinito negativo (-∞); ad esempio,
il risultato -5.3 viene arrotondato a -6 anziché a -5.
Se m è un numero intero negativo multiplo intero di
2n, si ottiene un risultato che coincide con il quoziente
della divisione intera tra m e 2n; infatti, in questo
caso il resto è zero e quindi il quoziente non necessita di nessun
arrotondamento (quoziente esatto).
Se m è un numero intero negativo non divisibile per 2n,
si ottiene un risultato che non coincide con il quoziente della divisione intera
tra m e 2n; infatti, in questo caso il resto è diverso
da zero e quindi il quoziente necessita di un arrotondamento. Mentre però la
divisione arrotonda il suo quoziente verso lo zero, l'istruzione SAR
arrotonda il suo risultato verso l'infinito negativo (ad esempio, il valore
-9.3 viene arrotondato a -9 dalla divisione e a -10 da
SAR); in tal caso, il risultato prodotto da SAR differisce del
valore -1 rispetto al quoziente della divisione!
I problemi appena illustrati, inducono decisamente ad evitare l'uso degli
scorrimenti aritmetici a destra per dividere rapidamente numeri negativi per
potenze intere del 2; appare molto più sensato, in questo caso,
ricorrere alla divisione tradizionale che comporta però, come sappiamo, una
notevole lentezza di esecuzione.
6.8.3 Verifica del risultato attraverso i flags
Come accade per tutte le altre operazioni logico aritmetiche, la CPU utilizza
i vari flags per fornire al programmatore tutti i dettagli sul risultato di una
operazione appena eseguita; analizziamo innanzi tutto le informazioni che ci
vengono fornite attraverso CF, SF e OF in relazione al risultato
prodotto dalle istruzioni SHL, SAL, SHR e SAR applicate
ad un numero binario n.
Il flag CF contiene sempre l'ultimo bit traboccato da n (da destra o
da sinistra); se, ad esempio, facciamo scorrere di un posto verso destra i bit di
01001101b, si ottiene CF=1 in quanto il bit traboccato da destra ha
valore 1.
Il flag SF contiene sempre il MSB del risultato appena ottenuto;
se, ad esempio, facciamo scorrere di un posto verso sinistra i bit di 01001101b,
si ottiene il risultato 10011010b, e quindi SF=1 in quanto il MSB
ha assunto il valore 1.
Il flag OF si porta a 1 ogni volta che il MSB del risultato
appena ottenuto cambia da 0 a 1 o da 1 a 0; se, ad esempio,
facciamo scorrere di un posto verso sinistra i bit di 01001101b, si ottiene il
risultato 10011010b e quindi OF=1 in quanto il valore del MSB
è cambiato da 0 a 1.
6.8.4 Esempio di shifter a 4 bit
Vediamo ora come può essere implementata una R.C. capace di effettuare lo
scorrimento dei bit di un numero binario; la Figura 6.24 illustra uno "shifter"
a 4 bit capace di implementare tutte le 4 istruzioni SHL,
SAL, SHR e SAR.
Indichiamo con A0, A1, A2 e A3 i 4 bit del nibble in
ingresso e con Y0, Y1, Y2 e Y3 i 4 bit del nibble
in uscita; abbiamo poi l'uscita CF che permette di inviare al Carry Flag
il bit appena scartato, e infine i seguenti tre ingressi di abilitazione:
- SHL che abilita lo scorrimento logico a sinistra (equivalente a SAL)
- SHR che abilita lo scorrimento logico a destra
- SAR che in congiunzione con SHR abilita lo scorrimento aritmetico a destra
Come si può constatare attraverso la Figura 6.24 si ha, inoltre:
SF = Y3, OF = A3 EX-NOR Y3
Ponendo in Figura 6.24 SHL=1, SHR=0 e SAR=0, si abilita la
R.C. allo scorrimento logico/aritmetico verso sinistra dei bit del nibble in
ingresso; come già sappiamo, questa operazione produce effetti identici, sia sui
numeri senza segno, sia sui numeri con segno (SHL=SAL).
Ponendo in Figura 6.24 SHL=0, SHR=1 e SAR=0, si abilita la
R.C. allo scorrimento logico verso destra dei bit del nibble in ingresso; il
nibble in ingresso deve essere quindi un numero senza segno.
Ponendo in Figura 6.24 SHL=0, SHR=1 e SAR=1, si abilita la
R.C. allo scorrimento aritmetico verso destra dei bit del nibble in ingresso;
il nibble in ingresso deve essere quindi un numero con segno.
In relazione agli scorrimenti dei bit di un numero binario, è necessario sottolineare
un aspetto molto importante; come si può facilmente intuire, più aumenta il numero di
shift da effettuare, più aumentano i tempi per l'esecuzione di questa operazione da
parte della CPU. È chiaro allora che al crescere del numero di shift da
effettuare diminuiscono i vantaggi di questa tecnica rispetto alle moltiplicazioni e
divisioni tradizionali.
6.9 Rotazione dei bit di un numero binario
In conclusione di questo capitolo, citiamo anche l'operazione di rotazione dei bit
di un numero binario; questa operazione è simile a quella di scorrimento dei bit,
con la differenza però che i bit che traboccano da una estremità dell'operando,
rientrano in sequenza dall'altra estremità dell'operando stesso. Appare evidente che
in relazione alla operazione di rotazione dei bit, non ha alcun senso la distinzione
tra numeri senza segno e numeri con segno.
Le CPU della famiglia 80x86 mettono a disposizione 4 istruzioni
per la rotazione dei bit, che sono: ROL, RCL, ROR, RCR.
L'istruzione ROL (Rotate Left) fa scorrere verso sinistra i bit di un
numero binario; i bit che traboccano da sinistra rientrano in sequenza da destra. In
Figura 6.24 si può simulare l'istruzione ROL abilitando l'ingresso SHL
e collegando l'uscita Y0 all'uscita CF.
L'istruzione RCL (Rotate Through Carry Left) fa scorrere verso sinistra
i bit di un numero binario; i bit che traboccano da sinistra finiscono in sequenza nel
flag CF, mentre i vecchi bit contenuti in CF rientrano in sequenza dalla
destra del numero binario. In Figura 6.24 si può simulare l'istruzione RCL
abilitando l'ingresso SHL e memorizzando in Y0 il vecchio contenuto di
CF (prima di ogni shitf).
L'istruzione ROR (Rotate Right) fa scorrere verso destra i bit di un
numero binario; i bit che traboccano da destra rientrano in sequenza da sinistra. In
Figura 6.24 si può simulare l'istruzione ROR abilitando l'ingresso SHR
e collegando l'uscita Y3 all'uscita CF.
L'istruzione RCR (Rotate Through Carry Right) fa scorrere verso destra
i bit di un numero binario; i bit che traboccano da destra finiscono in sequenza nel
flag CF, mentre i vecchi bit contenuti in CF rientrano in sequenza dalla
sinistra del numero binario. In Figura 6.24 si può simulare l'istruzione RCR
abilitando l'ingresso SHR e memorizzando in Y3 il vecchio contenuto di
CF (prima di ogni shitf).
Dalle considerazioni appena esposte si intuisce che la R.C. di Figura 6.23
può essere facilmente modificata per poter implementare, sia le istruzioni di
scorrimento, sia le istruzioni di rotazione dei bit; nella pratica si segue proprio
questa strada che permette di ottenere un notevole risparmio di porte logiche.