Laboratorul 03 - FPGA & Debugging

Obiective

În acest laborator vom discuta la nivel teoretic despre FPGA-uri și vom încerca o implementare formală a unor circuite simple. Apoi, vom intra un pic mai în detaliu în ceea ce privește simularea codului. La finalul acestui laborator veţi avea cunoștințele minime necesare atât pentru implementarea unui circuit pe FPGA, cât și pentru simularea/depanarea codului.

1. FPGA-uri

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.

Structura internă a unui FPGA

Structura internă a unui CLB simplu

Structura internă a unui CLB simplu

Structura internă a unui CLB simplu

În general, un CLB poate conţine mai multe celule logice ca cea din figura de mai sus. Se pot observa două structuri LUT (Look-Up Table), acestea au rolul de a emula un circuit combinaţional cu 3 intrări. Folosind un multiplexor împreună cu cele două LUT putem emula o poartă logică cu 4 intrări. Acest bloc are şi un Full-Adder, alte multiplexoare şi un bistabil de tip D (DFF - D Flip Flop). Aşadar, un CLB poate face parte dintr-un circuit digital combinaţional sau secvenţial complex.

Cum arată un FPGA?

Digilent Nexys A7

Pentru mai multe detalii vezi aici.

2. Sintetizarea folosind Vivado

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 XDC (Xilinx Design Constraints) pentru a asocia pinii fizici ai FPGA-ului la intrările și ieșirile modulului. Structura sa de bază aratǎ în felul următor:

set_property -dict { PACKAGE_PIN J15   IOSTANDARD LVCMOS33 } [get_ports { i_w_switch }]; #Intrarea numita "i_w_switch" este rutata la pinul J15 al FPGA
set_property -dict { PACKAGE_PIN H17   IOSTANDARD LVCMOS33 } [get_ports { o_w_led }]; #Iesirea numita "o_w_led" este rutata la pinul H17 al FPGA

Scrierea unui fișier XDC de la 0 poate fi un proces complicat pentru începători. Toate plăcuțele Digilent au corespunzător un fișier XDC ce conține (într-un mod structurat și ușor de înțeles) cod XDCscript pentru toți pinii externi ce pot fi asociați cu intrările/ieșirile din modulele create de noi. (ex. Nexys-A7-100T-Master.xdc)

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.

Implicit design-urile sunt scrise într-o memorie volatilă. Design-ul se va pierde în momentul în care resetați sau opriți plăcuța. Există și posibilitatea de a programa în mod permanent FPGA-ul, punând programul în memoria non-volatilă (flash) a plăcuței (ex. datasheet la pagina 8).

Implementare Hello, World!

Implementare Hello, World!

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.

hello_world.v
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 (preluată din datasheet) 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 P17 pentru cele intrare (fiindcă este legat pe placă la un buton) și pinul K15 pentru ieșire (fiindcă este legat la un LED).

I/O de bază Digilent Nexys A7

5. Decomentăm constrângerile necesare și înlocuim numele intrărilor/ieșirilor din fișierul XDC.

Nexys-A7-100T-Master.xdc
##Buttons
set_property -dict { PACKAGE_PIN P17   IOSTANDARD LVCMOS33 } [get_ports { button }];
##LEDs
set_property -dict { PACKAGE_PIN K15   IOSTANDARD LVCMOS33 } [get_ports { led }];

6. Sintetizăm și implementăm design-ul. Generăm fișierul de programare. Încărcăm pe placă. Urmăriți instrucțiunile din acest tutorial

3. Switch debouncing

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

Semnalul la apasarea si lasarea unui buton

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ă.

Cum implementăm un debouncer?

Cum implementăm un debouncer?

debouncer.v
// 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 de delay, pe care îl incrementăm
            // Reținem starea butonului
            // Actualizăm ieșirea debouncerului doar când contorul revine la 0 
            // (adică abia după ce a trecut delay-ul vom lua în considerare starea butonului)
        end
    end
 
endmodule

Un exemplu de cum folosim debouncerul:

Hello.v
module Hello(
    output reg led,
    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
            led <= 0;
	end else begin
            led <= ~led;
        end
    end
 
endmodule
 

Soluție hardware debouncing

Soluție hardware debouncing

Oricând lucrăm cu butoane sau întrerupatoare trebuie sa luăm în considerare fenomenul de debouncing. De multe ori întârzierea dată de execuţia codului este de ajuns pentru a ascunde acest fenomen, însă nu trebuie să ne bazăm niciodată pe asta!

4. Simulare & Debug

Demo!

TL;DR

  • Un FPGA e un circuit integrat care poate fi programat pentru a se comporta ca orice alt circuit digital.
  • În cadrul acestui laborator vom studia plăcuțele FPGA Digilent Nexys A7
  • Implementarea modulelor folosind un FPGA se numește sintetizare.
  • Fenomenul de “Bouncing” e întâlnit la apăsarea unui întrerupător - semnalul nu e unul de tip treaptă. Soluția? Switch debouncing.

Exerciții

Task 0 (3p) Implementați circuitul corespunzător unei unități de procesare a operațiilor logice. Avem:

  1. o intrare de selecție pe 2 biți între următoarele 4 operații logice:
    • XOR (0,0)
    • NAND (0,1)
    • OR (1,0)
    • AND (1,1)
  2. două intrări, pe cate un bit fiecare, corespunzătoare valorilor operanzilor;
  3. o intrare pentru resetarea circuitului;
  4. o intrare legată la ceasul sistemului.

Utilizați descrierea la nivel procedural și eșantionați datele doar pe fronturile ceasului. Rezultatul va fi legat pe portul o_w_out.

Task 1 (3p) Pentru modulul implementat anterior, definiți un fișier de constrângeri potrivit pentru circuitul modelat.

Hint

Hint

  1. conectați intrările corespunzătoare operanzilor la butoanele de tip push;
  2. conectați selectorul la switch-urile plăcuței;
  3. conectați intrarea de reset tot la un buton de tip push;
  4. conectați intrarea de ceas la semnalul de ceas generat de placuță
  5. conectați ieșirea la LED-urile plăcuței.

Task 2 (2p) Implementați un modul debouncer.

Task 3 (2p) Treceți intrările de la Task 1 prin module de debouncer. Simulați circuitul modelat și asigurați-vă că rezultatele sunt cele dorite. Încărcați noul modul pe FPGA, urmărind pașii de aici

Hint

Hint

  1. Verificați logica de emulare a fenomenului de „bouncing” pentru o eșantionare corectă.

Resurse

soc/laboratoare/03bis.txt · Last modified: 2024/02/29 14:37 (external edit)
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