În acest laborator vom proiecta o unitate fundamentală a oricarui procesor: Unitatea Aritmetică Logică (UAL, în literatura de specialitate Arithmetic Logic Unit sau ALU).
Unitatea aritmetică logică se ocupă de aproape toate calculele numerice de care are nevoie un procesor. În ciclul de prelucrare a instrucţiunilor, în diverse etape procesorul are nevoie de rezultatele unor calcule, fie că sunt solicitate de o intrucţiune explicită cum este add din limbajul de asamblare, fie dintr-un motiv intern (de exemplu adresa absolută a unei date se calculează în funcţie de segment şi offset, ceea ce implică nevoia unor calcule).
O UAL poate fi proiectată să execute, în principiu, orice operație. Totuși, cu cât operațiile devin mai complexe UAL devine mai scumpă, ocupă mai mult loc și disipă mai multă căldură. Operațiile care sunt, în general, suportate de toate UAL sunt:
Există întotdeauna un compromis pe care inginerii trebuie să îl facă la proiectarea unei UAL. Ideea este că o instrucţiune poate:
Procesoarele moderne folosesc întotdeauna prima variantă pentru instrucţiuni simple şi diverse implementări ale variantei a doua pentru operaţii de complexitate medie şi ridicată.
De asemenea, o UAL trebuie să fie în stare la orice moment să ruleze operaţia corespunzătoare la fiecare comandă din partea procesorului. Pentru aceasta fiecare operație are un cod al operației implementat în hardware, astfel încât la o instrucţiune add să intre în funcţiune modulul pentru adunare, la o instrucţiune and să intre în funcţiune poarta AND, etc.
În urma calculelor făcute de UAL pot apărea anumite situații, precum overflow la adunare/scădere, care este util să fie semnalate în cadrul sistemului. În acest scop procesoarele au un registru de status în care prin setarea unui bit anume este semnalat un anumit eveniment. Cel mai adesea, informațiile din registrul de status (sau Status Register) sunt folosite pentru a modifica fluxul de execuție al programului pe baza instrucțiunilor condiționale.
Spre exemplu, procesorul calculează A + B și dorește să sară la o anumită etichetă dacă rezultatul este 0. UAL va calcula rezultatul lui A + B și va seta bitul Z (Zero) din Status Register dacă rezultatul adunării este 0. Instrucțiunea de salt JZ (jump zero) verifică bitul Z din Status Register și sare la eticheta data dacă bitul este 1, altfel execută instrucțiunea imediat următoare.
În cadrul x86 Status Register se numește FLAGS și are 16 biți. El are o extensie de 32 de biți numită EFLAGS. Biții din cele două registre au următoarea semnificație:
Un exemplu mai simplu este registrul de status pentru Atmega324 (AVR în general), care se numește SREG și are 8 biți:
În ambele registre de status prezentate mai sus se regăsesc o serie de biți de interes pentru UAL:
CF
sau C
(carry flag) este setat pe 1 când operația executată pe UAL are carry. ZF
sau Z
(zero flag) este setat pe 1 când rezultatul operației de pe UAL este 0. N
(negative) este setat pe 1 când rezultatul operației de pe UAL este un număr negativ (bitul de semn al rezultatului este 1). OF
sau V
(overflow flag) este setat pe 1 când operația executată pe UAL produce overflow, mai exact există 2 cazuri: Înmulțirea a două numere cu semn pe UAL este o operație complexă și costisitoare. Ținând cont că operațiile de shift sunt mai rapide ca cele de adunare, Andrew Booth a venit cu o propunere de algoritm pentru înmulțirea numerelor cu semn care acum îi poartă numele.
Dorim să calculăm produsul P = M x R unde
1. Formăm P
2. Determinăm două valori auxiliare A și S
Valorile auxiliare A și S vor fi adunate lui P în cadrul algoritmului. Ele vor avea tot 2 * n + 1 biți.
3. Verificăm primii 2 biți ai lui P
Verificăm primii 2 biți ai lui P (cei mai nesemnificativi biți):
4. Shiftam aritmetic la dreaptă pe P cu o poziţie
5. Repetăm pașii 3 și 4 de n ori
6. Eliminăm bitul Z
Eliminăm bitul Z, adică cel mai nesemnificativ bit al lui P. Valoarea din P este rezultatul înmulțirii.
Fie M = 0011 (3) (-M = 1101 (-3)) și R = 1100 (-4).
Pasul 1
Îl construim pe P prin concatenarea valorilor R și Z. Restul biților vor fi padding cu 0. Practic adunăm valoarea 2R la P.
P = {(0000), (R), (Z)} = 0000 1100 0
Pasul 2
Formăm A și S.
A = {(M), (0000), (Z)} = 0011 0000 0 S = {(-M), (0000), (Z)} = 1101 0000 0
Pașii 3 și 4
Pașii 3 și 4 vor fi efectuați de n = 4 ori:
Pasul 5
Eliminăm bitul Z, iar P = 1111 0100 (-12) este rezultatul.
Ideea de la care pleacă algoritmul este că orice secvența continuă de 1 dintr-un număr binar poate fi rescrisă ca diferența a două numere binare:
În cazul înmulțirii a două numere, următoarele expresii sunt echivalente:
P = M x 10011 = M x (2^4 + 2^1 + 2^0) = M x (2^5-2^4 + 2^2-2^0)
Practic, algoritmul lui Booth se reduce la a identifica secvențele continue de biți de 1 din termenul R pentru P = M x R și a le înlocui cu o diferență. Astfel pentru o operație de înmulțire, numărul adunărilor este redus, realizându-se în schimb mai multe shiftari.
Grafic, algoritmul face următoarea transformare:
Tabelul de mai jos surprinde motivul pentru care algoritmul verifică biții LSB ai lui P:
Bitul curent | Bitul din dreapta | Explicație | Exemplu |
---|---|---|---|
1 | 0 | Începutul unei secvențe continue de 1 | 0001111000 |
1 | 1 | Interiorul unei secvențe continue de 1 | 0001111000 |
0 | 1 | Sfârșitul unei secvențe continue de 1 | 0001111000 |
0 | 0 | Interiorul unei secvențe continue de 0 | 0001111000 |
Asemenea înmulțirii, împărțirea pe UAL este o operație complexă și costisitoare. Întrucât operațiile de shift sunt mai rapide ca cele de scădere, similar ca în cazul înmulțirii, Andrew Booth a creat un algoritm pentru împartirea numerelor.
Dorim să calculăm împărțirea Q / M = R[3:0] rest R[7:4] unde:
1. Initializam R
R = biții cei mai nesemnificativi (din dreapta) ai lui R vor lua valoarea lui Q, restul vor fi 0
2. Determinăm două valori auxiliare minus_M (S) si plus_M (A)
Valorile auxiliare minus_M (S) și plus_M (A) vor fi adunate lui R in cadrul algoritmului (vor avea 8 biți)
3. Verificăm primul bit al lui R
Verificăm primul bit al lui R (cel mai semnificativ bit):
4. Verificăm primul bit al lui R
Verificăm primul bit al lui R (cel mai semnificativ bit) iar:
5. Repetăm pașii 3 și 4 de n (n = 4, numărul de biți) ori
6. Verificam primul bit al lui R
Verificăm primul bit al lui R (cel mai semnificativ bit) o ultimă dată:
7. Extragem câtul si restul din R
AND
, OR
, NOT
, XOR
, NOR
, NAND
, etc.shift stânga
, shift dreapta
, shift circular
, etc.ADD
, SUB
, MUL
(nu toate), DIV
(nu toate).'ZF
/Z
- Zero, CF
/C
- Carry, N
- Negative, OF
/V
- Overflowshift
Task 0 (2p) - Implementati Algoritmul lui Booth pentru inmultire in ``task0.v``.
Task 1 (2p) - Implementati Algoritmul lui Booth pentru impartire (non-restoring division) in ``task1.v``.
<<<
și >>>
pentru shiftări aritmetice. Valoarea bitului de fill se determină pe baza contextului (signed sau unsigned). Din acest motiv, pentru a shifta aritmetic la dreapta un număr negativ va trebui să specificăm contextul signed
al valorii shiftate.
assign magic_shift = 4'b1010; assign logic_right_shift = (magic_shift >> 1); // 4'0101 assign logic_left_shift = (magic_shift << 1); // 4'b0100 assign arithmetic_right_shift = (magic_shift >>> 1); // 4'b0101 <- greșit pentru numere cu semn assign arithmetic_left_shift = (magic_shift <<< 1); // 4'0100 assign signed_arithmetic_right_shift = ($signed(magic_shift) >>> 1); // 4'b1101 <- corect, semnul se păstrează assign signed_arithmetic_left_shift = ($signed(magic_shift) <<< 1); // 4'b0100 <- nu e relevant aici
assign signed_arithmetic_right_shift = ($signed(magic_shift) >>> 1); // 4'b1101 <- corect, semnul se păstrează
Task 2 (6p) - Implementați un modul UAL ``task2.v`` cu intrări pe 4 biți. Modulul va primi la intrare 2 numere (i_w_a și i_w_b) și indicatorul unei operații ce se va efectua asupra numerelor (i_w_op_sel). Ieșirea modului (o_w_result) va fi un număr pe 8 biți ce reprezintă rezultatul aplicării operației asupra numerelor i_w_a și i_w_b (Daca rezultatul are mai putin de 8 biti se va face padding cu zero pe cei mai semnificativi biti). Pentru valorile lui i_w_op_sel avem urmatoarele operatii:
AND
XOR
OR
ADD
SUB
(folosind ADD)MUL
(folosind algoritmul lui Booth)DIV
(folosind algoritmul lui Booth pentru împărțire)MOD
(folosind algoritmul lui Booth pentru împărțire)
BONUS: Task 3 (2p) - Incarcati modulul ``task3.v`` pe placa de dezvoltare din laborator.