Un ALU, acronimo di Arithmetic Logic Unit ovvero Unità Aritmetico-Logica, ovvero una scatola che consente di eseguire sia operazioni logiche che aritmetiche.
La nostra ALU consentirà di effettuare le operazioni di AND, OR e somma.
Il nostro circuito dovrà scegliere quale operazione dovrà effettuare, ogni operazioni ha un codice, diciamo che l'AND è riconosciuto da 00, OR da 01 e che la somma sia riconosciuta da 10.
Per poter rappresentare questi codici sono necessari due bit.
Operazione | Codice |
---|---|
AND | 00 |
OR | 01 |
SUM | 10 |
??? | 11 |
Avremmo la possibilità di rappresentare un'altra operazione, ma al momento ignoriamo una eventuale operazione con codice 11.
Mettendo insieme tutto ciò che abbiamo studiato fino ad ora, cosa può esserci utile?
Come abbiamo già ribadito, le linee di un circuito logico "vanno sempre avanti", la nostra ALU, dati due bit in input, eseguirà contemporaneamente tutte le operazioni per cui è progettata, ma grazie al multiplexer verrà mandata in uscita solamente l'operazione selezionata.
Adesso il nostro circuito è in grado di eseguire le operazioni desiderate, tuttavia non abbiamo ancora scelto quale fra tutte restituire in output.
Ovviamente, posizionando l'addizionatore è necessario anche inserire un bit per il riporto in entrata.
I bit operation in entrata nel multiplexer è di 2 bit, come abbiamo detto poc'anzi, il motivo è che bisogna prendere una scelta fra 3 e 2 bit ci consentono di portare a termine questo obiettivo.
Per l'intuizione accennata nel file sugli addizionatori, possiamo collegare in serie ALU ad un bit per ottenere ALU a diversi bit, per esempio a 64 bit. I bit di operation sono condivisi tra tutte le ALU, in modo che l'operazione che viene mandata in uscita sia la stessa per tutte le ALU.
Implementeremo la sottrazione come una somma, infatti sappiamo che
Quindi per calcolare la differenza tra due bit dobbiamo invertire uno dei due operandi, diciamo b.
In decimale l'inverso di 1 è -1, quindi invertire un numero è estremamente facile. In binario puro l'inverso di 01 è 11, quindi sarebbe sufficiente invertire il bit più significativo. Nei calcolatori è utilizzata la notazione in complemento a 2, quindi ciò che bisogna fare è:
Abbiamo aggiunto un multiplexer con un bit (bitinvert) che non è altro che un bit di selezione per il multiplexer. Quando bitinvert = 0 b non viene invertito, mentre se bitinvert = 1 b viene invertito.
Deve succedere che quando bitinvert = 1 e quindi quando deve essere invertito b,
Succede che:
Bitinvert | CarryIn |
---|---|
0 | 0 |
1 | 1 |
bitinvert e CarryIn sono sempre uguale, quindi possiamo tenerne solo uno e collegarlo sia all'addizionatore che al multiplexer per invertire b.
L'operazione NOR è l'operazione opposta dell'OR.
A | B | NOR |
---|---|---|
0 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 0 |
Per le leggi di De Morgan, la NOR:
Quindi la NOR è implementabile come l'AND di due bit negati, abbiamo già il multiplexer per negare il bit b, ne aggiungiamo un altro per negare a.
Per fare la NOR, facciamo la AND di due bit NEGATI, non invertiti, quindi in questa circostanza verrà mandata in uscita l'operazione 01 ovvero l'AND.
Quando si vorrà fare la NOR, verranno negati entrambi i bit (a e b) e l'operazione che verrà mandata in uscita è la 01, ovvero l'AND.
L'operazione slt: set on less than restituisce 1 se rs1 < rs2 e 0 altrimenti.
Con rs1 e rs2 ci riferiamo ai registri degli operandi di RISC-V.
Quindi vogliamo aggiungere un operatore che ci consenta di stabilire se un numero è strettamente minore dell'altro:
Avendo un n bit quindi quello che vedremo saranno nel primo caso, n-1 bit e l'n-esimo posto a 1, altrimenti tutti i bit posti a 0.
L'idea che vogliamo sfruttare per implementare questa operazione è la sottrazione e in particolare: avendo 2 numeri, diciamo
se
Verifichiamo che il bit più significativo della sottrazione tra a e b valga 1.
Prima di tutto aggiungiamo al multiplexer delle operazioni, l'operazione 11 (la quarta, che non avevamo ancora considerato).
Nell'operazione SLT so che dovrò restituire 1 (0...01) oppure 0 (0...0). Quello che può cambiare è il bit meno significativo che potrebbe anche essere 1.
L'ALU che abbiamo sviluppato è ad un solo bit. Questo vorrà dire che per implementare la funzione SLT per n bit, dobbiamo avere n ALU. Il punto è che, le prime n-1 ALU dovranno mandare verso l'uscita il bit 0, mentre l'ultima ALU dovrà mandare verso l'uscita 1 se la sottrazione ha restituito un numero negativo, 0 altrimenti.
Nell'ALU sopra abbiamo aggiunto al multiplexer, l'operazione 11 per SLT. Quando SLT sarà impostata nel multiplexer, verrà mandato in uscita il bit che è impostato nell'input Less, che sarà sempre 0 per le prime n-1 ALU.
L'n-esima ALU invece sarà così fatta:
A questo punto l'ALU è stata arricchita anche di un modulo per l'identificazioni di eventuali overflow, di cui discuteremo successivamente.
Inoltre si noti che la linea azzurra non ha un comportamento particolare, ma è stata colorata solo per distinguere meglio le linee del circuito.
Infine, notiamo che questa ALU, per l'n-esimo bit prende il risultato restituito dall'addizionatore. In particolare tiene conto solo di quest'ultimo bit, che è il primo nella sequenza numerica, infatti ricordiamo che un cifra binaria è considerata in questo modo:
Le linee del circuito avanzano sempre, cambia solo il risultato che viene mandato effettivamente in uscita. Quindi non è strano che set prende il risultato della somma, che viene comunque eseguito dalla ALU, nonostante il valore mandato in uscita si quello selezionato dal multiplexer per l'operazione 11.
Quindi se
Quindi come facciamo?
Mandiamo set indietro al bit in posizione 0. Infatti, notate che la selezione nel multiplexer dell'operazione 11 manda in uscita anche nella ALU per l'n-esimo bit il bit less = 0, mentre il bit set viene rimandato alla ALU in posizione 0, come si vede nell'immagine seguente:
Notate che se operation = 11 (slt), deve anche essere seguita la sottrazione tra a e b e quindi Bnegate (nell'immagine) deve essere posto a 1. Ogni ALU sputa fuori less = 0, l'ALU numero 63 sputa anch'essa less = 0, ma rispetto alle ALU precedenti tiene conto del bit più significativo risultante dalla differenza tra le cifre a e b, e lo rimanda alla ALU alla 0-esima (bit meno significativo) posizione nella serie.
Rispetto alla ALU nella versione precedente è stato aggiunto un circuito per il segnale Zero.
Utilizzabile per esempio per controllare se il risultato di una differenza è uguale 0, in modo da poter controllare anche se i registri sono uguali o no, questo ci consente anche le operazioni di controllo come:
Nella somma/differenza di due interi con segno in complemento a due, abbiamo overflow se i due operandi (le cifre) hanno lo stesso segno, ma il risultato ha segno opposto.
Possiamo dunque dire che quando il riporto in entrata del bit più significativo e il riporto in uscita di quello stesso bit sono discordi.
Quindi l'overflow mette semplicemente in XOR CarryIn e CarryOut, se sono discordi lo XOR restituisce 1, quindi l'overflow viene rilevato.
bitinvert(A) | bitInvert(B) | Operation | Funzione |
---|---|---|---|
0 | 0 | 00 | AND |
0 | 0 | 01 | OR |
0 | 0 | 10 | add |
0 | 1 | 10 | sub |
0 | 1 | 11 | set less than |
1 | 1 | 00 | NOR |
Lo stesso simbolo è usato per gli addizionatori, quindi è bene specificare di che modulo stiamo parlando.
Adesso siamo pronti per passare ai circuiti sequenziali.