CID aplicatii 2 : Instantiere si porti logice

De la WikiLabs
Jump to navigationJump to search

Teorie: incapsulare si instantiere

Unitatile constructive de baza din care se formeaza circuitele digitale se numesc porti logice. Acestea implementeaza functii logice si prin conectarea mai multor astfel de circuite simple complexitatea unui circuit poate creste pana la nivelul procesoarelor actuale. Pentru a putea controla cresterea complexitatii in proiectarea unui circuit de dimensiuni mari se folosesc 2 concepte cheie:

1) incapsularea functiei dorite intr-un modul

2) instantierea unor module mai mici si asamblarea acestora pentru a forma un modul mai mare.

Incapsularea se refera la a grupa elementele ce alcatuiesc o anumita functionalitate intre-un modul. Aceste elemente pot la randul lor sa fie alte module. Instantierea (asemanator cu POO) se refera la a apela un modul deja scris pentru a fi folosit efectiv in circuitul curent. Pentru a face o analogie cu programarea, cand se declara o variabila (sau un obiect), tipul variabilei este echivalent cu modulul si numele ei este numele instantei.

Pentru a intelege aceste concepte se da circuitul de mai jos:

Schema principiu instantiere.png


Observatii:

1) Notatie aici: Numele modulului este scris in interior.

2) Notatie aici: Numele instantei este scris deasupra.

3) Idee fundamentala: prin instantierea si incapsularea unor circuite simple, mici, apar circuite mai complexe.

4) Idei esentiale (daca vreuna e neclara consultati cadrul didactic):

a) "modul_2" este instantiat o singura data in tot proiecul, iar instanta se cheama "x"
b) "modul_2" are o intrare numita "in0" si o iesire numita "out"
c) "modul_2" este instantiat in cadrul "modul_6"


d) Modulul cel mai mare, ce cuprinde toata functionalitatea dorita a sistemului (aici "modul_6") se numeste uzual top.
e) Instanta top-ului care apare atunci cand se doreste testarea sa in simulare intr-un testbench (tb) se numeste uzual DUT (design under test)


f) "modul_0" apare instantiat de 3 ori. Circuitul final, cuprinde 3 subcircuite de tip "modul_0".
g) La nivel de top, instanta sa se cheama "b".
h) La nivel de "modul_5", cele 2 instante se cheama "a" si "c".
i) "a" si "c" sunt 2 circuite fizice diferite chiar daca ambele sunt de tipul "modul_0". Fiind instante ale aceluiasi modul, deci identice in alcatuire, luate separat ele fac acelasi lucru. Luate in contextul lui "modul_5", "a" genereaza datele de pe firul "w1" si "c" genereaza datele pentru iesire.


j) circuitul "b" de tip "modul_1" (instantiat in "modul_5") este complet diferit de circuitul "b" de tip "modul_0" (instantiat in top). Este permis ca ele sa aiba acelasi nume (cele 2 instante) deoarece se afla in locatii diferite.


k) Identic, mai multe module au o intrare numita "in_0". La sinteza circuitului nu se face confuzie intre acestea deoarece fiecare e vazut la nivelul altei instante.
l) Identic, firele de legatura "w0", "w1".


m) La nivelul "top", firul de legatura "w2" leaga iesirea "out_0" a instantei "b" la intrarea "in2" a instantei "y".
n) La nivelul "top", firul de legatura "w1" leaga iesirea "out_0" a instantei "x" la intrarea "in1" a instantei "y".
o) La nivelul "modul_5", firul de legatura "w1" leaga iesirea instantei "b" la intrarea instantei "c".
Pentru claritatea desenului, respectivele intrari si iesiri nu au fost denumite. In cod este obligatoriu ca ele sa fie definite si denumite.


p) La nivelul "top", "in0","in1","in2","in3","in4" si "out0" formeaza interfata modulului (semnalele care intra sau ies din modul).
q) La nivelul "top", "w0", "w1", "w2" sunt fire interne de legatura.
r) La nivelul "top", firul "in3" este conectat ca intrare pentru 3 submodule.


Teorie: testarea circuitelor

Pentru a se testa functionarea corecta a circuitului final, acesta este instantiat intr-un modul numit "test_bench" sau "tb". Acest modul este folosit strict in simulare. Testarea unui circuit are loc conform schemei urmatoare:

Schema principiu tb.png

Generarea datelor de intrare se va face asemanator cu laboratorul 1.

Voi veti avea rolul modelului ideal si al comparatorului datelor de iesire, uitandu-va la datele de intrare veti calcula iesirea corecta si apoi veti compara aceasta valoare cu raspunsul circuitului ce se testeaza.


In cazul unui sistem automat, se genereaza mesaje de eroare sau mesaje ca functionarea este in regula.


Observatie: proiectarea si verificarea sunt 2 domenii diferite, firmele avand departamente separate pentru acestea.

Proiectarea/Design se ocupa cu scrierea in Verilog a modului de top si toate modulele ce se afla in acesta, avand ca scop final realizarea fizica pe placa a unui circuit.

Verificarea se ocupa de scrierea in SystemVerilog (limbaj format din Verilog cu concepte de POO) a modului de testbench si generarea de stimuli si scenarii care sa testeze functionarea design-ului.


Teorie: porti logice

Portile logice folosite uzual, impreuna cu tabelele lor de adevar si reprezentarea grafica sunt date mai jos.

Se folosesc porti logice cu 1 sau 2 intrari, cele cu mai multe intrari fiind construite din acestea.


Tip Simbol Tabel de adevăr Tip Simbol Tabel de adevăr
Buffer/Repetor Buffer symbol
INTRARE IEȘIRE
A A
0 0
1 1
NOT NOT symbol
INTRARE IEȘIRE
A NOT A
0 1
1 0
AND AND symbol
INTRARE IEȘIRE
A B A AND B
0 0 0
0 1 0
1 0 0
1 1 1
NAND NAND symbol
INTRARE IEȘIRE
A B A NAND B
0 0 1
0 1 1
1 0 1
1 1 0
OR OR symbol
INTRARE IEȘIRE
A B A OR B
0 0 0
0 1 1
1 0 1
1 1 1
NOR NOR symbol
INTRARE IEȘIRE
A B A NOR B
0 0 1
0 1 0
1 0 0
1 1 0
XOR XOR symbol
INTRARE IEȘIRE
A B A XOR B
0 0 0
0 1 1
1 0 1
1 1 0
XNOR XNOR symbol
INTRARE IEȘIRE
A B A XNOR B
0 0 1
0 1 0
1 0 0
1 1 1

Teorie: Programarea unui FPGA

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: elaborarea, sinteza, implementarea, generarea fisierului de implementare si programarea.


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


Sinteza este procesul în care se realizează transformarea circuitului descris într-un netlist dependent de tehnologie. Se vor folosi la acest pas primitivele disponibile pe FPGA.


Implementarea este procesul în care se preia netlist-ul ce conține primitivele FPGA și modul lor de interconectare realizat la pasul de sinteză și se realizează maparea lor efectivă în FPGA (place and route).


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 sintezei, 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

Observatii:

  • Conexiunile dintre comutatorare si pinii FPGA sunt fixe. La fel si cele intre pinii FPGA si LED-uri.
  • Conexiunile dintre porturile modulului Module si pinii FPGA sunt configurabile.
  • Legarea porturilor modulului Module la pinii fizici ai FPGA se realizeaza prin configurarea conexiunilor din FPGA conform contrangerilor de I/O pe care le vom mentiona in proiect, inainte de sinteza.

Exemple

Exemplul1: Analiza circuitelor cu porti

Fie urmatorul circuit alcatuit din porti logice:

Mux2 schema interna 2.png

Se doreste exprimarea circuitului de mai sus ca formula de tip: iesire = f(intrari).

Pentru asta, abordarea consta in a porni de la iesire si a merge pas cu pas catre intrari, asa cum este exemplificat mai jos. Se vor folosi simbolurile "~" pentru NOT, "&" pentru AND, "|" pentru OR.

pas1: out0 = ?
pas2: out0 = w1 | w2
pas3: out0 = w1 | ( ? )
pas4: out0 = w1 | ( sel & in1 )
pas5: out0 = ( ? ) | ( sel & in1 )
pas6: out0 = ( in0 & w0 ) | ( sel & in1 )
pas7: out0 = ( in0 & ( ? ) ) | ( sel & in1 )
pas8: out0 = ( in0 & (~sel) ) | ( sel & in1 )

Exemplul2: NAND2 din AND2 si NOT

In urmatorul exemplu se implementeaza o poarta NAND din o poarta AND si o poarta NOT.

Codul de mai jos exemplifica ideea de instantiere si contine comentarii legate de sintaxa verilog necesara.

In mod uzual fisierele sunt denumite dupa modulul ce se afla in ele, in fiecare fisier fiind un singur modul.

Schema circuitul care se doreste a fi creat este:

Schema exemplu rezolvat.png

Descrierea portii NOT (fisierul not_gate.v):

// comentarii cu "//" sau cu /* .... */ ;
/* 
	ca in c/c++ 
*/ 

module not_gate	// cuvant cheie "module" apoi numele modulului (asemanator clase din c++)
	( // intre paranteze se pune interfata (firele care intra sau ies din modul)
		input wire in0,         // in0 este o intrare => input, in0 este fir => wire ;
		output wire out0	// out0 este o intrare => output, out0 este fir => wire 
	); // aici ";" sa nu il uitati

assign out0 = ~in0;	// cuvant cheie assign; 
					// semnalele de tip wire (cum e out0) iau valoare prin assign
					// ~ e semnul pentru negatie pe biti (ca in c/c++)

endmodule // cuvant cheie "endmodule". orice module se inchide cu endmodule.

Descrierea portii AND (fisierul and_gate.v):

module and_gate
	(
		input in0,	// aici sunt 2 intrari 
		input in1,	// se poate omite "wire". default e wire daca nu e pus nimic 
		output out0
	); // nu conteaza ordinea in care sunt puse intrarile si iesirile.
			// uzual si pentru usurinta se ordoneaza si grupeaza dupa functionalitate
			// in cazul de mai sus, am pus intai intrarile, apoi iesirile.

assign out0 = in0 & in1; // operatia propriu zisa 

endmodule

Descrierea modulului top (fisierul top.v):

module top
	(
		input a,
		input b,
		output c
    );
    
wire w0;    //declarat un fir intern de legatura 
    
and_gate and_gate_0	// instantiere: nume_modul nume_instanta (asemanator int x din c/c++)
	(
		.in0(a), // la intrarea "in0" a instantei "and_gate_0"  se conecteaza firul "a" din top
		.in1(b), // grija ca "in0", "in1", "out0" sa existe in declararea modulului "and_gate"
		.out0(w0) // grija ca "a", "b", "w0" sa existe la nivelul modulului in care se face instantierea
	);    
    
not_gate not_gate_0
	(
		.in0(w0), // "w0" care iese din "and_gate_0" intra in "not_gate_0"
		.out0(c) // "c" care iese din "not_gate_0" iese din modulul "top" (e iesire in interfata de sus)
	);   // se poate scrie si ".in0(w0),.out0(c)" dar se prefera fiecare fir pe randul sau (lizibilitate si loc de comentarii pt design-uri complexe)
	// Observatie: la varianta de mai sus de instantiere, nu conteaza ordinea firelor. 

/*
not_gate not_gate_0		
	(				// se poate instantia si in forma prescurtata ca aici 
		w0,				// in acest caz se pun conexiunile in ordinea in care sunt declarate intrarile si iesirile din modul
		c	// NU se recomanda stilul asta de instantiere
	); 			// apar greseli frecvent la ordinea firelor, la numarul lor, 
*/				// mai ales daca modulul e complex si are multe intrari si intrari
 
endmodule

Echivalent se pot folosi si "primitive" Verilog pentru scrierea top-ului.

Primitivele sunt porti logice care exista deja in limbaj, folosite atunci cand se doreste o descriere structurala a circuitului.

In instantiere acestora, iesirea se pune prima, urmata de intrari.

Observatie: daca se doreste scrierea acestor porti de catre voi (ca mai sus), numele modulului nu trebuie sa fie un cuvant cheie ocupat de primitive.


Descrierea alternativa a modulului top (fisierul top_v2.v):

module top_v2
    (
	    input a,
	    input b,
	    output c
    );
  
wire w0;

and and_gate_0(w0,a,b);	// primitiva pentru poarta and
not not_gate_0(c,w0);  	// primitiva pentru poarta not
   
endmodule

Fiind un modul simplu, intreaga functionalitate putea fi scrisa la nivel de "top" si simplificat ca mai jos.


Descrierea alternativa a modulului top (fisierul top_v3.v):

module top_v3
    (
	    input a,
	    input b,
	    output c
    );
   
assign c = ~ (a & b); // a si b, negate
   
endmodule

Testarea circuitul se face printr-un testbench, acesta fiind:


Descrierea testbench-ului (fisierul top_tb.v):

`timescale 1ns / 1ps

module top_tb(); // din/in tb nu intra si iese nimic. niciodata.

reg a_tb;	// ce va fi intrare pentru dut, aici e declarat ca "reg", ca sa poata tine valori 
reg b_tb;	
wire c_tb;	// ce e declarat ca iesire pentru dut, aici e declarat ca "wire" pentru ca device-ul ii va da valorile

top dut	// instantierea modulului de tip "top" sub numele "dut" 
	(
		.a(a_tb),
		.b(b_tb),
		.c(c_tb)
    );

initial
begin // in loc de { ... } din c/c++, in Verilog se pune begin ... end 
	#10;		// dupa 10 unitati de timp 
	a_tb = 0;		// a_tb ia valoarea 0
	b_tb = 0;
	#10;		// dupa inca 10 unitati de timp, deci in total la 20
	a_tb = 1;
	b_tb = 0;
	#10;
	a_tb = 0;
	b_tb = 1;
	#10;
	a_tb = 1;
	b_tb = 1;
	#10;
	a_tb = 0;
	b_tb = 0;
	
	#20 $stop();	// oprirea simularii 
end //end pentru initial 

endmodule

Formele de unda rezultate din simulare se pot vedea mai jos:

Exemplu rezolvat nand forme de unda.png


In chenarul verde se pot observa instantele din simulare.

Daca se doreste adaugarea de semnale noi pentru a fi vazute, se selecteaza modulul instantiat in care acestea se afla (chenar verde).

Apoi, din chenarul rosu se aleg semnalele si se adauga prin click dreapta->add to wave window.

Pentru o mai usoara vizualizare, semnalele se pot grupa asa cum se vede in chenarul mov prin click dreapta->new group.


Se observa o functionare corecta circuitului, acesta fiind o poarta NAND. El scoate "0" cand ambele semnale de intrare sunt "1" si scoate "1" in rest.


Dupa ce circuitul a fost testat in simulare, se doreste punerea sa fizica pe placa.

Pentru asta se urmeaza pasii descrisi in tutorialul Vivado. Se selecteaza intrarile ca fiind cele 2 Switch-uri de pe placa si iesirea pe Led[0] (se consulta: Pynq-Z2 - Pinout).

Proiectul complet se poate descarca de aici: W2_exemplu_nand.zip

Exercitii

Pentru urmatoarele exercitii se doreste atat testarea designurilor prin simulare cat si punerea acestora pe placa. Legat de placa, intrarile vor fi conectate la butoane, iar iesirile la leduri. Se va consulta tabelul cu pini disponibili (Pynq-Z2 - Pinout).

Exista moduri mai rapide de a scrie functionalitatea dorita (vezi exemplu), dar tema principala a acestui laborator este instantierea, asa ca sunteti rugati sa respectati desenele si sa instantiati fiecare poarta individual.

Exercitiul 1: AND4 din AND2

Acest exercitiu arata cum se construieste o poarta AND cu 4 intrari din porti AND mai simple, cu 2 intrari. Iesirea acesteia va fi "1" doar daca toate intrarile sunt "1".

Schema and4 din and2.png

Pentru testarea circuitului, generati in testbench urmatoarele forme de unda (liniile punctate reprezinta 5ns):

Wavedrom and4.png


Exercitiul 2: OR4 din OR2

Asemanator cu exercitiul anterior, se poate construi si o poarta or cu 4 intrari din porti or cu 2 intrari. Iesirea acesteia va fi "1" doar daca oricare din intrari (cel putin una) este "1".

Pentru testarea circuitului se folosesc formele de unda de la exercitiul 1.

Schema or4 din or2.png


Exercitiul 3: AND4 din AND2 aranjat pe lung (nu arbore)

In multe cazuri, aceeasi functionalitate poate fi atinsa prin circuite care arata diferit. O alta varianta de a face o poarta AND cu 4 intrari este prezentata mai jos.

Comparati cele 2 variante.

Pentru testarea circuitului se folosesc formele de unda de la exercitiul 1.

Schema and4 din and2 v2.png


Exercitiul 4: AND4 din NAND2

Orice poarta logica de baza poate fi construita doar din porti NAND sau doar din porti NOR.

Desenati si implementati circuitul pentru o poarta AND4 din porti NAND cu 2 intrari.

Tip: Incercati intai sa generati o poarta AND2 din NAND2.

Pentru testarea circuitului se folosesc formele de unda de la exercitiul 1.


Exercitiul 5: AND4 pe 4b din AND4 pe 1b

Se pot face operatii logice si pe mai multi biti deodata prin punerea in paralel a mai multor porti cu o singura iesire. Exemplu : pentru intrarile 0011 si 1110 iesirea va fi 0010

Pentru exersarea instantierii si intelegerea circuitului din spatele operatilor multibit implementati acest circuit prin instantierea a 4 porti AND2 pe 1 bit intr-o poarta AND2 pe 4 biti.

Schema and4 4b din and4 1b v1.png

Pentru testarea circuitului, generati in testbench urmatoarele forme de unda (liniile punctate reprezinta 5ns):

Wavedrom and4 4b.png

Exista si un mod mai rapid si simplu de a scrie aceasta functionalitate, grupand intrarile si iesirile in "bus"-uri, ca in codul de mai jos. Acest mod de a grupa firele este desenat mai jos.

Schema and4 4b din and4 1b v2.png

Fisierul and4_4b.v:

module and4_4b
	(
		input wire [3:0] in0,	// in0 are 4b, de la bit 3 la bit 0 inclusiv 
		input wire [3:0] in1,	// se noteaza msb:lsb (most significant bit: least significant bit) (echivalent cu conceptul de cifra sutelor, cifra unitatilor, pt binar)
		input wire [3:0] in2,
		input wire [3:0] in3,
		output wire [3:0] out0
	);
	
assign out0 = in0 & in1 & in2 & in3;
	
endmodule

Exercitiul 6: Desenati schemele logice pentru urmatoarele circuite:

a) Schema 1

module schema1 (a, b, c, d, f);
	input a, b, c, d;
	output f, g;
	
	wire w1;
	wire w2;
	
	and P1 ( w1, a, c );
	or P2 ( f, w1, w2, d);
	not P3 (w2, b);
	and P4 (g, w1, b, d);
endmodule

Observatie: In versiunea veche de verilog (si s-a pastrat si in cea curenta), se pot specifica directiile porturilor si in exteriorul parantezelor, ca mai sus. Aceasta scriere NU este recomandata.


b) Schema 2

module schema2 (a, b, c, d, f);
	input a, b, c, d;
	output f;
	
	wire w1, w2;
	
	nand P1 ( w1, a, b ), P2 (w2, c, d);
	and P3 ( f, w1, w2);
endmodule

Observatie: asemanator cu C/C++, se pot face declaratii in aceeasi linie a mai multor "variabile", aici fire sau instante, cum se poate vedea la w1 si w2 (ambele fiind fire) sau la P1 si P2 (ambele fiind porti nand). Aceasta scriere NU este recomandata.