Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cn1:laboratoare:08 [2023/05/08 15:58]
mihnea.dinica add link to skel
— (current)
Line 1: Line 1:
-===== 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).  
- 
-{{ :​cn1:​laboratoare:​08:​ual.jpg?​300 | }} 
- 
-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: ​ 
-{{:​cn1:​laboratoare:​08:​eflags.png?​|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: 
- 
-{{:​cn1:​laboratoare:​08:​sreg_avr.png?​700|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 ==== 
- 
-Î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. 
- 
-=== 4.1. 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. 
- 
-<note undefined> ​ 
-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. ​ 
-</​note>​ 
- 
-**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. ​ 
-  * A = biții cei mai semnificativi (din stânga) vor lua valoarea lui M, restul vor fi 0.  
-  * S = 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): ​ 
-  - dacă sunt 01 => P = P + A (ignorăm overflow) ​ 
-  - dacă sunt 10 => P = P + S (ignorăm overflow) ​ 
-  - dacă sunt 00 => nu facem nimic  
-  - dacă sunt 11 => nu facem nimic 
- 
-  ​ 
-**4. Shiftam aritmetic la dreaptă pe P cu o poziţie** ​ 
- 
-<note important> ​ 
-Atenție la [[https://​en.wikipedia.org/​wiki/​Arithmetic_shift | Arithemtic Shift]] vs [[https://​en.wikipedia.org/​wiki/​Logical_shift | Logical Shift]]. ​ 
-</​note> ​ 
- 
-**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.2. Exemplu ===  
- 
-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.  
- 
-<​code> ​ 
-P = {(0000), (R), (Z)} = 0000 1100 0  
-</​code> ​ 
-  ​ 
-**Pasul 2** 
- 
-Formăm A și S. 
- 
-<​code> ​ 
-A = {(M), (0000), (Z)} = 0011 0000 0  
-S = {(-M), (0000), (Z)} = 1101 0000 0  
-</​code> ​ 
-  ​ 
-**Pașii 3 și 4** 
- 
-Pașii 3 și 4 vor fi efectuați de **n = 4** ori:  
-  - P = 0000 110//**0 0**//  
-    * Ultimii 2 biți sunt 00 (ne aflăm într-o secvența continuă de biți de 0) => Nu modificăm P  
-    * Îl shiftam aritmetic la dreapta pe P => P = 0000 0110 0  
-  - P = 0000 011//**0 0**//  
-    * Ultimii 2 biți sunt 00 => Nu modificăm P  
-    * Îl shiftam aritmetic la dreapta pe P => P = 0000 0011 0  ​ 
-  - P = 0000 001//**1 0**//  
-    * Ultimii 2 biți sunt 10 (am identificat începutul unei secvențe continue de biți de 1) => P = P + S = 1101 0011 0  
-    * Îl shiftam aritmetic la dreapta pe P => P = 1110 1001 1  
-  - P = 1110 100//**1 1**//  
-    * Ultimii 2 biți sunt 11 (suntem în interiorul unei secvențe continue de biți de 1) => Nu modificăm P  
-    * Îl shiftam aritmetic la dreapta pe P => P = 1111 0100 1  
-  ​ 
-**Pasul 5** 
- 
-Eliminăm bitul Z, iar P = 1111 0100 (-12) este rezultatul. 
-  
-=== 4.3. De ce merge algoritmul / Ce face? ===  
- 
-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: ​ 
-{{  :​cn1:​laboratoare:​08:​booth_binary.png?​ |}}  
-  ​ 
-În cazul înmulțirii a două numere, următoarele expresii sunt echivalente: ​ 
- 
-<​code>​ 
-P = M x 10011 =  M x (2^4 + 2^1 + 2^0) =  M x (2^5-2^4 + 2^2-2^0) 
-</​code>​ 
- 
-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: ​ 
-{{ :​cn1:​laboratoare:​08:​booth_example.png?​ |}}  
-  ​ 
-Tabelul de mai jos surprinde motivul pentru care algoritmul verifică biții LSB ai lui P:  
-  * Începutul unei secvențe înseamnă că trebuie să scădem -M * 2<​sup>​i</​sup>,​ unde i e indexul bitului de început ​ secvenței de 1  
-  * Sfârșitul unei secvențe înseamnă că trebuie să adunăm M * 2<​sup>​j</​sup>,​ 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  |  000111**10**00 ​ |  
-| 1 |  1  |  Interiorul unei secvențe continue de 1  |  00011**11**000 ​ |  
-| 0 |  1  |  Sfârșitul unei secvențe continue de 1  |  00**01**111000 |  
-| 0 |  0  |  Interiorul unei secvențe continue de 0  |  **00**01111000 ​ |  
-  ​ 
-De asemenea, observam ca motivul pentru care lui P i se adauga un bit Z pe pozitia LSB este ca sa putem verifica inceputul unei secvente continue de 1 ce porneste de pe indexul 0 (ea poate fi identificata corect doar prin secventa de tip "​10"​). 
- 
-==== 5. Algoritmul lui Booth pentru împărțire ==== 
- 
-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. 
- 
-=== 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 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) 
- 
-  * minus_M (S) = biții cei mai semnificativi (din stânga) vor lua valoarea lui -M, iar restul vor fi 0 
-  * plus_M (A) = biții cei mai semnificativi (din stânga) vor lua valoarea lui M, iar restul vor fi 0 
- 
-**3. Verificăm primul bit al lui R** 
- 
-Verificăm primul bit al lui R (cel mai semnificativ bit): 
- 
-  * dacă este 0 => shiftăm aritmetic la stânga pe R cu o poziție și adaugăm pe minus_M (S) la R, R = R + minus_M 
-  * dacă este 1 => shiftăm aritmetic la stânga pe R cu o poziție și adaugăm pe plus_M (A) la R, R = R + plus_M 
- 
-**4. Verificăm primul bit al 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. Verificam primul bit al 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 plus_M (A), 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 ==== 
- 
-<note warning> 
-Va trebui sa faceti atat **implementarea** cat si **simularea** cu seturi de date relevante. 
-</​note>​ 
- 
-**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``. 
- 
-<note tip> 
-În Verilog folosim operatorii ''<​nowiki><<<</​nowiki>''​ și ''<​nowiki>>>></​nowiki>''​ 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. 
- 
-<code verilog> 
-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 
-</​code>​ 
-</​note>​ 
-<note important>​ 
-În implementarea algoritmului lui Booth aveți nevoie de: 
- 
-<code verilog> 
-assign signed_arithmetic_right_shift = ($signed(magic_shift) >>>​ 1); // 4'​b1101 <- corect, semnul se păstrează 
-</​code>​ 
-</​note>​ 
- 
-**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: 
- 
-  *  **(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) 
-  *  **(1p)** 6 : ''​DIV''​ (folosind algoritmul lui Booth pentru împărțire) 
-  *  **(1p)** 7 : ''​MOD''​ (folosind algoritmul lui Booth pentru împărțire) 
- 
-<note tip> 
-Î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. Pentru a-l folosi, tratați modulul test_ual.v drept unul de simulare. 
-</​note>​ 
- 
-**BONUS: Task 3 (2p)** ​ - Incarcati modulul ``task3.v`` pe placa de dezvoltare din laborator. 
- 
-    * Switch-urile for fi folosite pentru a da valoare celor doua intrari i_w_a si i_w_b 
-    * Butoanele vor fi folosite pentru reset si selectia operatiei 
-    * Displayul 7-LED va afisa rezultatul operatiei in zecimal (utilizati modului din display_7_segment_driver.v) 
-    * LED-urile din dreptul switch-urilor vor afisa rezultatul operatiei in format binar 
-    * Folositi doar 4 dintre cele 8 cifre de pe afisaj (spre exemplu cele cu anozii AN0-AN3) 
- 
-<​hidden>​ 
-Recomandat sa faceti la tabla cate un exemplu atat de inmultire (factorii de max 4 biti) si de impartire (deimpartitul pe max 8 biti) cu algoritmul lui Booth; prezentati-le exemplul din lab, cu explicatii. La UAL si Status Register nu e nevoie sa intrati la fel de mult in detalii. 
-</​hidden>​ 
-<ifauth @user> 
-</​ifauth>​ 
- 
-==== 8. Resurse ==== 
-  * [[https://​gitlab.cs.pub.ro/​calculatoare-numerice/​soc-public/​-/​tree/​main/​lab08|Scheletul de laborator]] 
- 
-==== 9. Linkuri utile ==== 
- 
-  * [[https://​en.wikipedia.org/​wiki/​Booth%27s_multiplication_algorithm|Booth algorithm Wiki]] 
-  * [[http://​www.vlsiip.com/​download/​booth.pdf|Booth algorithm]] 
-  * [[http://​www.massey.ac.nz/​~mjjohnso/​notes/​59304/​l5.html|Exemplu Booth]] 
-  * [[https://​en.wikipedia.org/​wiki/​Logical_shift|Logical Shift]] 
-  * [[https://​en.wikipedia.org/​wiki/​Arithmetic_shift|Arithmetic Shift]] 
-  * [[https://​digilent.com/​reference/​_media/​reference/​programmable-logic/​nexys-a7/​nexys-a7_rm.pdf|Datasheet Digilent Nexys A7]] 
-  * [[https://​digilent.com/​reference/​_media/​reference/​programmable-logic/​nexys-a7/​nexys-a7-sch.pdf|Schema Digilent Nexys A7]] 
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