This shows you the differences between two versions of the page.
ac-is:lab-ie:lab00 [2023/10/01 13:59] alexandru.predescu created |
ac-is:lab-ie:lab00 [2023/11/06 22:41] (current) ionut.pascal |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ======= Laboratorul 0 - Recapitulare ====== | + | ======= Lab 0 - Verilog. The basics ====== |
+ | ===== Verilog ===== | ||
- | ===== Circuite combinaționale ===== | + | Î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. |
+ | 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**. | ||
- | Circuitele logice combinaționale aplică funcții logice pe semnalele de intrare pentru a obține semnalele de ieșire. Valorile de ieșire depind doar de valorile de intrare, iar când starea unei intrări se schimbă, acest lucru se reflectă imediat la ieșirile circuitului. | + | 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). |
- | {{ .:lab00:circuit-comb.png | Diagrama bloc pentru un circuit combinațional cu n intrări și m ieșiri}} | + | ==== De ce Verilog? ==== |
- | <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) | ||
- | Circuitele combinaționale sunt folosite în procesoare în cadrul componentelor de calcul, iar cele mai des întâlnite sunt: | + | 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ă. |
- | * multiplexoarele și demultiplexoarele | + | |
- | * codificatoarele și decodificatoarele | + | |
- | * sumatoarele | + | |
- | * comparatoarele | + | |
- | * memoriile ROM (read-only, nu păstrează stare) | + | |
- | Un exemplu de folosire a sumatoarelor este în cadrul Unităților Aritmetice-Logice (UAL) din interiorul procesoarelor. | + | Î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: |
+ | * 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; | ||
+ | * 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. | ||
- | ==== Porți logice ==== | ||
- | Porțile logice reprezintă componentele de bază disponibile în realizarea circuitelor combinaționale. Ele oglindesc operațiile din algebra booleană, algebră care stă la baza teoriei circuitelor combinaționale. În sunt prezentate cele mai întâlnite porți logice împreună cu operația booleană pe care o implementează. | + | ==== Ce tipuri de circuite putem construi? ==== |
- | ^ Denumire ^ Simbol ^ Operator ^ Tabel de adevăr ^^^ | + | **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. |
- | | Inversor (NOT) | {{.:lab00:gate-not.png?nolink&|}} | f = !a | a || f | | + | |
- | | ::: | ::: | ::: | 0 || 1 | | + | |
- | | ::: | ::: | ::: | 1 || 0 | | + | |
- | | Poarta SAU \\ (OR) | {{.:lab00:gate-or.png?nolink&|}} | f = a %%||%% b | a | b | f | | + | |
- | | ::: | ::: | ::: | 0 | 0 | 0 | | + | |
- | | ::: | ::: | ::: | 0 | 1 | 1 | | + | |
- | | ::: | ::: | ::: | 1 | 0 | 1 | | + | |
- | | ::: | ::: | ::: | 1 | 1 | 1 | | + | |
- | | Poarta ŞI \\ (AND) | {{.:lab00:gate-and.png?nolink&|}} | f = a && b | a | b | f | | + | |
- | | ::: | ::: | ::: | 0 | 0 | 0 | | + | |
- | | ::: | ::: | ::: | 0 | 1 | 0 | | + | |
- | | ::: | ::: | ::: | 1 | 0 | 0 | | + | |
- | | ::: | ::: | ::: | 1 | 1 | 1 | | + | |
- | | Poarta \\ SAU-NU \\ (NOR) | {{.:lab00:gate-nor.png?nolink&|}} | f = !(a %%||%% b) | a | b | f | | + | |
- | | ::: | ::: | ::: | 0 | 0 | 1 | | + | |
- | | ::: | ::: | ::: | 0 | 1 | 0 | | + | |
- | | ::: | ::: | ::: | 1 | 0 | 0 | | + | |
- | | ::: | ::: | ::: | 1 | 1 | 0 | | + | |
- | | Poarta \\ ŞI-NU \\ (NAND) | {{.:lab00:gate-nand.png?nolink&|}} | f = !(a && b) | a | b | f | | + | |
- | | ::: | ::: | ::: | 0 | 0 | 1 | | + | |
- | | ::: | ::: | ::: | 0 | 1 | 1 | | + | |
- | | ::: | ::: | ::: | 1 | 0 | 1 | | + | |
- | | ::: | ::: | ::: | 1 | 1 | 0 | | + | |
- | | Poarta \\ SAU EXCLUSIV \\ (XOR) | {{.:lab00:gate-xor.png?nolink&|}} | f = a %%^%% b | a | b | f | | + | |
- | | ::: | ::: | ::: | 0 | 0 | 0 | | + | |
- | | ::: | ::: | ::: | 0 | 1 | 1 | | + | |
- | | ::: | ::: | ::: | 1 | 0 | 1 | | + | |
- | | ::: | ::: | ::: | 1 | 1 | 0 | | + | |
- | | Poarta \\ SAU EXCLUSIV NU \\ (XNOR) | {{.:lab00:gate-xnor.png?nolink&|}} | f = !(a %%^%% b) | a | b | f | | + | |
- | | ::: | ::: | ::: | 0 | 0 | 1 | | + | |
- | | ::: | ::: | ::: | 0 | 1 | 0 | | + | |
- | | ::: | ::: | ::: | 1 | 0 | 0 | | + | |
- | | ::: | ::: | ::: | 1 | 1 | 1 | | + | |
- | <html><p align="left">Porțile logice de bază</p></html> | + | |
+ | {{ :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> | ||
- | ==== Sumatorul elementar ==== | + | Logica combinațională poate fi reprezentată prin: |
+ | * diagrame structurale la nivel de porți logice, | ||
+ | * tabele de adevăr, | ||
+ | * expresii booleene (funcții logice). | ||
- | Sumatoarele (//adders//), folosite cel mai mult în unitățile aritmetice logice ale procesoarelor, realizează adunări pe un număr dat de biți, furnizând la ieșirea circuitului suma și transportul (//carry//) rezultat în urma operației. | + | 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ă. |
- | Există mai multe tipuri de sumatoare pentru adunarea numerelor pe //n// biți, iar acestea se bazează pe sumatoare simple de 1 bit, care pot fi de două tipuri: | + | {{ :ac-is:lab:lab00:circuit-secv.png?400 |Schema bloc a unui circuit secvențial sincron}} |
- | * sumatorul elementar parțial (//Half adder//) - însumează doi operanzi pe 1 bit și oferă la ieșire suma acestora și transportul. | + | <html><p align="center">Schema bloc a unui circuit secvențial sincron</p></html> |
- | * sumatorul elementar complet (//Full adder//) - însumează doi operanzi pe 1 bit și un transport și oferă la ieșire suma acestora și transportul. | + | |
- | {{ .:lab00:half-adder.png |Diagrama bloc pentru half adder}} | + | Î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. |
- | <html><p align="center">Diagrama bloc pentru half adder</p></html> | + | |
- | {{ .:lab00:full-adder.png |Diagrama bloc pentru full adder}} | + | |
- | <html><p align="center">Diagrama bloc pentru full adder</p></html> | + | |
- | {{ .:lab00:diagrama_semnale_full_adder.jpg |Diagrama semnale pentru full adder}} | + | |
- | <html><p align="center">Diagrama semnale pentru full adder</p></html> | + | |
+ | Pentru mai multe detalii și exemple privind circuitele combinaționale și secvențiale, studiați [[ac-is:lab:lab00|Laboratorul 0]]. | ||
- | ==== Sumatorul elementar parțial ==== | + | În laboratorul curent ne vom concentra asupra asimilării acestui nou limbaj, începând cu **descrierea structurală a unui circuit combinațional**. |
- | Acest sumator este în continuare descris prin expresiile booleene, tabelul de adevăr și schema logică. | ||
+ | ===== Structura limbajului Verilog ====== | ||
- | ^ Inputs ^^ Outputs ^^ | ||
- | ^ a ^ b ^ sum ^ c_out ^ | ||
- | | 0 | 0 | 0 | 0 | | ||
- | | 0 | 1 | 1 | 0 | | ||
- | | 1 | 0 | 1 | 0 | | ||
- | | 1 | 1 | 0 | 1 | | ||
- | <html><p align="left">Tabelul de adevăr pentru half adder</p></html> | ||
- | Din tabelul de adevăr se pot deduce următoarele formule: | + | 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. |
- | <code> | + | Î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. |
- | sum = a ^ b | + | |
- | c_out = a && b | + | |
- | </code> | + | |
- | Conform acestor formule putem exprima circuitul prin porți logice, ca în imaginea de mai jos: | ||
- | {{ .:lab00:half-adder-gates.png | Schema logică pentru half adder}} | + | ==== Module ==== |
- | <html><p align="center">Schema logică pentru half adder</p></html> | + | |
- | <spoiler Despre tabele de adevăr și deducerea expresiilor booleene //(click aici)//> | ||
- | Dintr-un tabel de adevăr, pentru fiecare output se va deduce o funcție/expresie aplicând următoarele reguli: | + | 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ă. |
- | - fiecare rând din tabel pentru care funcția are valoarea 1 va genera un termen | + | |
- | - termenii sunt formați din parametrii funcției legați prin ȘI | + | |
- | - dacă parametrul are valoarea 1 se consideră în formă directă | + | |
- | - dacă parametrul are valoarea 0 se consideră în formă negată | + | |
- | - se aplică SAU între toți termenii deduși | + | |
- | Pentru sumatorului elementar parțial avem: | + | 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. | ||
- | $ sum\ =\ \bar a\ \cdotp\ b\ +\ a\ \cdotp\ \bar b $ | + | <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> | ||
- | $c_{out}\ =\ a\ \cdotp \ b$ | + | == Exemplu declarare modul == |
- | În multe cazuri aceste formule sunt prea complexe, conținând multe operații și necesitând multe porți logice pentru a fi implementate. Pentru a reduce complexitatea formulelor rezultate se poate aplica un procedeu de **minimizare**, care va reduce dimensiunea termenilor sau chiar îi va elimina. Minimizarea se poate realiza folosind teoremele algebrei booleene sau grafic, prin diagrame [[http://www.ee.surrey.ac.uk/Projects/Labview/minimisation/karnaugh.html|Karnaugh]]. | + | <code systemverilog> |
+ | module my_beautiful_module ( | ||
+ | output out, | ||
+ | input [3:0] a, | ||
+ | input b); | ||
+ | /* descrierea funcționalității */ | ||
+ | endmodule | ||
+ | </code> | ||
- | </spoiler> | + | Pentru implementarea modului avem la dispoziție câteva elemente, care sunt descrise în ceea ce urmează. |
- | ==== Sumatorul elementar complet ==== | ||
+ | ==== Primitive ==== | ||
- | ^ Inputs ^^^ Outputs ^^ | ||
- | ^ a ^ b ^ c_in ^ sum ^ c_out ^ | ||
- | | 0 | 0 | 0 | 0 | 0 | | ||
- | | 0 | 1 | 0 | 1 | 0 | | ||
- | | 1 | 0 | 0 | 1 | 0 | | ||
- | | 1 | 1 | 0 | 0 | 1 | | ||
- | | 0 | 0 | 1 | 1 | 0 | | ||
- | | 0 | 1 | 1 | 0 | 1 | | ||
- | | 1 | 0 | 1 | 0 | 1 | | ||
- | | 1 | 1 | 1 | 1 | 1 | | ||
- | <html><p align="left">Tabelul de adevăr pentru full adder</p></html> | + | 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. | ||
- | Din tabelul de adevăr se pot deduce următoarele formule: | + | 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. |
- | <code> | + | 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. |
- | sum = a ^ b ^ c_in | + | |
- | c_out = ((a ^ b) && c_in) || (a && b) | + | |
- | </code> | + | |
- | Conform acestor formule putem exprima circuitul prin porți logice sau putem folosi sumatoare elementare parțiale, ca în imaginea de mai jos: | + | ^ 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> | | ||
- | {{ .:lab00:full-adder-gates.png | Schema logică pentru full adder}} | + | <html><p align="left">Exemple de instanțiere a primitivelor</p></html> |
- | <html><p align="center">Schema logică pentru full adder</p></html> | + | |
- | <spoiler Codul C pentru full adder> | ||
- | <code C> | ||
- | void full_adder(int a, int b, int c_in, // inputs | + | ==== Wires ==== |
- | int *sum, int *c_out) // outputs | + | |
- | { | + | |
- | *sum = a ^ b ^ c_in; | + | |
- | *c_out = ((a ^ b) && c_in) || (a && b); | + | |
- | } | + | |
- | </code> | ||
- | </spoiler> | ||
+ | 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''. | ||
- | ==== Multiplexorul 4:1 ==== | ||
- | Un multiplexor digital este un circuit combinațional care implementează o funcție de selecție a uneia dintre intrările sale. | + | | {{ .:lab01:gates.png?400&nolink |}} | <code verilog>wire y1, y2; |
- | * $2^n$ intrări | + | xor(out, y1, y2); |
- | * $n$ intrări de selecție | + | and(y1, in1, in2); |
- | * o ieșire | + | nand(y2, in3, in4, in5); |
+ | </code> | | ||
+ | <html><p align="left"></p></html> | ||
- | {{ .:lab00:mux4.png | Diagrama bloc a multiplexorului 4:1}} | ||
- | <html><p align="center">Diagrama bloc a multiplexorului 4:1</p></html> | ||
- | {{ .:lab00:mux4-gates.png?455 | Schema logică a multiplexorului 4:1}} | ||
- | <html><p align="center">Schema logică a multiplexorului 4:1</p></html> | ||
- | Alegerea semnalului de ieșire se face pe baza intrărilor de selecție, care reprezintă în baza 2 numărul intrării ce trebuie selectate. În exemplul din imaginea de mai sus avem schema bloc a unui multiplexor cu 4 intrări, iar acesta are nevoie de două intrări de selecție. | + | Î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. |
- | Funcția inversă a multiplexorului este realizată de către circuitele de demultiplexare, care preiau un semnal de intrare și folosesc intrările de selecție pentru a-l transmite pe una din ieșirile posibile. | + | 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]). | ||
- | ^ S2 ^ S1 ^ out ^ | + | <code systemverilog> |
- | | 0 | 0 | I1 | | + | wire[7:0] m; // 8 biti, MSB este bitul 7, LSB bitul 0 |
- | | 0 | 1 | I2 | | + | wire[0:4] n; // 5 biti, MSB este bitul 0, LSB bitul 4 |
- | | 1 | 0 | I3 | | + | wire[7:0] a [9:0]; // array multidimensional cu 10 elemente de 8 biti |
- | | 1 | 1 | I4 | | + | </code> |
- | <html><p align="left">Selecția intrărilor</p></html> | + | ==== Assign ==== |
- | Deoarece multiplexorul 4:1 are 6 intrări, tabelul de adevăr devine destul de mare și nu mai este indicat de pornit de la acesta pentru obținerea funcției logice. Din descrierea funcționării circuitului și proprietățile porții AND, putem deduce termenii formulei: | ||
- | $ f = \bar s_1\ \cdotp\ \bar s_2\ \cdotp\ I_1\ +\ s_1\ \cdotp \bar s_2\ \cdotp\ I_2\ +\ \bar s_1\ \cdotp\ s_2\ \cdotp\ I_3\ +\ s_1\ \cdotp\ s_2\ \cdotp\ I_4 $ | + | Deși partiționarea circuitelor în cadrul unei arhitecturii duce la simplificarea implementării unui modul, implementarea acestuia la nivel de porți logice este rareori folosită, întrucât aceasta devine complicată și dificil de înțeles. |
- | Conform formulei se poate realiza circuitul cu porți logice din imaginea de mai sus. | + | Primul pas este reprezentat de ușurarea modalității de scriere a unei funcții logice. Pentru aceasta, Verilog oferă o instrucțiune numită **atribuire continuă**. Aceasta folosește cuvântul cheie ''assign'' și “atribuie” unei variabile de tip ''wire'', valoarea expresiei aflată în partea dreaptă a semnului egal. Atribuirea are loc la fiecare moment de timp, deci orice schimbare a valorii expresiei din partea dreaptă se va propaga imediat. |
+ | În partea stângă a unei atribuiri continue se poate afla orice variabilă declarată de tip wire sau orice ieșire a modulului care nu are altă declarație (ex. reg). Expresiile din partea dreaptă pot fi formate din orice variabile sau porturi de intrare și de ieșire și orice operatori suportați de Verilog. | ||
- | <spoiler Codul C pentru un multiplexor 4:1> | + | Bazându-ne pe circuitul descris în figura de mai jos, acesta se poate scrie sub formă de atribuiri continue în următoarea formă: |
- | <code C Multiplexor 4:1> | + | |
- | void mux41(int s1, int s2, // selection inputs | + | | {{ .:lab01:gates.png?400&nolink |}} | <code verilog>wire y1, y2; |
- | int i1, int i2, int i3, int i4, // inputs | + | xor(out, y1, y2); |
- | int *out) // output | + | and(y1, in1, in2); |
- | { | + | nand(y2, in3, in4, in5); |
- | switch((s2 << 1) | s1) | + | </code> | |
- | { | + | |
- | case 0: | + | |
- | *out = i1; | + | |
- | break; | + | |
- | + | ||
- | case 1: | + | |
- | *out = i2; | + | |
- | break; | + | |
- | + | ||
- | case 2: | + | |
- | *out = i3; | + | |
- | break; | + | |
- | + | ||
- | case 3: | + | |
- | *out = i4; | + | |
- | break; | + | |
- | } | + | |
- | } | + | |
- | </code> | + | == Exemplu atribuire continuă == |
- | </spoiler> | + | |
+ | <code systemverilog> | ||
+ | module my_beautiful_module ( | ||
+ | output out, | ||
+ | input i1,i2,i3,i4,i5); | ||
- | ==== Sumatorul cu transport succesiv ==== | + | assign y1 = i1 & i2; |
+ | assign y2 = ~(i3 & i4 & i5); | ||
- | Cel mai intuitiv mod de a forma un sumator este de a lega în cascadă mai multe sumatoare elementare complete pe 1 bit. În acest fel se formează un sumator cu transport succesiv (eng. //ripple-carry adder)//, cum este cel pe 4 biți din imaginea de mai jos, care primește la intrare ''a[3:0]'', ''b[3:0]'', ''c_in'' și are ca ieșiri suma ''s[3:0]'' și transportul ''c_out''. În cazul sumatoarelor pe mai mulți biți nu mai este indicat de pornit întâi de la o tabelă de adevăr deoarece aceasta ajunge la dimensiuni prea mari. | + | assign out = y1 ^ y2; |
- | {{ .:lab00:ripple-carry.png |Schema sumatorului cu transport succesiv, pe 4 biți}} | + | endmodule |
- | <html><p align="center">Schema sumatorului cu transport succesiv, pe 4 biți</p></html> | + | </code> |
- | Un alt avantaj al acestui design simplu, este că se pot forma sumatoare pe mai mulți biți din înlănțuirea oricâtor sumatoare. De exemplu, pentru a însuma numere pe 16 biți se poate crea un sumator ripple-carry din legarea în cascadă a 4 sumatoare pe 4 biți, ca în imaginea de mai jos. | + | sau, mai concis: |
- | {{ .:lab00:ripple-carry16.png |Schema sumatorului cu transport succesiv, pe 16 biți}} | + | <code systemverilog> |
- | <html><p align="center">Schema sumatorului cu transport succesiv, pe 16 biți</p></html> | + | module my_beautiful_module ( |
- | + | output out, | |
- | Deși are un design simplu, dezavantajul acestui sumator este că este **lent**, fiecare sumator elementar necesitând transportul de la sumatorul precedent. Există alte sumatoare, cum ar fi cel cu transport anticipat (eng. //[[http://www.eng.ucy.ac.cy/theocharides/Courses/ECE210/Carrylookahead_supp4.pdf|carry-lookahead adder]]//), care oferă o funcționare mai rapidă, eliminând așteptarea propagării transportului. | + | input i1,i2,i3,i4,i5); |
+ | assign out = (i1 & i2) ^ (~(i3 & i4 & i5)); | ||
- | ===== Circuite secvențiale ===== | + | endmodule |
+ | </code> | ||
- | 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. | + | Se poate observa că o atribuire continuă este mult mai ușor de scris, de înțeles și de modificat decât o descriere echivalentă bazată pe instanțierea de primitive. Circuitul descris de o atribuire continuă poate fi însă relativ ușor sintetizat ca o serie de porți logice care implementează expresia dorită, unii operatori având o corespondență directă cu o poartă logică. |
- | Logica secvențială poate fi de două tipuri: **sincronă** și asincronă. Î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 deoarece pot apărea probleme de sincronizare. Din această cauză ele sunt folosite mai rar. | + | <note important>Este o eroare să folosiți aceeași variabilă destinație pentru mai multe atribuiri continue. Ele vor încerca simultan să modifice variabila, lucru ce nu este posibil în hardware.</note> |
- | În continuare ne vom referi doar la circuitele secvențiale sincrone. | ||
- | {{ .:lab00:circuit-secv.png?400 |Schema bloc a unui circuit secvențial sincron}} | + | ==== Constante ==== |
- | <html><p align="center">Schema bloc a unui circuit secvențial sincron</p></html> | + | |
- | ==== Bistabilul D ==== | + | Pentru specificarea valorilor întregi este folosită următoarea sintaxă: |
- | Elementele de memorare din circuitele secvențiale pot fi implementate prin bistabile (eng. //flip-flops//). Acestea stochează valori în funcție de valoarea de la intrare și de semnalul de ceas. Valoarea stocată poate fi schimbată doar atunci când ceasul realizează o tranziție activă (un semnal de ceas poate fi "activ" pe front crescător (eng. //rising edge//) sau pe front descrescător (eng. //falling edge//)). | + | ''[size]['radix] constant_value'' |
- | Există 4 tipuri principale de bistabile: D, T, SR și JK, iar în acest laborator ne vom axa pe bistabilul D. Acesta are un design simplu și este folosit în general pentru implementarea registrelor din procesoare (cea mai mică și mai rapidă unitate de stocare din ierarhia de memorie). | + | * numerele conțin doar caracterele bazei lor și caracterul '_' |
+ | * pentru a ușura citirea, se poate folosi caracterul '_' ca delimitator | ||
+ | * caracterul '?' specifică impedanță mare (z) | ||
+ | * caracterul 'x' specifică valoare necunoscută | ||
+ | * se poate specifica dimensiunea numărului în biți dar și baza acestuia (b,B,d,D,h,H,o,O - binar, zecimal, hexa, octal) | ||
- | {{ .:lab00:d-flip-flop.png?200 |Diagrama bloc pentru bistabilul D}} | + | <code systemverilog> |
- | <html><p align="center">Diagrama bloc pentru bistabilul D</p></html> | + | 8'b1; //binar, pe 8 biti, echivalent cu 1 sau 8'b00000001 |
+ | 8'b1010_0111; //binar, echivalent cu 167 sau 8'b10100111 | ||
+ | 4'b10; //binar, pe 4 biti, echivalent cu 2 sau 4'b0010 etc. | ||
+ | 126; //scriere in decimal | ||
+ | 16'habcd; //scriere in hexazecimal | ||
+ | </code> | ||
- | Intrările și ieșirile circuitului sunt: | ||
- | * ''D'' - valoarea (//data//) de stocat | ||
- | * ''clk'' - semnalul de ceas, considerat activ pe front crescător în descrierile următoare | ||
- | * ''Q'' - starea curentă | ||
- | * ''!Q'' - starea curentă negată | ||
- | Ca mod de funcționare, ecuația caracteristică a sa este ''Qnext = D'', adică starea următoare (''Qnext'') a bistabilului depinde doar de intrarea ''D'', fiind independentă de starea curentă (''Q''), după cum se observă și din tabelul de mai jos. | + | ==== Operatori ==== |
- | ^ D ^ Q ^ Qnext ^ | + | Descrierea comportamentală la nivelul fluxului de date, descrisă anterior, presupune în continuare cunoașterea schemei hardware la nivelul porților logice sau, măcar, expresia logică. Deși reprezintă o variantă mai simplă decât utilizarea primitivelor, nu este cea mai facilă. |
- | | 0 | 0 | 0 | | + | |
- | | 0 | 1 | 0 | | + | |
- | | 1 | 0 | 1 | | + | |
- | | 1 | 1 | 1 | | + | |
- | <html><p align="left">Tabelul de tranziții pentru bistabilul D</p></html> | + | Pentru a ușura implementarea, Verilog pune la dispoziție mai multe tipuri de operatori. Unii dintre aceștia sunt cunoscuți din limbajele de programare precum C, C++, Java, și au aceeași funcționalitate. Alții sunt specifici limbajului Verilog și sunt folosiți în special pentru a descrie ușor circuite logice. Cu ajutorul acestora putem simplifica implementarea, apelând la construcții folosind limbajul de nivel înalt. |
- | Pentru a înțelege mai ușor comportamentul bistabilelor, pe lângă tabelele de tranziții mai sunt utile și diagramele de semnale (eng. //timing diagrams//), cum este cea din figura de mai jos, unde se poate observa cum ieșirea ''Q'' se schimbă doar pe frontul crescător de ceas și devine egală cu intrarea ''D'' în momentul tranziției ceasului. | + | Tabelul de mai jos conține operatorii suportați de Verilog, împreună cu nivelul lor de precedență. |
- | {{ .:lab00:d-flip-flop-timing.png?680 |Diagrama de semnale pentru bistabilul D}} | + | ^ Simbol ^ Funcție ^ Precedență ^ |
- | <html><p align="center">Diagrama de semnale pentru bistabilul D</p></html> | + | | ''<nowiki>! ~ + -</nowiki>'' (unari) | Complement, Semn | 1 | |
+ | | ''<nowiki>**</nowiki>'' | Ridicare la putere | 2 | | ||
+ | | ''<nowiki>* / %</nowiki>'' | Înmulțire, Împărțire, Modulo | 3 | | ||
+ | | ''<nowiki>+ -</nowiki>'' (binari) | Adunare, Scădere | 4 | | ||
+ | | ''<nowiki><< >> <<< >>></nowiki>'' | Shiftare | 5 | | ||
+ | | ''<nowiki>< <= > >= == !=</nowiki>'' | Relaționali | 6 | | ||
+ | | ''<nowiki>& ~& ^ ~^ ^~ | ~|</nowiki>'' | Reducere | 7 | | ||
+ | | ''<nowiki>&& ||</nowiki>'' | Logici | 8 | | ||
+ | | ''<nowiki>?:</nowiki>'' | Condițional | 9 | | ||
+ | | ''<nowiki>{,}</nowiki>'' | Concatenare | | | ||
+ | În continuare sunt prezentați operatorii mai neobișnuiți suportați de Verilog: | ||
- | ==== Automate finite ==== | + | * Operatorii de shiftare aritmetică; realizează shiftarea cu păstrarea bitului de semn, pentru variabilele declarate ca fiind cu semn. |
+ | <code systemverilog> | ||
+ | wire signed[7:0] a, x, y; | ||
+ | assign x = a >>> 1; // dacă bitul de semn al lui a este 0 bitul nou | ||
+ | //introdus este 0 | ||
+ | // dacă bitul de semn al lui a este 1 bitul nou | ||
+ | // introdus este 1 | ||
+ | assign y = a <<< 1; // bitul nou introdus este tot timpul 0, | ||
+ | //asemănător cu operatorul << | ||
+ | </code> | ||
- | Prin automate finite (eng. //Finite-state machine - FSM//) înțelegem de fapt un circuit secvențial sincron așa cum a fost el descris anterior. De obicei, proiectarea unui automat finit pornește de la o descriere informală a modului în care automatul trebuie să funcționeze. Primul pas în realizarea automatului este descrierea formală a funcționării acestuia. Două dintre metodele prin care un automat finit poate fi descris sistematic sunt: | + | * Operatorii de reducere; se aplică pe un semnal de mai mulți biți și realizează operația logică între toți biții semnalului |
- | * **Diagrama de stări** prezintă într-un mod grafic funcționarea unui automat finit. Stările automatului sunt reprezentate prin noduri, iar tranzițiile sunt reprezentate prin arce între starea sursă și starea destinație. Fiecare arc este marcat cu condiția necesară pentru a fi efectuată o tranziție. De asemenea, eventualele semnale de ieșire ale automatului sunt marcate în dreptul stărilor care generează acele ieșiri. | + | <code systemverilog> |
+ | wire[7:0] a; | ||
+ | wire x, y, z; | ||
+ | assign x = &a; // realizeaza AND între toți biții lui a | ||
+ | assign y = ~&a; // realizează NAND între toți biții lui a | ||
+ | assign z = ~^a; // realizeaza XNOR între toți biții lui a, | ||
+ | // echivalent cu ^~ | ||
+ | </code> | ||
- | {{ .:lab00:fsm-simplu.png?nolink |Exemplu de diagramă de stări}} | + | * Operatorul de concatenare; realizează concatenarea a două sau mai multe semnale, într-un semnal de lățime mai mare. |
- | <html><p align="center">Exemplu de diagramă de stări</p></html> | + | <code systemverilog> |
+ | wire[3:0] a, b; | ||
+ | wire[9:0] x; | ||
+ | |||
+ | // biții 9:6 din x vor fi egali cu biții 3:0 ai lui b | ||
+ | // biții 5:4 din x vor fi egali cu 01 | ||
+ | // biții 3:2 din x vor fi egali cu biții 2:1 ai lui a | ||
+ | // biții 1:0 din x vor fi egali cu 00 | ||
+ | assign x = {b, 2'b01, a[2:1], 2'b00}; | ||
+ | </code> | ||
- | ^ Starea curentă ^ x ^ Starea următoare ^ | + | ==== Parametrizarea modulelor ==== |
- | | S0 | 1 | S1 | | + | |
- | | S1 | 0 | S0 | | + | |
- | <html><p align="left">Exemplu de tabel de tranziții</p></html> | ||
- | * **Tabelul de tranziții** prezintă funcționarea unui automat finit sub formă de tabel. Fiecare rând al tabelului reprezintă o tranziție a automatului și conține starea curentă, starea următoare și intrările necesare pentru a activa tranziția. | + | === Parameter === |
- | În continuare vom proiecta două automate finite simple: | + | Cuvântul rezervat ''parameter'' este o construcție de limbaj în verilog care permite unui modul să fie reutilizat cu specificații diferite. Spre exemplu, un sumator poate fi parametrizat să accepte o valoare pentru numărul de biți care poate să fie configurată diferit de la o simulare la alta. Comportamentul lor este similar cu cel al argumentelor unor funcții în alte limbaje de programare cunoscute. Folosind ''parameter'' este declarată o valoare constantă, prin urmare este ilegală modificarea valorii acesteia în timpul simulării. De asemenea, este ilegal ca un alt tip de dată să aibă același nume ca unul dintre parametri. |
+ | <code systemverilog> | ||
+ | parameter MSB = 7; // MSB este un parametru cu valoarea constantă 7 | ||
+ | parameter [7:0] number = 2’b11; // o valoare de 2 biți este convertită | ||
+ | // într-o valoare de 8 biți | ||
+ | </code> | ||
- | === Recunoaşterea secvenței "ba" === | + | O variabilă de tip parametru este vizibilă local, în modulul ce a fost declarată. |
- | Se dorește proiectarea unui automat finit capabil să recunoască secvența "ba". Automatul primește la intrare în mod continuu caractere codificate printr-un semnal de un bit (caracterele posibile sunt "a" și "b"). Ieșirea automatului va consta dintr-un semnal care va fi activat (valoarea 1) atunci când ultimele două caractere introduse vor fi "b" urmat de "a". Semnalul de ieșire va rămâne activ până la introducerea unui nou caracter, după care automatul va continua operația de recunoaștere. | ||
- | <spoiler Click pentru rezolvare> | + | === Construirea și instanțierea modulelor parametrizabile === |
- | Vom începe proiectarea automatului prin identificarea intrărilor și ieșirilor. Din descriere observăm că intrarea este formată dintr-un singur semnal de 1 bit (automatul va avea și o intrare de ceas, însă aceasta nu este considerată intrare propriu zisă de date). Deoarece codificarea caracterelor nu este specificată vom presupune că valoarea 0 indică un caracter "a", iar valoarea 1 indică un caracter "b". Ieșirea este formată deasemenea dintr-un semnal de 1 bit cu valoarea 1 atunci când secvența căutată a fost găsită și 0 în rest. | ||
- | Vom realiza în continuare diagrama de stări a automatului. La pornire, vom inițializa automatul într-o stare pe care o vom numi ''S0''. Dacă la prima tranziție de ceas intrarea are valoarea: | + | Instanțierea modulelor a fost folosită și în laboratorul anterior pentru a invoca logica implementată într-un alt modul. În acel context, era necesar să cunoaștem dimensiunea semnalelor din interfață pentru a le potrivi cu variabilele conectate la instanță. În cazul în care un modul are dimensiunile porturilor parametrizate, acesta poate fi instanțiat cu valori particulare ale parametrilor (diferite de cele predefinite). Să considerăm ca exemplu un modul de mai jos: |
- | * 0 (caracterul "a") - vom avansa într-o stare pe care o vom numi ''Sa'' care ne spune că intrarea precedentă a fost "a" | + | |
- | * 1 (caracterul "b") - vom avansa într-o stare pe care o vom numi ''Sb'' care ne spune că intrarea precedentă a fost "b" | + | |
- | În continuare vom analiza ce se întâmplă atunci când automatul este în starea ''Sa''. Dacă la intrare avem valoarea: | + | |
- | * 0 (caracterul "a") - automatul va rămâne în acestă stare, care ne spune că intrarea precedentă a fost "a" | + | |
- | * 1 (caracterul "b") - automatul va trece în ''Sb'', care ne spune că intrarea precedentă a fost "b" | + | |
- | Dacă ne aflăm în starea ''Sb'' și automatul primește la intrare valoarea: | + | |
- | * 0 (caracterul "a") - automatul a întâlnit secvența dorită "ba" (fiind în starea ''Sb'' intrarea precedentă a fost "b", iar intrarea curentă este "a"); vom avansa într-o stare pe care o vom numi ''SA'' în care vom activa ieșirea automatului; de asemenea, această stare ne spune și că intrarea precedentă a fost "a", lucru folosit pentru a putea recunoaște și următoarele secvențe "ba" care vor mai fi întâlnite la intrare | + | |
- | * 1 (caracterul "b") - automatul va rămâne în această stare, care ne spune că intrarea precedentă a fost "b" | + | |
- | Dacă ne aflăm în starea ''SA'' și automatul primește la intrare valoarea: | + | |
- | * 0 (caracterul "a") - automatul va trece în starea ''Sa'' care ne spune că intrarea precedentă a fost "a", însă nu vom activa ieșirea automatului deoarece automatul nu a văzut și caracterul "b" | + | |
- | * 1 (caracterul "b") - automatul va trece în starea ''Sb'' care ne spune că intrarea precedentă a fost "b" | + | |
- | În momentul de față comportamentul automatului a fost descris complet, toate cele 4 stări identificate având definite tranzițiile pentru toate combinațiile semnalelor de intrare. Figura de mai jos prezintă diagrama de stări a automatului. | + | <code systemverilog> |
+ | module my_beautiful_module (out, a, b); | ||
+ | output [7:0] out; | ||
+ | input [3:0] a; | ||
+ | input [4:0] b; | ||
- | {{ .:lab00:fsm-ba.png?331&nolink |Automatul de recunoaștere a secvenței "ba"}} | + | …// some logic |
- | <html><p align="center">Automatul de recunoaștere a secvenței "ba"</p></html> | + | endmodule |
+ | </code> | ||
- | O dată determinată diagrama de stări a automatului, putem trece la implementarea acestuia într-un limbaj cunoscut (C/C++/C#/Java): | + | Pentru a instanția acest modul, vom avea nevoie de 3 variabile de 8, 4, respectiv 5 fire pe care le vom conecta astfel: |
- | <code c Automatul de recunoaștere a secvenței "ba"> | + | <code systemverilog> |
+ | My_beautiful_module inst1(out, a, b); | ||
+ | </code> | ||
- | void FSM_ba(int in, // FSM input: 0 - a, 1 - b | + | Pe de altă parte, având modulul: |
- | int out) { // FSM output: 0 - not found, 1 - found | + | |
- | int state = 0; // FSM state: 0 - S0, 1 - Sa, 2 - Sb, 3 - SA | + | <code systemverilog> |
+ | module my_beautiful_parameterized_module(out, a, b); | ||
+ | parameter a_width = 4; | ||
+ | parameter b_width = 5; | ||
+ | parameter out_width = 8; | ||
- | while(1) { | + | output [out_width-1:0] out; |
- | switch(state) { | + | input [a_width-1:0] a; |
- | case 0: | + | input [b_width-1:0] b; |
- | out = 0; | + | |
- | break; | + | |
- | case 1: | + | …// some logic |
- | out = 0; | + | endmodule |
- | break; | + | </code> |
- | case 2: | + | Îi putem utiliza logica fără a depinde de o dimensiune predefinită a semnalelor din interfață |
- | out = 0; | + | |
- | break; | + | |
- | case 3: | + | <code systemverilog> |
- | out = 1; | + | wire [4:0] out1; |
- | break: | + | wire [4:0] out2; |
- | } | + | wire [2:0] a; |
- | + | wire [1:0] b; | |
- | read_inputs(); | + | |
- | switch(state) { | + | my_beautiful_parameterized_module #(.a_width(3), |
- | case 0: | + | .b_width(2), |
- | if(in == 0) | + | .out_width(5)) inst2(out, a, b); |
- | state = 1; | + | |
- | else | + | |
- | state = 2; | + | |
- | break; | + | |
- | case 1: | + | // Sau, menținându-se ordinea parametrilor, doar prin specificarea noilor // dimensiuni: |
- | if(in == 0) | + | |
- | state = 1; | + | |
- | else | + | |
- | state = 2; | + | |
- | break; | + | |
- | case 2: | + | my_beautiful_parameterized_module #(3, 2, 5) inst3(out, a, b); |
- | if(in == 0) | + | </code> |
- | state = 3; | + | |
- | else | + | |
- | state = 2; | + | |
- | break; | + | |
- | case 3: | ||
- | if(in == 0) | ||
- | state = 1; | ||
- | else | ||
- | state = 2: | ||
- | break: | ||
- | } | ||
- | } | ||
- | </code> | + | ===== Testare ===== |
- | </spoiler> | + | |
- | === Intersecție semaforizată === | + | Pentru testarea unui modul folosind simulatorul se creează module speciale de test, în care, printre altele, se vor atribui valori intrărilor. Simularea permite detecția rapidă a erorilor de implementare și corectarea acestora. |
- | Se dorește modelarea prin intermediul unui automat de stări a unei intersecții semaforizate în care mașinile pot intra din nord (N), est (E), sud(S) sau vest (W). Semaforul din nord este sincronizat cu semaforul din sud, iar cel din est este sincronizat cu cel din vest. Duratele de timp pentru cele două direcții vor fi: Nord - Sud: roșu - 40 sec, galben - 10sec, verde - 50sec; Est-Vest: roșu - 60 sec, galben - 10 sec, verde - 30 sec. | + | Pentru a creea un modul de test și a-l simula puteți urma tutorialul de simulare [[https://ocw.cs.pub.ro/courses/ac-is/tutoriale/2-ise-simulare|aici]], iar această secțiune va prezenta câteva din construcțiile de limbaj pe care le puteți folosi într-un astfel de modul. |
- | <spoiler Click pentru rezolvare> | + | {{ :ac-is:lab:lab02:circuit_tb.png }} |
- | Vom începe proiectarea automatului prin identificarea intrărilor și ieșirilor. Deoarece descrierea informală nu conține informații despre intrările și ieșirile necesare vom folosi oricâte intrări și ieșiri avem nevoie pentru implementarea comportamentului. Un minim de ieșiri pentru automat reprezintă semnalele de comandă pentru culorile semaforului pentru pietoni și pentru mașini. Cele 5 semnale vor fi: | ||
- | * N_rosu- aprindere culoare roșie pentru mașinile din Nord | ||
- | * N_galben - aprindere culoare galbenă pentru mașinile din Nord | ||
- | * N_verde - aprindere culoare verde pentru mașinile din Nord | ||
- | * E_rosu- aprindere culoare roșie pentru mașinile din Est | ||
- | * E_galben - aprindere culoare galbenă pentru mașinile din Est | ||
- | * E_verde - aprindere culoare verde pentru mașinile din Est | ||
- | * S_rosu- aprindere culoare roșie pentru mașinile din Sud | ||
- | * S_galben - aprindere culoare galbenă pentru mașinile din Sud | ||
- | * S_verde - aprindere culoare verde pentru mașinile din Sud | ||
- | * W_rosu- aprindere culoare roșie pentru mașinile din Vest | ||
- | * W_galben - aprindere culoare galbenă pentru mașinile din Vest | ||
- | * W_verde - aprindere culoare verde pentru mașinile din Vest | ||
- | Pentru a măsura duratele de timp am putea folosi semnalul de ceas al automatului, introducând multiple stări cu tranziții necondiționate, în care o culoare a semaforului este ținută aprinsă. Având în vedere însă că semnalul de ceas pentru un automat are o perioadă de ceas mică (%%<<%% 1 sec) am avea nevoie de multe stări pentru a realiza o durată de 30 sec. O soluție mult mai bună este să folosim un numărător pentru a realiza întârzierile necesare. Numărătorul este un circuit secvențial (automat finit) care poate număra crescător sau descrescător tranzițiile unui semnal, având un semnal de ieșire care este activat atunci când indexul ajunge la 0 sau la o valoare care poate fi controlată. Concret, pentru măsurarea duratelor de timp în automatul nostru vom folosi un numărător crescător a cărui valoare maximă o vom configura pentru a obține duratele de timp necesare, în funcție de perioada de ceas a automatului. | ||
- | Vom adăuga astfel o ieșire (''T''), care va controla valoarea maximă a numărătorului și o intrare (''done'') care va primi semnalul de terminare de la numărător. | ||
- | Diagrama de stări a automatului va urmări tranziția celor 3 culori ale semaforului pentru mașini: verde -> galben -> roșu -> verde . | + | ==== Blocul initial ==== |
- | {{ .:lab00:fsm_intersectie.jpg?550&nolink |Automatul intersecției}} | ||
- | <html><p align="center">Automatul intersecției</p></html> | ||
- | Odată determinată diagrama de stări a automatului, putem trece la implementarea acestuia într-un limbaj cunoscut (C/C++/C#/Java): | + | Blocurile //initial// descriu un comportament executat o singură dată la începerea/activarea simulării și sunt folosite pentru inițializări și în module de test. Instrucțiunile sale trebuie încadrate între cuvintele cheie //begin// și //end// și sunt executate secvențial. |
- | <code c Automatul trecerii de pietoni> | + | <code systemverilog> |
+ | initial begin | ||
+ | a = 0; | ||
+ | b = 1; | ||
+ | #10; // delay 10 unități de timp de simulare | ||
+ | a = 1; | ||
+ | b = 0; | ||
+ | end | ||
+ | </code> | ||
- | void FSM_intersectie(int done, | + | Blocurile ''initial'' nu sunt sintetizabile, fiind folosite doar în simulări. |
- | int T, | + | |
- | int N_rosu, | + | |
- | int N_galben, | + | |
- | int N_verde, | + | |
- | int S_rosu, | + | |
- | int S_galben, | + | |
- | int S_verde, | + | |
- | int W_rosu, | + | |
- | int W_galben, | + | |
- | int W_verde, | + | |
- | int E_rosu, | + | |
- | int E_galben, | + | |
- | int E_verde) { | + | |
- | int state = 0; // FSM state: 0 - N/S_verde, 1 - N/S_galben, 2 - N/S_rosu, 3 - E/W_galben | ||
- | while(1) { | + | ==== Sincronizarea prin întârziere ==== |
- | read_inputs(); | + | |
- | N_rosu = 0; | ||
- | N_galben = 0; | ||
- | N_verde = 0; | ||
- | S_rosu = 0; | ||
- | S_galben = 0; | ||
- | S_verde = 0; | ||
- | W_rosu = 0; | ||
- | W_galben = 0; | ||
- | W_verde = 0; | ||
- | E_rosu = 0; | ||
- | E_galben = 0; | ||
- | E_verde = 0; | ||
- | switch(state) { | + | Folosind operatorul //#// se poate specifica o durată de timp între apariția instrucțiunii și momentul executării acesteia. Aceasta este utilă pentru a separa temporal diversele atribuiri ale intrărilor. Durata de timp este reprezentată prin unități de timp de simulare. De exemplu, dacă simularea folosește un //timescale// în nanosecunde, //#n// va reprezenta n nanosecunde. |
- | case 0: | + | |
- | E_rosu = 1; | + | |
- | W_rosu = 1; | + | |
- | N_verde = 1; | + | |
- | S_verde = 1; | + | |
- | T = 50; | + | |
- | if(done == 1) | + | |
- | state = 1; | + | |
- | else | + | |
- | state = 0; | + | |
- | break; | + | |
- | case 1: | ||
- | N_galben = 1; | ||
- | S_galben = 1; | ||
- | E_rosu = 1; | ||
- | W_rosu = 1; | ||
- | T = 10; | ||
- | if(done == 1) | ||
- | state = 2; | ||
- | else | ||
- | state = 1; | ||
- | break; | ||
- | case 2: | + | ==== Afișare ==== |
- | N_rosu = 1; | + | |
- | S_rosu = 1; | + | |
- | E_verde = 1; | + | |
- | W_verde = 1; | + | |
- | T = 30; | + | |
- | if(done == 1) | + | |
- | state = 3; | + | |
- | else | + | |
- | state = 2; | + | |
- | break; | + | |
- | case 3: | + | |
- | N_rosu = 1; | + | |
- | S_rosu = 1; | + | |
- | E_galben = 1; | + | |
- | W_galben = 1; | + | |
- | T = 10; | + | |
- | if(done == 1) | + | |
- | state = 0; | + | |
- | else | + | |
- | state = 3; | + | |
- | break; | + | |
- | } | + | |
- | } | + | |
+ | |||
+ | Atât în modulele de test cât și în modulele testate se pot folosi construcții pentru afișare în interiorul blocurilor //initial// și //always//. Una dintre aceste instrucțiuni este ''display'': | ||
+ | |||
+ | <code systemverilog> | ||
+ | $display(arguments); | ||
</code> | </code> | ||
- | </spoiler> | ||
+ | Argumentele acestei comenzi sunt similare cu cele ale funcției //printf// din C, ca în exemplul de mai jos, iar specificația completă o puteți găsi [[https://www.chipverify.com/verilog/verilog-display-tasks|aici]]. //$display// adaugă o linie nouă, iar dacă nu se dorește acest lucru se poate folosi comanda //$write//. | ||
+ | <code systemverilog> | ||
+ | a = 1; b = 4; | ||
+ | | ||
+ | $display("suma=%d", a+b); | ||
+ | </code> | ||
===== Resurse ===== | ===== Resurse ===== | ||
* <html><a class="media mediafile mf_pdf" href="https://ocw.cs.pub.ro/courses/ac-is/lab/lab00?do=export_pdf">PDF laborator</a></html> | * <html><a class="media mediafile mf_pdf" href="https://ocw.cs.pub.ro/courses/ac-is/lab/lab00?do=export_pdf">PDF laborator</a></html> |