Circuiti numerici Passiamo ora a vedere come, grazie all'uso di tecniche di codifica binaria, possiamo definire dei circuiti booleani in grado di effettuare delle manipolazioni di tipo numerico su rappresentazioni di numeri. In generale il metodo consiste nel definire un algoritmo di manipolazione su rappresentazioni binarie finite. La definizione viene data sotto forma esaustiva mediante la definizione della tavola di verità. La correttezza della realizzazione dipende dall'uso della tecnica di codifica assunta per la progettazione del circuito. Sommatori Cominciamo a considerare il caso semplice di rappresentazioni binarie di numeri su un solo bit (ossia numeri interi compresi tra 0 e 1). Notiamo subito che la somma dei valori 1+1=2 genera un valore di uscita non rappresentabile in forma binaria su un bit; per consentire la rappresentazione del risultato occorre infatti che il risultato venga rappresentato con almeno un bit in più rispetto al numero di bit usati per la rappresentazione degli addendi (in modo da poter codificare un valore massimo almeno doppio rispetto al massimo valore dei due addendi). La tavola di verità che specifica il comportamento di un tale circuito addizionatore é quindi la seguente: s1: a b U1 U0 0 0 0 0 0 1 0 1 1 0 0 1 1 1 1 0 Possiamo notare che la colonna u0 corrisponde alla tavola di verità della funzione XOR, mentre la colonna u1 corrisponde a quella della funzione AND. Le due uscite del circuito possono quindi essere ottenute mediante l'uso di queste due funzioni elementari applicate alla stessa coppia di ingressi a e b. Un tale circuito con due ingressi e due uscite viene normalmente chiamato half adder (semi addizionatore), per il motivo che scopriremo tra poco. Passiamo ora al caso di somma tra due numeri rappresentati su 2 bit (con risultato rappresentato su 3 bit). Il funzionamento del circuito, usando la normale codifica binaria, può essere definito mediante la seguente mappa: \ b1 | 0 : 0 : 1 : 1 | \ b0 | 0 : 1 : 0 : 1 | a1 a0 \ | : : : | --------\--+-----:-----:-----:-----+ 0 0 | 000 : 001 : 010 : 011 | ..........|.....:.....:.....:.....| 0 1 | 001 : 010 : 011 : 100 | ..........|.....:.....:.....:.....| 1 0 | 010 : 011 : 100 : 101 | ..........|.....:.....:.....:.....| 1 1 | 011 : 100 : 101 : 110 | -----------+-----:-----:-----:-----+ La realizzazione diretta di tale circuito risulta evidentemente molto più complessa di quella di un semi addizionatore. In oltre, la complessità di realizzazione diventa rapidamente proibitiva se aumentiamo ulteriormente il numero di bit di rappresentazione per gli addendi e per il risultato. La soluzione che consente di realizzare in modo semplice ed efficiente circuiti addizionatori per rappresentazioni su due o più bit consiste nel modularizzare la realizzazione. In questo caso la corretta modularizzazione può essere individuata facendo ricorso all'algoritmo di somma cifra per cifra: ad ogni passo consideriamo una sola cifra dell'addendo a e una sola cifra dell'addendo b, oltre all'eventuale cifra "di riporto" derivante dall'applicazione dello stesso algoritmo alle cifre precedenti. Quindi ad ogni passo operiamo su rappresentazioni binarie di un solo bit; l'unica differenza rispetto al circuito semi sommatore visto in precedenza é che ora dobbiamo considerare la presenza di 3 addendi da un bit (a, b e la cifra di riporto c). La specifica di un circuito in grado di effettuare tale somma (chiamato full adder, o sommatore completo a 1 bit) é data dalla seguente tavola di verità: s2: a b c r u 0 0 0 0 0 0 0 1 0 1 0 1 0 0 1 0 1 1 1 0 1 0 0 0 1 1 0 1 1 0 1 1 0 1 0 1 1 1 1 1 L'uscita u corrisponde ad una funzione XOR a tre ingressi, mentre l'uscita r deve essere generata da un circuito in grado di riconoscere la condizione "almeno due ingressi assumono il valore 1". Indichiamo in modo compatto un circuito di tipo fulladder mediante il seguente simbolo: Tali moduli possono essere combinati in modo da realizzare l'operazione di somma tra rappresentazioni binarie su più cifre, usando un modulo per ogni cifra; ciascun modulo genera una cifra di "riporto" verso il modulo successivo. In figura viene riportato lo schema di connessione di due moduli per formare un addizionatore per addendi rappresentati su due bit (e risultato rappresentato su 3 bit). Notiamo che questa realizzazione di un circuito addizionatore su N bit presenta vantaggi e svantaggi rispetto ad una realizzazione diretta mediante logica a 3 livelli dalla tavola di verità (ammesso che questa fosse possibile anche per grandi valori di N). Il vantaggio principale (derivante dalla modularità della realizzazione) é che la complessità della realizzazione cresce linearmente all'aumentare del numero N di bit di rappresentazione degli addendi (in quanto l'aggiunta di una cifra comporta esattamente l'aggiunta di un modulo full-adder). Lo svantaggio principale é dovuto alla tecnica di propagazione del riporto (chiamata ripple carry). Questo fa sì che anche il tempo di assestamento del risultato a seguito di una variazione dei valori in ingresso cresca linearmente al crescere del numero N di bit della rappresentazione degli addendi. In particolare, notiamo che il tempo di assestamento per la cifra più significativa sarà N volte più grande di quello per la cifra meno significativa, a causa della possibile propagazione del riporto attraverso tutti i moduli full-adder. Questo problema in pratica viene risolto con l'aggiunta di ulteriori circuiti combinatori realizzati in logica a 3 livelli per la "previsione delle cifre di riporto" (carry lookahead). Per esempio, la cifra binaria più a sinistra nelle caselle della mappa del circuito sommatore a 2 bit può essere usata per specificare un circuito di previsione del riporto sulla terza cifra. Una possibile realizzazione della funzione di previsione del riporto sulla terza cifra in logica a due livelli (nessuna variabile appare in forma negata in questo esempio) potrebbe quindi essere definita dalla formula (normale disgiuntiva): (a1·b1)+(a0·a1·b0)+(a0·b1·b0). Notiamo infine che un circuito sommatore su N bit progettato per operare su rappresentazioni binarie di numeri senza segno può essere usato anche per operare su rappresentazioni di numeri con segno in complemento a 2 su N bit. L'unica accortezza che occorre osservare in tal caso é quella di definire il risultato su N bit (senza considerare l'N+1-esimo bit di riporto), e quindi di accertarsi che il risultato di una operazione di somma tra numeri dello stesso segno non generi condizioni di overflow. Un caso particolare di dispositivo sommatore é costituito dal caso di un "incrementatore" che somma un valore costante K ad un addendo variabile A. Nella maggior parte dei casi ci si riduce a voler sommare la costante K=1 ad un valore arbitrario in ingresso. In tal caso il dispositivo può essere realizzato in modo molto semplice usando N moduli di tipo "half-adder" (quello sulla vifra meno significativa somma 1 ad a0, quelli sulle cifre successive sommano ai+ri). In questo caso anche la funzione "carry lookahead" può essere estremamente semplificata: ri = a(i-1) · a(i-2) · ... · a0