CID Aplicatii 3

De la WikiLabs

Programarea unui FPGA – Introducere

FPGA-ul (Field-programmable gate array) este un circuit programabil, capabil sa implementeze un anumit circuit definit de utilizator si este format dintr-o matrice de blocuri programabile, interconectate intre ele printr-o serie de conexiuni programabile. Aceste blocuri pot implementa atat functii logice, cat si memorii.

Schema simplificată a unui FPGA

Cand se doreste implementarea unui circuit pe FPGA, acesta urmeaza mai multe etape: sinteza, implementarea, generarea fisierului de implementare si programarea.

Sinteza este procesul prin care codul Verilog este transformat intr-un circuit la nivel de porti si registre si este independent de modelul de FPGA folosit.

Implementarea este procesul prin care circuitul obtinut la pasul anterior este transformat in circuit la nivel de blocuri disponibile pe FPGA. Acest pas este dependent de FPGA-ul folosit.

Structura unui bloc generator de functii


Nodurile de interconectare

Registrul de Tip D sau bistabilul, este un circuit de memorie ce poate memora date la momentul aparitiei unui eveniment front crescator de ceas (vom discuta despre acesta in detaliu mai tarziu). MUX este un multiplexor si este capabil sa selecteze pentru iesire datele de la bistabil, sau datele de la LUT (Look-Up Table). Acest LUT este o memorie ROM capabila sa stocheze date ce vor fi accesate cu ajutorul intrarilor (intrarile au rol de adresa). LUT-ul este secretul din spatele implementarii functiilor logice. Circuitele implementate pe FPGA nu sunt efectiv porti interconectate intre ele, ci memorii ROM (LUT) care stiu sa memoreze iesirile asociate unor combinatii de intrare.

Asadar, in timpul implementarii, functiile logice sunt transformate in tabele de adevar (pentru fiecare combinatie de intrare se calculeaza iesirea iesirea), care sunt implementate prin LUT-uri.

Circuitul dezvoltat de utilizator trebuie legat apoi la pini fizici ai FPGA-ului, pentru a putea stimula intrarile si a observa starile iesirilor. Aceasta legare la pinii fizici este posibila, deoarece, asa cum am mentionat si mai sus, conexiunile in interiorul FPGA-ului sunt programabile. Pe placa, avem comutatoare si butoane pentru a controla starea intrarilor si LED-uri si afisaje pentru a observa starea iesirilor.

In figura de mai jos este prezentata o schema simplificata a conectarii modulului implementat pe FPGA la pinii fizici. Fiecare pin fizic este codat in tool-ul de sinteza cu un anumit cod. Mentionand in fisierul de constrangeri in dreptul fiecarui bit al fiecarei intrari si iesiri codul aferent pinului fizic, tool-ul va programa conexiunile programabile astfel incat portul modulului sa fie conectat la pinul fizic ales. Mai departe, pe placa de dezvoltare ce contine si FPGA-ul, pinii fizici sunt conectati prin trasee metalice la dispozitivele de control si observatie.

Folosind toate aceste informatii (implementarea circuitului si modul de conectare la porturile fizice ale FPGA-ului) se va genera fisierul de implementare, ce va fi apoi descarcat in memoria SRAM a FPGA-ului, configurand astfel dispozitivul.

Fpgaimg.PNG

Exemplul 1

In exemplul de mai jos, vom descrie comportamental un comparator si il vom testa prin simulare. Comparatorul pe care il vom descrie este un circuit cu 2 intrari pe 4 biti si 3 iesiri pe un bit.

Comparator4b.PNG


Implementarea in Verilog a circuitului

module Comparator(
    output reg equal,
    output reg lower,
    output reg greater,
    input [3:0] in0,
    input [3:0] in1
);

always@(in0 or in1) begin
    if(in1 > in0) begin
        equal = 0;
        lower = 0;
        greater = 1;
    end else if(in1 == in0) begin
        equal = 1;
        lower = 0;
        greater = 0; 
    end else begin
        equal = 0;
        lower = 1;
        greater = 0; 
    end
end

endmodule

Tbcomparator4b.PNG


Implementarea in Verilog a modulului de test

`timescale 1ns/1ps
module Comparator_TB();
 
 wire equal_t;
 wire lower_t;
 wire greater_t;
 reg [3:0] in0_t;
 reg [3:0] in1_t;
 
 initial begin
       in0_t = 0;
       in1_t = 0;
   #1  in0_t = 1;
   #1  in1_t = 2;
   #1  $stop(); 
 end
 
 Comparator DUT(
    .equal(equal_t),
    .lower(lower_t),
    .greater(greater_t),
    .in0(in0_t),
    .in1(in1_t)
 );

endmodule

Observatii:

  • Cele 3 iesiri ale modulului Comparator sunt de tip reg deoarece isi modifica valoarea intr-un bloc de tip always.
  • Fiind vorba de un circuit combinational, blocul de tip always este sensibil la orice modificare a oricarui semnal de intrare. De aceea, punem in lista de sensitivitati ambele semnale de intrare. Mai simplu, pentru a fi siguri ca nu uitam niciun semnal pentru blocurile always mai complexe, putem pune * in lista de sensitivitati (always@(*) begin).
  • Pentru modulul de test (Comparator_TB), observam ca interfata acestuia este goala (nu exista intrari sau iesiri).
  • Pentru fiecare intrare a modulului testat se defineste un semnal de tip reg, iar pentru fiecare iesire se defineste un semnal de tip wire. Atentie! Dimensiunea acestor semnale trebuie sa corespunda cu dimensiunea semnalelor din interfata modului la care vor fi legate.
  • Aici s-a optat pentru un test simplu, generand cate o combinatie pentru fiecare caz (egalitate, mai mic, mai mare), obsevatia facandu-se direct pe forma de unda (fara evaluare automata).


Exemplul 2

In exemplul de mai jos, vom implementa un circuit de adunare pe 4 biti, folosind sumatoare pe 1 bit.

4bitadder.PNG

Implementarea in Verilog a Adder1b

module Adder1b(
    output sum,
    output carry_out,
    input in0,
    input in1,
    input carry_in
);

assign {carry_out, sum} = in0 + in1 + carry_in;

endmodule

Observatii

  • Suma a doua numere pe n biti este un numar pe n+1 biti, cel de-al n+1 -ulea fiind bitul de carry.
  • Operatorul {} concateneaza mai multe semnale. Concatenarea {carry_out, sum} va duce la obtinerea unui numar pe 2 biti: bitul cel mai semnificativ (cel mai din stanga), reprezentat de carry_out si bitul cel mai putin semnificativ (cel mai din dreapta), reprezentat de sum.

Implementarea in Verilog a Adder4b

module Adder4b(
    output carry_out,
    output [3:0] sum,
    input [3:0] in0,
    input [3:0] in1
);

wire c1, c2, c3;

Adder1b Adder0(
    .sum(sum[0]),
    .carry_out(c1),
    .in0(in0[0]),
    .in1(in1[0]),
    .carry_in(0)
);
Adder1b Adder1(
    .sum(sum[1]),
    .carry_out(c2),
    .in0(in0[1]),
    .in1(in1[1]),
    .carry_in(c1)
);
Adder1b Adder2(
    .sum(sum[2]),
    .carry_out(c3),
    .in0(in0[2]),
    .in1(in1[2]),
    .carry_in(c2)
);
Adder1b Adder3(
    .sum(sum[3]),
    .carry_out(carry_out),
    .in0(in0[3]),
    .in1(in1[3]),
    .carry_in(c3)
);

endmodule

Implementarea in Verilog a modulului de test

module Adder4b_TB();
 
 wire carry_out_t;
 wire [3:0] sum_t;
 reg [3:0] in0_t;
 reg [3:0] in1_t;
 
 reg flag;
 integer idx, jdx;
 initial begin
     flag = 0;
     for(idx = 0; idx < 16; idx = idx + 1) begin
         for(jdx = 0; jdx < 16; jdx = jdx + 1) begin
             in0_t = idx;
             in1_t = jdx;
             #1;
             if({carry_out_t, sum_t} != (in0_t + in1_t))
             flag = 1;
         end
     end
 
     if(flag == 0)
         $display("TEST_PASS");
     else
         $display("TEST_FAIL");
 
     #2 $stop();
 end

 Adder4b DUT(
    .carry_out(carry_out_t),
    .sum(sum_t),
    .in0(in0_t),
    .in1(in1_t)
 ); 

endmodule

Observatii

  • Pentru modulul de test: Pe langa generarea semnalelor de test, observam ca la fiecare pas testam corectitudinea iesirii.
  • Consideram un fanion flag, de tip reg deoarece isi va modifica valoarea intr-un bloc initial, pe care il vom initializa cu 0 (prezumtia de nevinovatie – consideram initial circuitul corect). Testam conditia de corectitudine la fiecare pas de generare a semnalelor de test (la fiecare iteratie a buclei for) si daca ea este indeplinita, nimic nu se intampla cu flag, ramanand 0. Daca acesta conditie nu este indeplinita cel putin o data, flag se va face 1, semnalizand ca apare cel putin un caz incorect, afisandu-se totodata in consola cu $display mesajul TEST_FAIL si starea semnalelor in momentul acestuia. La iesirea din bucla testam din nou fanionul si daca acesta este 0, inseamna ca nu a aparut nici macar un caz incorect, afisand in cazul acesta mesajul TEST_PASS.

Exercitii suplimentare

1. Folosind un multiplexor cu 2 intrari de date, realizati un multiplexor cu 8 intrari de date si o iesire. Stabiliti numarul de intrari de selectie. Realizati acest circuit structural.

2. Realizati circuitul de la exercitiul anterior, descriindu-l comportamental.

3. Realizati un modul de test pentru circuitul de mai sus.

4. Utilizand circuitul half adder, prezentat in secventa de cod de mai jos, realizati un circuit full adder. Circuitul full adder are 3 porturi de intrare si 2 porturi de iesire. Acesta face suma celor 3 intrari si o scoate la iesire.

module half_adder(
    input wire a,
    input wire b,
    output wire carry,
    output wire sum
);

assign sum = a ^ b;
assign carry = a & b;

endmodule