This shows you the differences between two versions of the page.
ac-is:lab-ie:lab01 [2023/11/06 22:05] ionut.pascal [Laboratorul 1 - Introduction to Verilog. Behavior level.] |
ac-is:lab-ie:lab01 [2023/11/07 22:46] (current) ionut.pascal [Home assignment] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Lab 1 - Introduction to Verilog. Behavior level. ====== | + | ====== Lab 1 - Combinational Circuits. Behavior level. ====== |
- | ===== Verilog ===== | + | ===== Exercises ===== |
+ | In order to implement the exercises, use the lab archive below, considering the simulator you have on your workstation. The files already have a Xilinx ISE / Vivado project that you are able to run; in addition, for this practice you already have a testing module; the checking will be performed visually during the lab practice. Follow the instructions and hints below and the zones marked with TODO in the corresponding files. | ||
- | În cadrul laboratorului de Arhitectura Calculatoarelor vom studia un limbaj de descriere a hardware-ului (//eng. Hardware Description Language - **HDL**//) numit **Verilog**. Îl vom folosi pe tot parcursul laboratorului pentru a implementa noțiuni legate de arhitectura calculatoarelor. | + | ==== Lab practice ==== |
- | Limbajele de descriere a hardware-ului sunt folosite în industrie pentru proiectarea și implementarea circuitelor digitale. Cele mai folosite limbaje de descriere a hardware-ului sunt **Verilog** și **VHDL**. | + | - Implement an 8bit **Adder**, with 2 inputs and one output. |
+ | - The implementation shall contain only continuous assignments; | ||
+ | - The implementation shall use a procedural block; | ||
+ | * //Hint//: Don't forget to consult the basics of Verilog from Lab0 until you are familiarized with the new programming language. | ||
+ | * //Hint//: Follow the proper tutorial for understanding the simulation; skip the test-adding part if needed. | ||
+ | - Add extra stimuli in order to have the carry bit exercised. | ||
+ | * //Hint//: The values must be added in the //adder8_test//, the file which drives the stimuli to our module. | ||
+ | - Implement a 4bit **Comparator** using only continuous assignments. This module has 2 inputs and 3 outputs ( less than, equal, grater than). | ||
+ | * //Hint//: To verify a condition, '**?**' operator can be used. | ||
+ | - Implement a 4:1 **Multiplexer**. Try to explain the generated waveform. | ||
+ | - The implementation shall include a case construction. | ||
+ | * //Hint//: Do not forget about the //default// state! | ||
+ | - Let's make this tricky. Implement the multiplexor using the ' **?** ' operator. | ||
+ | * //Note//: The ' **?** ' operator can replace //case// or //if-elseif-else// statements when using the **assign**. However, abusing it can lead to a not-so-understandable implementation | ||
- | Deși din punct de vedere sintactic se aseamănă foarte mult cu un limbaj de programare de uz general (C/C++/Java), trebuie ținut cont că instrucțiunile nu se execută secvențial, ca pe un procesor. Ținta unui cod scris în Verilog este implementarea sa pe un **FPGA** sau dezvoltarea unui **ASIC** (Application Specific Integrated Circuit). | ||
- | ==== De ce Verilog? ==== | + | ==== Home assignment ==== |
+ | For the home assignment, you should use the corresponding resources from below. | ||
+ | - **(2p)** Change the adder8 module from exercise 1 by including a carry bit as an additional input. You can use any of //a// or //b// implementation. Add at least 5 data sets in adder8_test.sv, which will exercise the module and upload a screenshot with the simulation waveform to emphasize them. | ||
+ | * //Hint//: You should modify the interface and the logic inside. | ||
+ | * //Hint//: A complete testing shall exercise all of the inputs. | ||
+ | - **(3p)** Change the implementation of the comparator, following the rules below: | ||
+ | * Implement it using if constructs, using a procedural block; | ||
+ | * Change the size of the inputs to 6 bits. | ||
+ | * //Hint//: Do not forget to use the reg type when needed! | ||
+ | * //Note//: In procedural blocks, the outputs shall have a value at any moment so that they do not become memory components at the synthesis level. Considering this, it is useful to initialize them in the beginning. | ||
+ | - **(4p)** Implement a 8bit multiplier, with 2 inputs and one output. | ||
+ | * //Hint//: Take as reference the adder8 module. | ||
+ | * //Hint//: What is the size of the output? Consider the highest 8bit number. | ||
+ | - **(4p)** Implement a tiny ALU (**A**rithmetical **L**ogical **U**nit), that shall be able to execute 16bit ADD and SUB operations. The ALU has 3 inputs, op1 (16bits), op2(16bits), sel(1bit) and 2 outputs, result(16bits) and carry(1bit). | ||
+ | * //Hint//: Sel input selects the operation that is executed: 0 - ADD, 1 - SUB | ||
+ | * //Hint//: You can use the concatenate operator { } in order to make the result a 17bit variable! | ||
+ | * //Note//: When you operate the ALU inside a CPU, you have a fixed length bus available for all the data. | ||
- | + | ===== Resources ===== | |
- | Un limbaj de descriere hardware conține o serie de abstractizări sau moduri de a genera, prin intermediul codului, porți logice. În comparație cu a proiecta “de mână” circuitele integrate, tocmai aceste abstractizări sunt cele care au permis electronicii digitale să se dezvolte în ritm rapid, odată cu progresul tehnologiei de fabricație. Cu ajutorul lor putem descrie relativ ușor structuri complexe, divizându-le în componentele lor comune și de bază. | + | * {{ac-is:lab-ie:lab1_xilinx.zip|XILINX work files}} |
- | + | * {{ac-is:lab-ie:lab1_vivado.zip|VIVADO work files}} | |
- | Însă apare întrebarea naturală: Ce aș putea face cu un FPGA și nu aș putea face cu un procesor? Pe scurt, există trei răspunsuri: | + | * {{ac-is:lab-ie:lab1ha_xilinx.zip|XILINX home assignment files}} |
- | * Un FPGA poate fi reconfigurat într-un timp foarte scurt. Asta înseamnă că, dacă am greșit ceva în design-ul nostru, dacă dorim să-l modificăm sau să-l extindem, timpul și costul acestei acțiuni sunt foarte mici; | + | * {{ac-is:lab-ie:lab1ha_vivado.zip|VIVADO home assignment files}} |
- | * Un FPGA, prin construcția lui, oferă un grad extrem de ridicat de paralelism, lucru pe care codul scris pentru un procesor (deci cod secvențial) îl oferă într-o măsură mai redusă și mai greu de controlat; | + | |
- | * Un FPGA este de preferat oricând se dorește interfațarea unui dispozitiv (un senzor, un dispozitiv de afișare, etc.) care are nevoie de timpi foarte stricți în protocolul de comunicatie (exemplu: așteaptă 15 nanosecunde înainte să schimbi linia de ceas, apoi activează linia de enable pentru 25 de nanosecunde, apoi pune datele pe linia de date și ține-le cel puțin 50 de nanosecunde, etc). Pe un procesor acest lucru este iarăși dificil de controlat, fiindcă majoritatea instrucțiunilor se execută într-un număr diferit de cicli de ceas. | + | |
- | + | ||
- | Întrucât au fost puse în discuție atât proiectarea prin porți logice a unui circuit cât și descrierea lui la un nivel mai abstract, putem clasifica alternative de descriere a unui circuit: | + | |
- | * **descrierea structurală** - mai puțin folosită, ea reprezintă o implementare asemănătoare cu o schemă logică a unui circuit, folosind primitive și module pentru implementarea funcționalității | + | |
- | * **descrierea comportamentală** - divizată în descriere la nivel de flux de date și descriere la nivel procedural, folosește construcții de nivel înalt, întâlnite și în alte limbaje de programare. | + | |
- | + | ||
- | + | ||
- | ==== Ce tipuri de circuite putem construi? ==== | + | |
- | + | ||
- | + | ||
- | **Circuitele logice combinaționale** aplică funcții logice pe intrări pentru a obține ieșirile. Valorile de ieșire depind astfel doar de valorile curente de intrare, iar când starea unei intrări se schimbă, se reflectă imediat asupra ieșiri. | + | |
- | + | ||
- | {{ :ac-is:lab:lab00:circuit-comb.png |Diagrama bloc pentru un circuit combinațional cu n intrări și m ieșiri}} | + | |
- | <html><p align="center">Diagrama bloc pentru un circuit combinațional cu n intrări și m ieșiri</p></html> | + | |
- | + | ||
- | Logica combinațională poate fi reprezentată prin: | + | |
- | * diagrame structurale la nivel de porți logice, | + | |
- | * tabele de adevăr, | + | |
- | * expresii booleene (funcții logice). | + | |
- | + | ||
- | Spre deosebire de **circuitele** logice combinaționale, cele **secvențiale** (eng: sequential logic) nu mai depind exclusiv de valoarea curentă a intrărilor, ci și de stările anterioare ale circuitului. Logica secvențială poate fi de două tipuri: sincronă și asincronă. | + | |
- | + | ||
- | {{ :ac-is:lab:lab00:circuit-secv.png?400 |Schema bloc a unui circuit secvențial sincron}} | + | |
- | <html><p align="center">Schema bloc a unui circuit secvențial sincron</p></html> | + | |
- | + | ||
- | În primul caz, cel cu care vom lucra și la laborator, este folosit un semnal de ceas care comandă elementul/elementele de memorare, acestea schimbându-și starea doar la impulsurile de ceas. În al doilea caz, ieșirile se modifică atunci când se modifică și intrările, neexistând un semnal de ceas pentru elementele de memorare. Circuitele secvențiale asincrone sunt mai greu de proiectat, pot apărea probleme de sincronizare și sunt folosite mai rar. În continuare ne vom referi doar la circuitele secvențiale sincrone. | + | |
- | + | ||
- | Pentru mai multe detalii și exemple privind circuitele combinaționale și secvențiale, studiați [[ac-is:lab:lab00|Laboratorul 0]]. | + | |
- | + | ||
- | În laboratorul curent ne vom concentra asupra asimilării acestui nou limbaj, începând cu **descrierea structurală a unui circuit combinațional**. | + | |
- | + | ||
- | + | ||
- | ===== Structura limbajului Verilog ====== | + | |
- | + | ||
- | + | ||
- | Atunci când proiectăm un circuit digital folosind un HDL, începem prin a face o descriere textuală a circuitului, adică scriem cod. Acesta este compilat, iar în urma procesului va rezulta un model al circuitului care poate fi apoi rulat într-un simulator cu scopul de a verifica funcționalitatea descrierii. O alternativă la simulare este folosirea unui utilitar de sintetizare, care preia codul HDL și generează fișiere de configurare pentru FPGA. | + | |
- | + | ||
- | Însă proiectarea circuitelor poate deveni complexă. Datorită acestui motiv, se preferă proiectarea de tip top-down, o modalitate de partiționare sistematică și repetată a unui sistem complex în unități funcționale mai simple, a căror implementare poate fi făcută mai facil. O partiționare și organizare la nivel înalt a unui sistem reprezintă arhitectura acestuia. Unitățile funcționale individuale ce rezultă în urma partiționării sunt mai ușor de proiectat și de testat decât întregul sistem. Strategia divide-et-impera a proiectării top-down ne permite proiectarea de circuite care conțin milioane de porți. | + | |
- | + | ||
- | + | ||
- | ==== Module ==== | + | |
- | + | ||
- | + | ||
- | Modulul este unitatea de bază a limbajului Verilog, element ce încapsulează inferfața și comportamentul unui circuit. Modelul //black-box// este cel mai apropiat de definiția unui modul, întrucât se cunosc elementele de legătură: intrările și ieșirile din modul precum și funcționalitatea precisă a modulului, cu un accent mai redus asupra detaliilor de implementare și a modului în care acesta funcționează. | + | |
- | + | ||
- | Pentru declararea unui modul, se folosesc cuvintele cheie ''module'' și ''endmodule''. Pe lângă aceste cuvinte cheie, declarația unui modul mai conține: | + | |
- | * numele acestuia, | + | |
- | * lista de porturi (pentru interfața cu exteriorul): pot fi de intrare (input), ieșire (output) sau intrare - ieșire (inout) și pot avea unul sau mai mulți biți, | + | |
- | * și, desigur, implementarea funcționalității modulului. | + | |
- | + | ||
- | <note important>Ordinea porturilor unui modul nu este restricționată. Intrările și ieșirile pot fi declarate în orice ordine, însă, pentru consistență, o regulă de bună practică este folosirea convenției (obligatorie la primitive): prima dată se declară ieșirile, apoi intrările. | + | |
- | </note> | + | |
- | + | ||
- | == Exemplu declarare modul == | + | |
- | + | ||
- | <code systemverilog> | + | |
- | module my_beautiful_module ( | + | |
- | output out, | + | |
- | input [3:0] a, | + | |
- | input b); | + | |
- | /* descrierea funcționalității */ | + | |
- | endmodule | + | |
- | </code> | + | |
- | + | ||
- | Pentru implementarea modului avem la dispoziție câteva elemente, care sunt descrise în ceea ce urmează. | + | |
- | + | ||
- | + | ||
- | ==== Primitive ==== | + | |
- | + | ||
- | + | ||
- | Element ce stă la baza descrierii structurale a circuitelor, primitiva este o funcție asociată unei porți logice de bază. Verilog are o suită de primitive predefinite: | + | |
- | * primitive asociate porților logice: and, or, nand, nor, xor, xnor; | + | |
- | * primitive asociate porților de transmisie: not, buf, etc; | + | |
- | * primitive asociate tranzistorilor: pmos, tranif, etc. | + | |
- | + | ||
- | Fiecare primitivă are porturi, prin care este conectată în exterior. Primitivele predefinite oferă posibilitatea conectării mai multor intrări (ex. or, and, xor etc.) sau mai multor ieșiri (ex. buf, not). Folosirea unei primitive se face prin instanțierea sa cu lista de semnale care vor fi conectate la porturile ei. Pentru primitivele predefinite porturile de ieșire sunt declarate **înaintea** porturilor de intrare. | + | |
- | + | ||
- | Tabelul de mai jos oferă câteva exemple de instanțiere a unor porți în Verilog. Pentru primitivele predefinite numele instanței este opțional. | + | |
- | + | ||
- | ^ Schema ^ Cod ^ | + | |
- | | {{ .:lab01:or-gate.png?210 |}} | <code verilog>or(out, a, b, c); | + | |
- | // sau | + | |
- | or o1(out, a, b, c);</code> | | + | |
- | | {{ .:lab01:not-gate.png?250 |}} | <code verilog>not(out, in); | + | |
- | // sau | + | |
- | not my_not(out, in);</code> | | + | |
- | | {{ .:lab01:nand-gate.png?210 |}} | <code verilog>nand (z, x, y); | + | |
- | // sau | + | |
- | nand n(z, x, y);</code> | | + | |
- | + | ||
- | <html><p align="left">Exemple de instanțiere a primitivelor</p></html> | + | |
- | + | ||
- | + | ||
- | ==== Wires ==== | + | |
- | + | ||
- | + | ||
- | Un singur modul sau o singură primitivă nu poate îndeplini singură funcția cerută. Astfel, apare necesitatea interconectării modulelor sau a primitivelor. Specificarea semnalelor dintr-o diagramă se face prin **wires**, care se declară prin cuvântul cheie ''wire''. | + | |
- | + | ||
- | + | ||
- | | {{ .:lab01:gates.png?400&nolink |}} | <code verilog>wire y1, y2; | + | |
- | xor(out, y1, y2); | + | |
- | and(y1, in1, in2); | + | |
- | nand(y2, in3, in4, in5); | + | |
- | </code> | | + | |
- | <html><p align="left"></p></html> | + | |
- | + | ||
- | + | ||
- | În exemplul anterior y1 și y2 sunt semnale de câte 1 bit care leagă ieșirile porților and (y1) și nand (y2) la intrările porții xor. | + | |
- | + | ||
- | Pentru a declara semnale pe mai mulți biți se pot folosi vectori precum în declarațiile următoare: m reprezintă un semnal de 8 biți, iar n reprezintă un semnal de 5 biți. Bitul cel mai semnificativ (eng. most significant bit - MSB) este situat întotdeauna în stânga, iar bitul cel mai puțin semnificativ (eng. least significant bit - LSB) în dreapta. | + | |
- | + | ||
- | În mod implicit semnalele care nu sunt declarate sunt considerate ca fiind de tip wire și având 1 bit (ex. in1, in2, … din codul de mai sus). Putem accesa individual biții dintr-un wire sau putem accesa un grup consecutiv de biți specificând intervalul (ex. m[0], m[3:1], m[7:2]). | + | |
- | + | ||
- | <code systemverilog> | + | |
- | wire[7:0] m; // 8 biti, MSB este bitul 7, LSB bitul 0 | + | |
- | wire[0:4] n; // 5 biti, MSB este bitul 0, LSB bitul 4 | + | |
- | wire[7:0] a [9:0]; // array multidimensional cu 10 elemente de 8 biti | + | |
- | </code> | + | |
- | + | ||
- | + | ||
- | ===== Exerciții ===== | + | |
- | + | ||
- | + | ||
- | Pentru implementarea exercițiilor se vor utiliza scheletele de cod din arhiva laboratorului. Scheletele de cod conțin deja un proiect Xilinx ISE și un modul de testare. Urmăriți cerința și zonele marcate cu TODO. | + | |
- | + | ||
- | - **(4p)** Implementați și simulați un **sumator elementar complet**, utilizând sumatoare elementare parțiale. | + | |
- | * //Hint//: Urmăriți tutorialul pentru a realiza simularea (săriți peste adăugarea modulului de test, deoarece este deja adăugat). | + | |
- | * //Hint//: Consultați [[ac-is:lab:lab00|laboratorul 0]] pentru implementare. | + | |
- | - **(3p)** Implementați și simulați un **sumator pe 4 biți**, cu două intrări și două ieșiri. Verificați corectitudinea sumatorului vizualizând semnalele în baza 10. | + | |
- | * //Hint//: Consultați [[ac-is:lab:lab00|laboratorul 0]] pentru implementarea unui sumator pe mai mulți biți. | + | |
- | * //Hint//: Folosiți sumatorul implementat la exercițiul 1, adăugându-l la proiect din meniul Project→Add Copy of Source… . | + | |
- | * //Hint//: Modificați afișarea unui semnal cu click dreapta→Radix→Unsigned Decimal. | + | |
- | - **(3p)** Implementați și simulați un **sumator pe 6 biți**, cu două intrări și o ieșire. Câți biți va avea ieșirea? De ce? | + | |
- | * //Hint//: Folosiți atât sumatoare pe 1 bit, cât și sumatoare pe 4 biți. | + | |
- | - **(2p)** Implementați și simulați un **comparator** pe un bit. Acesta are două intrări și 3 ieșiri (pentru mai mic, egal și mai mare). | + | |
- | * //Hint//: Respectați interfața cerută în scheletul de cod. | + | |
- | + | ||
- | + | ||
- | ===== Resurse ===== | + | |
- | * {{.:lab01:lab1_skel.zip|Schelet de cod}} | + | |
- | * <html><a class="media mediafile mf_pdf" href="https://ocw.cs.pub.ro/courses/ac-is/lab/lab01?do=export_pdf">PDF laborator</a></html> | + | |
<ifauth @ac-is> | <ifauth @ac-is> |