ALU
L'ALU, che sta per Arithmetic Logic Unit, ha lo scopo di eseguire operazioni aritmetiche e logiche come and
, or
, add
, beq
, etc.
Somma
La somma di due numeri a 32 bit sulla ALU è formata da 1 bit adder (una serie da 32) con la tabella di verità:
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 |
dove è il riporto precedente, è la somma e è il riporto dell'operazione.
Minimizzando la tabella si arriva ad avere che: ma dato che non è semplificabile, si possono usare le porte XOR, infatti:
Somma | XOR | ||
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 1 |
1 | 0 | 1 | 1 |
1 | 1 | 0 | 0 |
e quindi:
Sottrazione
Utilizzando il complemento a due e l'adder è possibile ottenere una somma, dato che:
Di conseguenza basterà fare la somma tra e , mettendo il riporto entrante , in modo da considerare anche il .
Confronto
Un'altra funzione della ALU è quella di confronto, che servirà poi per implementare l'istruzione slt
:
Lo stato del confronto si può ottenere dalla ALU sul MSB (Most Significant Bit) da cui viene letto il bit di segno della sottrazione tra e , con gli appropriati resti: dove il valore di verrà usato come input per (cioè less) sulla prima ALU in modo che , visto che il numero ha solo il LSB (Least Significant Bit) attivo.
Questo però non funziona se e , perchè è una somma tra due numeri positivi che può portare ad overflow, cosa che potrebbe dare un errato per cui il risultato sarebbe sbagliato.
ALU da 1 bit
dove:
- , sono gli input della ALU
- , , sono dei flag con cui si decide se usare la versione negativa di o di come input
- è il riporto precedente, mentre è il riporto di output
- indica quale operazione vogliamo dalla ALU
- è il risultato dell'operazione
- , sono usati internamente per la condizione
- indica l'overflow della somma
Quindi è possibile riassumere le operazioni in:
0 | 0 | 0 | 00 | and |
0 | 0 | 0 | 01 | or |
0 | 0 | 0 | 10 | |
0 | 1 | 1 | 10 | |
0 | 1 | 1 | 11 | |
1 | 1 | X | 00 | nor |
e visto che non ci interessa lo stato per il NOR, è possibile porre sulla prima 1 bit ALU nella catena delle ALU.
ALU da 32 bit
Per ricavare la ALU da 32 bit basta concatenare le varie ALU da 1 bit per ogni bit della word.
dove equivale al NOR tra tutti i bit di output e sarà quando .
Questa ALU però, avrà performance abbastanza lente, perchè ogni riporto di ogni 1 bit adder dovrà propagarsi per tutta la catena di 1 bit ALU, cosa che rallenterà il segnale. Per migliorare le prestazioni quindi, esiste un metodo chiamato Carry Lookahead.
Moltiplicazione
Questa parte non sarà contenuta all'interno dell'ALU ma la sfrutterà per creare l'algoritmo che permette la moltiplicazione tra due numeri.
L'algoritmo si basa sul metodo di moltiplicazione in colonna e si può riassumere in:
long mul(int a, int b) {
unsigned long r0 = abs(a); // Registro a 64bit perchè r0 << 1 per 32 volte
unsigned int r1 = abs(b);
long sum = 0;
for (size_t i = 0; i < sizeof(a) * 8; i++) {
if (r1 & 0x1) {
sum += r0; // Necessaria una ALU da 64bit
}
r0 <<= 1;
r1 >>= 1;
}
if ((a > 0) ^ (b > 0)) { // Controllo dei bit di segno
sum = -sum;
}
return sum; // a * b
}
che necessita, però, di una ALU e di un registro a 64bit, motivo per cui è stato ideato un algoritmo alternativo che è anche più ottimizzato:
long mul(int a, int b) {
unsigned int r1 = abs(b);
long sum = 0;
for (size_t i = 0; i < sizeof(a) * 8; i++) {
if (r1 & 0x1) {
unsigned int upper = sum >> (sizeof(sum)*8/2);
unsigned int lower = sum & ~0U;
upper += abs(a);
sum = (long) upper << (sizeof(sum)*8/2) | lower;
}
r1 >>= 1;
sum >>= 1;
}
if ((a > 0) ^ (b > 0)) {
sum = -sum;
}
return sum;
}