Laboratorul 8 - Unitatea aritmetică logică

1. Scopul laboratorului

Î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).

2. Cu ce se ocupă o UAL?

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:

  • Operații logice: AND, OR, NOT, XOR, NOR, NAND, etc.
  • Operații de shift: shift stânga, shift dreapta, shift circular, etc.
  • Operații aritmetice: adunare, scădere, înmulțire (nu toate), împărțire (nu toate).

Există întotdeauna un compromis pe care inginerii trebuie să îl facă la proiectarea unei UAL. Ideea este că o instrucţiune poate:

  • să fie executată într-un singur ciclu de ceas, ceea ce ar fi rapid, dar foarte costisitor din punctul de vedere al circuitului fizic (complex), cât şi din alte considerente (de exemplu, disiparea de căldură),
  • să fie executată în mai mulţi paşi, ceea ce e evident mai lent, dar uşor de proiectat şi de adăugat funcţionalităţi unui acelaşi circuit,
  • să nu fie executată ca o operaţie de sine stătătoare în UAL, ci implementată prin software pe baza operaţiilor deja suportate de UAL, ceea ce ar fi foarte lent, dar dă o oarecare flexibilitate celor care scriu programe la nivel mai înalt.

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.

3. Status Register

Î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: x86 Status Register

Un exemplu mai simplu este registrul de status pentru Atmega324 (AVR în general), care se numește SREG și are 8 biți:

SREG

Î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:
    • dacă adunăm 2 numere pozitive și bitul de semn al rezultatului este 1 (rezultatul este număr negativ)
    • dacă adunăm 2 numere negative și bitul de semn al rezultatului este 0 (rezultatul este număr pozitiv)

4. Algoritmul lui Booth pentru înmulțire

Înmulțirea a două numere cu semn pe UAL este o operație complexă și costisitoare. Ținând cont că operațiile de deplasare a biților 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.

4.1. Cum funcționează algoritmul?

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⁴ + 2¹ + 2⁰)
              =  M x (2⁵ - 2⁴ + 2² - 2⁰)
              =  M << 5 - M << 4 + M << 2 - M << 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 operații de deplasare a biților.

Grafic, algoritmul face următoarea transformare:

4.2. Prezentarea algoritmului

Dorim să calculăm produsul P = M x R unde

  • M, R sunt numere cu semn reprezentate pe n biți
  • P este un număr cu semn reprezentat pe 2 * n biți

1. Formăm P

  • P = biții cei mai nesemnificativi (din dreapta) ai lui P vor lua valoarea lui R, restul vor fi 0.
  • Adăugăm un bit de '0' în dreapta lui P (LSB). Numim acest bit Z.

P are acum 2 * n + 1 biți. El va fi folosit astfel în cadrul algoritmului, iar bitul extra va fi eliminat la sfârșit pentru a obține rezultatul final.

2. Determinăm două valori auxiliare M+ și M-

Valorile auxiliare M+ și M- vor fi adunate lui P în cadrul algoritmului. Ele vor avea tot 2 * n + 1 biți.

  • M+ = biții cei mai semnificativi (din stânga) vor lua valoarea lui M, restul vor fi 0.
  • M- = biții cei mai semnificativi (din stânga) vor lua valoarea lui -M, restul vor fi 0.

3. Verificăm primii 2 biți ai lui P

Verificăm primii 2 biți ai lui P (cei mai nesemnificativi biți):

  1. dacă sunt 01 ⇒ P = P + M+ (ignorăm overflow)
  2. dacă sunt 10 ⇒ P = P + M- (ignorăm overflow)
  3. dacă sunt 00 ⇒ nu facem nimic
  4. dacă sunt 11 ⇒ nu facem nimic

De ce?

De ce?

Tabelul de mai jos surprinde motivul pentru care algoritmul verifică biții LSB ai lui P:

  • Începutul unei secvențe înseamnă că trebuie să adunăm M- * 2i, unde i e indexul bitului de început secvenței de 1
  • Sfârșitul unei secvențe înseamnă că trebuie să adunăm M+ * 2j, unde j e indexul bitului de sfârșit secvenței de 1
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

4. Deplasăm aritmetic la dreaptă biții lui 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.

4.3. Exemplu înmulțire folosind algoritmul lui Booth

Exemplu înmulțire folosind algoritmul lui Booth

Exemplu înmulțire folosind algoritmul lui Booth

Fie M = 00010 (2), -M = ~M + 1 = 11110 (-2) și R = 10011 (-13).

Pasul 1

Îl construim pe P prin concatenarea valorilor n{1'b0}, R și Z.

 
P = {(00000), (R), (Z)} = 00000 10011 0 

Pasul 2

Formăm M+ și M-.

 
M<sub>+</sub> = { (M), (00000), (Z)} = 00010 00000 0
M<sub>-</sub> = {(-M), (00000), (Z)} = 11110 00000 0

Pașii 3 și 4

Pașii 3 și 4 vor fi efectuați de n = 5 ori:

  1. P = 00000 10011 0
    • Ultimii 2 biți sunt 10 (am identificat începutul unei secvențe continue de biți de 1)
      • P = P + M- = 00000 10011 0 + 11110 00000 0 = 11110 10011 0
    • Îl deplasăm aritmetic la dreapta pe P ⇒ P = 11111 01001 1
  2. P = 11111 01001 1
    • Ultimii 2 biți sunt 11 (suntem în interiorul unei secvențe continue de biți de 1) ⇒ Nu modificăm P
    • Îl deplasăm aritmetic la dreapta pe P ⇒ P = 11111 10100 1
  3. P = 11111 10100 1
    • Ultimii 2 biți sunt 01 (am identificat finalul unei secvențe continue de biți de 1)
      • P = P + M+ = 11111 10100 1 + 00010 00000 0 = 00001 10100 1
    • Îl deplasăm aritmetic la dreapta pe P ⇒ P = 00000 11010 0
  4. P = 00000 11010 0
    • Ultimii 2 biți sunt 00 (suntem în interiorul unei secvențe continue de biți de 0) ⇒ Nu modificăm P
    • Îl deplasăm aritmetic la dreapta pe P ⇒ P = 00000 01101 0
  5. P = 00000 01101 0
    • Ultimii 2 biți sunt 10 (am identificat începutul unei secvențe continue de biți de 1)
      • P = P + M- = 00000 01101 0 + 11110 00000 0 = 11110 01101 0
    • Îl deplasăm aritmetic la dreapta pe P ⇒ P = 11111 00110 1

Pasul 5

Eliminăm bitul Z, iar P = 11111 00110 (-26) este rezultatul.

5. Algoritmul Non-Restoring Division

Asemenea înmulțirii, împărțirea pe UAL este o operație complexă și costisitoare. Acest algoritm reduce operația de înmulțire la operații de deplasare a biților, adunare și scădere.

5.1 Prezentarea algoritmului

Dorim să calculăm împărțirea Q / M = R[3:0] rest R[7:4] unde:

  • Q este deîmpărțitul
  • M este împărțitorul
  • R[3:0] este câtul
  • R[7:4] este restul

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 M- si M+

Valorile auxiliare M- și M+ vor fi adunate lui R in cadrul algoritmului (vor avea 8 biți)

  • M- = biții cei mai semnificativi (din stânga) vor lua valoarea lui -M, iar restul vor fi 0
  • M+ = biții cei mai semnificativi (din stânga) vor lua valoarea lui M, iar restul vor fi 0

3. Verificăm semnul lui R

Verificăm primul bit al lui R (cel mai semnificativ bit):

  • dacă este 0 ⇒ deplasăm aritmetic la stânga pe R cu o poziție și adaugăm pe M- la R, R = R + M-
  • dacă este 1 ⇒ deplasăm aritmetic la stânga pe R cu o poziție și adaugăm pe M+ la R, R = R + M+

4. Verificăm semnul lui R

Verificăm primul bit al lui R (cel mai semnificativ bit) iar:

  • dacă este 0 ⇒ setăm ultimul bit (cel mai nesemnificativ) al lui R pe 1
  • dacă este 1 ⇒ setăm ultimul bit (cel mai nesemnificativ) al lui R pe 0

5. Repetăm pașii 3 și 4 de n (n = 4, numărul de biți) ori

6. Verificăm semnul lui R

Verificăm primul bit al lui R (cel mai semnificativ bit) o ultimă dată:

  • dacă este 0 ⇒ nu modifcăm nimic
  • dacă este 1 ⇒ adăugăm la R pe M+, R = R + plus_M

7. Extragem câtul si restul din R

  • câtul = R[3:0]
  • restul = R[7:4]

6. TL;DR

  • Unitatea Aritmetică Logică se ocupă de aproape toate calculele cerute de procesor, solicitate de o instrucțiune explicită sau necesară intern.
  • Instrucțiuni uzuale:
    • Operații Logice: AND, OR, NOT, XOR, NOR, NAND, etc.
    • Operații de shift: shift stânga, shift dreapta, shift circular, etc.
    • Operații aritmetice: ADD, SUB, MUL (nu toate), DIV (nu toate).
  • Există un compromis între viteză și cost (complexitate hardware)
  • Status Register: Registru de flag-uri: 'ZF/Z - Zero, CF/C - Carry, N - Negative, OF/V - Overflow
    • Setate de UAL în momentul producerii unor evenimente.
  • Algoritmul lui Booth este o metodă eficientă de a înmulți două numere cu semn, care folosește operații de shift

7. Exerciții

Va trebui să faceți atât implementarea cât și simularea cu seturi de date relevante.

Task 0 (2p) - Implementați Algoritmul lui Booth pentru înmulțire în task0.v.

Task 1 (2p) - Implementați Algoritmul Non-Restoring Division în task1.v.

În Verilog folosim operatorii <<< și >>> pentru deplasarea aritmetică a biților. Valoarea bitului de fill se determină pe baza contextului (signed sau unsigned). Din acest motiv, pentru a deplasa 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 este relevant aici

În implementarea algoritmului lui Booth aveți nevoie de:

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 (Dacă rezultatul are mai puțin de 8 biți se va face padding cu zero pe cei mai semnificativi biți). Pentru valorile lui i_w_op_sel avem următoarele operații:

  • (0.5p) 0 : AND
  • (0.5p) 1 : XOR
  • (0.5p) 2 : OR
  • (0.5p) 3 : ADD
  • (0.5p) 4 : SUB (folosind ADD)
  • (1.5p) 5 : MUL (folosind algoritmul lui Booth)
  • (1.0p) 6 : DIV (folosind algoritmul lui Booth pentru împărțire)
  • (1.0p) 7 : MOD (folosind algoritmul lui Booth pentru împărțire)

În acest laborator aveți fișiere de testare pentru fiecare task. Acesta are hardcodate operanzi și rezultatele unor operații, iar rolul lui este de a vă ajuta pentru testare.

OPȚIONAL: Task 3 - Încărcați modulul task3.v pe placa de dezvoltare din laborator.

  • Switch-urile vor fi folosite pentru a da valoare intrărilor: i_w_a, i_w_b și i_w_op_sel;
  • Butoanele vor fi folosite pentru i_w_reset;
  • Afișajul cu 7 segmente va afișa rezultatul operației în format zecimal (utilizați modulul din display_7_segment_driver.v);
  • LED-urile din dreptul switch-urilor vor afișa valoarea în format binar a intrărilor: i_w_a, i_w_b și i_w_op_sel;
  • Folosiți doar 4 dintre cele 8 cifre de pe afișaj (spre exemplu cele cu anozii AN0-AN3).

8. Resurse

9. Linkuri utile

soc/laboratoare/08.txt · Last modified: 2024/04/26 13:21 by george_mircea.grosu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0