CID aplicatii 2 : Instantiere si porti logice

De la WikiLabs
Versiunea din 8 martie 2022 12:09, autor: Gvpopescu (Discuție | contribuții) (Exemplul 2: NAND2 din AND2 si NOT)

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 mult de 2 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

Despre FPGA

FPGA-ul (Field-Programmable Gate Array) este un circuit programabil, capabil sa implementeze circuite definit de utilizator. El este format dintr-o matrice de blocuri programabile, interconectate intre ele printr-o serie de conexiuni la randul lor programabile.


Cand se doreste implementarea unui circuit pe FPGA, acesta urmeaza urmatoarele etape:

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).
Generarea Bitstream-ului este procesul prin care informatiile din implementare sunt asamblate intr-un singur fisier.
Programarea este procesul prin care fisierul generat anterior este trimis efectiv catre placa cu FPGA (prin USB) unde determina modificarea valorilor si conexiunilor interne din aceasta.


O introducere mai detaliata poate fi gasita aici : FPGA - Introducere.

Fpgaimg.PNG

Observatii:

  • Pentru a putea controla circuitul propus si a putea vedea rezultatele, intrarile si iesirile (input si output din module) in/din acesta trebuie conectate la pini fizici ai FPGA-ului care sunt conectati la butoane/switch-uri/leduri.
  • Conexiunile dintre butoane/switch-uri si pinii FPGA sunt fixe. La fel si cele intre pinii FPGA si LED-uri. (in functie de PCB)
  • Conexiunile dintre porturile modulului Module si pinii FPGA sunt configurabile. (in functie de noi, prin fisierul XDC)
  • 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

Exemplul 1: 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 )

Exemplul 2: 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.


Implementarea circuitului pe FPGA

Dupa ce circuitul a fost testat in simulare, se doreste punerea sa fizica pe placa FPGA. Pentru aceasta, se tine cont de urmatoarele.

  • Pentru a controla intrarile a si b ale circuitului 'top, va trebui sa le conectam prin intermediul conexiunilor configurabile ale FPGA-ului la pini ce sunt mai departe conectati fizic la dispozitive ce pot controla valorile semnalelor (switch-uri, butoane).
  • Pentru a observa valoarea iesirii, vom conecta semnalul de iesire cla un dispozitiv de observare, cum ar fi un LED, care va fi aprins daca c este 1 si stins daca c este 0.

NAND2 FPGA.png

Pentru a realiza conectarea porturilor modulului top la pinii fizici ai FPGA ce sunt conectati mai departe la componentele de pe placa, va trebui sa specificam in utilitarul de sinteza maparea porturilor la acestia. Maparea se realizeaza prin mentionarea codurilor pinilor la care dorim conexiunea si poata numele de constrangeri de I/O. Aceste coduri sunt mentionate in documentatia placii de dezvoltare cu FPGA (in cazul nostru, PYNQ Z2) si pot fi regasite si in pagina PYNQ-Z2 - Pinout.

Conform schemei, la acest exemplu avem nevoie de codurile pinilor conectati mai departe la Switch0, Switch1 si LED0.

Observatie: Daca intrarile si iesirile ar fi semnale pe mai multi biti, fiecare bit al intrarilor va fi conectat la cate un switch/buton si fiecare bit al iesirii va fi conectat la un LED. Acest lucru este necesar deoarece switch-urile/butoanele pot avea doar doua stari (0 si 1) si astfel pot controla un singur bit. La fel, un LED poate avea doar doua stari (0 - stins si 1 - aprins) si poate afisa starea unui singur bit.

Dupa ce ati extras codurile necesare, urmati pasii descrisi in tutorialul Vivado.

Dupa programarea cu succes a placii, puteti testa functionalitatea circuitului prin modficarea valorilor switch-urilor si observarea starii LED-ului.

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.