This is an old revision of the document!
În acest laborator vom lucra pentru prima oara cu plăcuţa Digilent Nexys 3, ce are încorporat FPGA-ul Spartan6. La finalul acestui laborator veţi înţelege diferența dintre cod simulat și cod sintetizat.
Un FPGA (Field-Programmable Gate Array) este un circuit integrat care poate fi programat pentru a se comporta ca orice alt circuit digital. Spre deosebire de un procesor, care stochează și execută instrucțiuni, programarea unui FPGA înseamnă reconfigurarea hardware a acestuia pentru a realiza funcționalitatea dorită.
FPGA-urile sunt construite dintr-un număr mare de blocuri logice configurabile (eng. configurable logic block - CLB), identice, interconectate printr-o matrice de fire și switch-uri programabile.
Numărul de celule dintr-un FPGA variază de la model la model, putând ajunge până la câteva sute de mii și chiar milioane. Un exemplu este familia Virtex-7 de la Xilinx care poate conține chiar şi 2 milioane de celule logice.
În cadrul acestui semestru veți folosi plăcuțele Digilent Nexys 3, ce au la bază FPGA-ul Spartan-6:
Plăcuța se va programa folosind Xilinx ISE Design Suite și un cablu USB.
Implementarea modulelor folosind un FPGA se numește sintetizare. Acesta este procesul de a transforma descrierea high-level (high-level design) a unui modul, ce nu are un corespondent direct în hardware, într-o descriere low-level (gate-level design), ce are un corespondent direct în hardware.
După pasul de sintetizare nu putem totuși programa un FPGA. Ne trebuie un fişier UCF (User Constraint File) pentru a asocia pinii fizici ai FPGA-ului la intrările și ieșirile modulului. Structura sa de bază aratǎ în felul următor:
NET "A" LOC = B21; // Intrarea/Ieşirea numită "A" este rutată la pinul B21 al FPGA. NET "B<0>" LOC = B22; // Bitul 0 al intrării/ieşirii multibit numită "B" este rutat la pinul B22 al FPGA. NET "B<1>" LOC = C21; // Bitul 1 al intrării/ieşirii multibit numită "B" este rutat la pinul C21 al FPGA.
După ce stabilim constrângerile putem implementa design-ul. Odată implementat, pentru a putea încărca acest design pe un FPGA, trebuie să creăm un fișier de programare, ce va avea extensia .bit . Folosind un programator și acest fișier vom încărca design-ul pe un FPGA.
Vom implementa un modul în Verilog care are următorul comportament: atunci când apăsăm pe un buton se aprinde un LED, iar când lăsăm butonul se stinge LED-ul.
1. Stabilim interfața modulului: o intrare (butonul) și o ieșire (LED-ul).
2. Scriem codul verilog pentru modulul nostru.
module hello_world( output led, input button ); assign led = button; endmodule
3. Simulăm comportamentul. Este indicat să simulăm mai întâi pe calculator, deoarece sintetizarea design-ului poate dura foarte mult.
4. Asociem pinii pe plăcuță.
În figura de mai jos puteți vedea fiecare switch la ce pin al FPGA-ului este conectat. Alegem pinii astfel încât să îndeplinească funcțiile necesare modulului nostru: pinul B8 pentru cele intrare (fiindcă este legat pe placă la un buton) și pinul U16 pentru ieșire (fiindcă este legat la un LED).
5. Generăm fișierul UCF.
NET "button" LOC = B8; NET "led" LOC = U16;
6. Sintetizăm și implementăm design-ul. Generăm fișierul de programare. Încărcăm pe placă.
7. ???
8. Profit.
În general în momentul în care apăsǎm un buton sau schimbǎm un întrerupǎtor semnalul la ieşire nu este unul treaptǎ (cum ar fi ideal şi ne-ar ajuta foarte mult). De fapt semnalul arată ca în figura urmǎtoare.
Din această cauză citirea unui astfel de input poate genera aparenţa mai multor apăsări. Pentru a detecta corect o apăsare trebuie să folosim un circuit (sau o logică) de debouncing. În cea mai simplă formă acesta este un delay: citim semnalul, aşteptăm o perioadă până când acesta s-a stabilizat şi acum luăm în considerare valoarea obţinută. Forma corectă de a proiecta un debouncer este de a ţine minte perioada dintre douǎ schimbări ale semnalului. În cazul în care această perioadă este mai mică decât o valoare de prag considerăm că semnalul nu s-a stabilizat şi valoarea nu este de încredere, altfel valoarea poate fi folosită.
// Pseudocod pentru debouncer module debouncer( output reg button_out, input clk, input reset, input button_in ); always @(posedge clk) begin if (reset == 1) begin // resetăm ieșirea și alte auxiliare end else begin // Ținem un contor intern de delay, pe care îl creștem // Reținem schimbările butonului // Actualizăm ieșirea debouncerului doar când contorul revine la 0 end end endmodule
Un exemplu de cum folosim debouncerul:
module Hello( output reg button_pressed, input button_in, input clk, input reset ); wire button_debounced; // Instantiem modulul de debouncer si ii dam butonul ca intrare // Iesirea modulului (button_debounced) identifica corect apasarea // butonului dat ca intrare si va fi folosita in restul programului // in locul lui button_in debouncer db(button_debounced, clk, reset, button_in); always @(posedge button_debounced, posedge reset) begin if (reset == 1) begin button_pressed <= 0; end else begin button_pressed <= ~button_pressed; end end endmodule
Task 01 (1p) Descarcati scheletul de laborator si sintetizati codul pe placă (Tutorial). Analizand fisierul UCF si inscriptiile de pe placa, ce buton trebuie apasat pentru aprinderea ledului?
Task 02 (4p) Implementați un circuit combinațional cu 4 intrări și 2 ieșiri. Două dintre intrări sunt intrări de date. Celelalte două intrări sunt intrări de selecție și decid ce funcție logică să fie aplicată pe cele două intrări de date pentru a rezulta ieșirile. Una dintre ieșiri este rezultatul funcției logice (i.e. 0 sau 1) iar cea de-a doua este negatul primei ieșiri (i.e. 1 sau 0). Cele 4 funcții logice sunt:
Task 03 (1p) Implementați un modul debouncer, care primește ca intrare un buton, semnal de ceas și reset. Modulul va detecta corect o apăsare a butonului, eliminând aparența mai multor apăsări. Pentru a vizualiza rezultatul, la apăsarea butonului se va aprinde un LED, iar la o nouă apăsare LED-ul se va stinge. Folosiți modulul Hello de mai sus pentru a vă testa codul, adăugându-l în proiect.
Task 04 (2p) Implementați un modul care primește ca intrare un buton, semnal de ceas și reset. La apăsarea butonului se va incrementa un contor intern. Semnalele de ieșire trebuie conectate la cele 8 LED-uri de pe plăcuță. Reprezentați contorul intern în baza 2 folosind ledurile (unde 0 = LED stins, 1 = LED aprins). Modulul va folosi intrarea de reset pentru a reseta contorul intern. Folosiți semnalul de ceas intern al plăcuței.
Datasheet, secţiunea 5, pagina 11. Aflați frecvența și pinul la care trebuie conectată intrarea de ceas pentru a folosi ceasul intern al plăcuței.
Task 05 (2p) Modificaţi modulul de mai sus astfel încât contorul să se incrementeze la o secundă, fără să mai fie necesară apăsarea butonului, păstrând opțiunea de reset a contorului.