CID aplicatii 3 : Circuite combinationale elementare (2023)

De la WikiLabs
Jump to navigationJump to search

Teorie

Acest laborator are ca scop scrierea unor circuite combinationale simple si explorarea variantelor de sintaxa diferite ce ajung sa ofere aceeasi functionalitate. Sintaxa va fi explicata in comentariile exemplului rezolvat mai jos.

Descrierea circuitelor in Verilog se face in una din doua forme:

1) structurala - descrie cum un modul e alcatuit din module sau primitive mai simple
2) comportamentala/behavioural - descrie cum se calculeaza iesirile din intrari


Exemplu - Decodorul

Decodorul este un circuit a carui iesire pe n biti are un singur bit de "1", anume cel selectat prin singura sa intrare pe log2(n) biti. Astfel, pentru un decodor cu iesire pe 8b, intrarea va avea 3b (logaritm in baza 2 din 8) si tabelul de adevar va arata astfel:

in(3b) out(8b)
000 0000_0001
001 0000_0010
010 0000_0100
011 0000_1000
100 0001_0000
101 0010_0000
110 0100_0000
111 1000_0000

Iesirea este numerotata de la bitul 7 (msb) pana la bitul 0 (lsb). Se observa ca daca intrarea are valoare "a", singurul bit de "1" din iesire este cel de pe pozitia "a".

Observatie: Atat in tabelul de mai sus, cat si in codul Verilog poate sa apara "_" (underscore) ca sa ajute vizual la separarea/numararea cifrelor. Se foloseste in mod uzual la scrierea in binar ca sa grupeze 4b (o cifra in hexa).


Circuitul exemplu al acestui laborator va fi un decodor mai mic, cu 2 biti de intrare si 4 de iesire, avand tabelul de adevar:

in(2b) out(4b)
00 0001
01 0010
10 0100
11 1000

O varianta de schema care implementeaza acest circuit este cea de mai jos:

W3 exemplu rezolvat (2023).png

In circuitul de mai sus, apare dubla negatie, respectivul semnal putand fi luat si direct de la intrare. Acest lucru se intampla din considerente electrice. Desi pe desene nu sunt trecute, fiecare poarta are si fire de alimentare si de masa. Astfel, pentru a evita fire de lungimi foarte mari sau fire care se propaga in multe intrari si probleme de cadere a tensiunilor, se folosesc astfel de buffere/repetoare.

Codul de mai jos exemplifica moduri diferite de a exprima aceasta functionalitate. El contine comentarii referitoare la sintaxa de baza Verilog, necesara implementarii modulelor in diverse moduri.

Descrierea modulului decodor - varianta structurala din porti, cu primitive (fisierul decodor_v1.v):

module decodor_v1
	(
		input wire [1:0] in,	// definirea intrarilor sub forma de "bus" 
									// bus-ul de intrare se numeste "in" si este pe 2 biti: in[1], in[0]
									// fizic sunt 2 fire distincte, grupate pentru o mai simpla folosire.
		output wire [3:0] out 	// avem o iesire, numita "out" pe 4 biti.
	);
	
wire in_0_inv;
wire in_1_inv;
wire in_0_nat;
wire in_1_nat;

not not_gate_0(in_0_inv,in[0]); // aleg bitul 0 al lui in sa intre in aceasta poarta not;

not not_gate_1(in_1_inv,in[1]); // observatie: cele 2 fire si cele 2 porti putea fi comprimate intr-un bus + poarta not pe 2 biti.

not not_gate_2(in_0_nat,in_0_inv);

not not_gate_3(in_1_nat,in_1_inv);

and and_gate_0(out[0],in_1_inv,in_0_inv);

and and_gate_1(out[1],in_1_inv,in_0_nat);

and and_gate_2(out[2],in_1_nat,in_0_inv);

and and_gate_3(out[3],in_1_nat,in_0_nat);
	
endmodule

Descrierea modulului decodor - varianta cu assign, urmand schema (fisierul decodor_v2.v):

module decodor_v2
	(
		input wire [1:0] in,
		output wire [3:0] out 
	);
	
	// fiecare bit din iesire calculat independent
assign out[0] = ~in[0] & ~in[1];
assign out[1] = in[0] & ~in[1];
assign out[2] = ~in[0] & in[1];
assign out[3] = in[0] & in[1];

endmodule

Descrierea modulului decodor - varianta cu assign, cu operatorul "?" (fisierul decodor_v3.v):

module decodor_v3
	(
		input wire [1:0] in,
		output wire [3:0] out 
	);

// operatorul "?": identic c/c++; (conditie) ? (if_true) : (if_flase);

assign out = (in==0) ? // if in == 0 
				1 : // if true: out = 1 
				(in==1) ? // else, verifica alt if. 
					2 : // if in !=0 si in == 1
					(in==2) ?
						4 :
						8; // last case in==3

endmodule

Descrierea modulului decodor - varianta cu always cu if (fisierul decodor_v4.v):

module decodor_v4
	(
		input wire [1:0] in,
		output reg [3:0] out // <<== !!!! important: orice semnal ia valoarea intrun "always", trebuie sa fie reg
	);						 // always => reg ; assign => wire

always @(*) // mereu cand se schimba ce se afla in paranteze, se executa (ish "executa") ce e mai jos
	// steluta inseamna "orice"
	// se putea pune si always @(in) => mereu cand se schimba "in", aici fiind singura intrare. daca sunt mai multe se poate pune "in0 or in1 ...or inx"
        // ce este intre paranteze se numeste lista de senzitivitati
begin	// begin si end pe post de acolade din c/c++; delimiteaza if/else si altele asemenea; inclusiv always cu totul
	if(in == 0)
		begin // nota autorului: eu prefer sa pun begin/end asa. ele se pot pune si dupa conditie sau aliniate altfel.
				// ^ preferinta personala legata de coding style, important e sa fiti consecventi in cum scrieti
		out = 1; 
		end
	else
		begin
		if(in == 1)
			out = 2;	// fiind o singura instructiune, begin/end se poate omite (identic c/c++)
		else
			begin	// nota autorului: eu personal le pun mereu. in caz ca mai adaug linii in if dupa, sa fie deja puse
			if(in == 2)
				begin
				out = 4;
				end
			else
				begin
				out = 8;
				end
			end
		end
end//end pt always

endmodule

Descrierea modulului decodor - varianta cu always cu case (fisierul decodor_v5.v):

module decodor_v5
	(
		input wire [1:0] in,
		output reg [3:0] out 
	);

always@(*)
begin
	case(in) // functioneaza ca switch/case din c/c++; sintaxa usor diferita
		0: // if in == 0
			begin
			out = 1;
			end
		2'd1: // if in == 1;	2'd1 se traduce: pe 2 biti, in baza zece (eng: decimal, "d"), valoarea 1
			begin	// pt "0" de mai sus: daca nu se pune nimic se considera in baza zece.
			out = 2;
			end
		2'b10: // if in == 2;	scris in binar "b", 2'b10 => adica valoarea 2(10 binar) pe 2 biti 
			begin
			out = 4;
			end
		2'h3: // if in == 3;	scris in hexa (baza 16 "h", se poate si "x"), fiind numar mic, nu se vede diferenta fata de zecimal.
			begin
			out = 8;
			end
		default: // daca in == un caz netrat; 
			begin // aici inutil pt ca am tratat toate cazurile posibile; pus ca exemplu de sintaxa 
			out = 0;
			end
	endcase // orice "case" se inchide cu "endcase"
end 

endmodule

Descrierea modulului decodor - varianta cu assign comportamental (fisierul decodor_v6.v):

module decodor_v6
	(
		input wire [1:0] in,
		output wire [3:0] out 
	);

assign out = 1 << in; 
	// prin shiftare
	// 1<<0 == 1 == 4'b0001; 
	// 1<<1 == 2 == 4'b0010; 
	// 1<<2 == 4 == 4'b0100; 
	// 1<<3 == 8 == 4'b1000; 

endmodule

Descrierea modulului top (fisierul top.v):

// observatie: 
	// puteti da in vivado: rtl analysis -> open elaborated design -> schematic pentru a vedea desenul rezultat
	// puteti vedea ca moduri diferite de scriere produc aparitia a diferite primitive de sinteza
module top
	(
		input wire [1:0] in,
		output wire [3:0] out_v1, // fiecare varianta de decodor cu iesirea lui
		output wire [3:0] out_v2, // toate ar trebui sa scoata acelasi rezultat 
		output wire [3:0] out_v3,
		output wire [3:0] out_v4,
		output wire [3:0] out_v5,
		output wire [3:0] out_v6
	);
	
decodor_v1 decodor_v1_0
	(
		.in(in),
		.out(out_v1)
	);

decodor_v2 decodor_v2_0
	(
		.in(in),
		.out(out_v2)
	);
	
decodor_v3 decodor_v3_0
	(
		.in(in),
		.out(out_v3)
	);
	
decodor_v4 decodor_v4_0
	(
		.in(in),
		.out(out_v4)
	);
	
decodor_v5 decodor_v5_0
	(
		.in(in),
		.out(out_v5)
	);

decodor_v6 decodor_v6_0
	(
		.in(in),
		.out(out_v6)
	);
	
endmodule

Proiectul complet se poate descarca de aici: W3_exemplu_decodor.zip

Exercitii

Exercitiul 1: Multiplexorul

Multiplexorul (mux) este un circuit ce are rolul de a selecta una dintre intrari pentru ca aceasta sa ajunga la iesire.

Imaginati-va o intersectie in care toate masinile vor sa se iasa catre aceasi strada. Pentru a evita accidente, este nevoie de un element de control, de selectie, prin care se hotaraste care dintre intrari este lasata sa ajunga la iesire, in respectivul moment de timp. Multiplexorul poate fi de asemenea vazut ca un comutator.

Observatie 1: conceptul de baza al multiplexarii este absolut vital si o sa apara des in multe zone ale ingineriei si la materii foarte diferite. Ca niste mici exemple, el se foloseste in:

a) telecomunicatii - cine are voie sa comunice pe un fir la un moment de timp
b) internet - routere - cine trimite pachetele tcp/ip catre destinatia x
c) panouri cu leduri - aprindere pe rand a ledurilor (de la instalatii de craciun pana la ecrane/televizoare care aprind randurile alternativ)

Observatie 2: numarul de intrari maxim posibile este 2^(nr_biti_selectie)

Observatie 3: multiplexoarele pot avea intrarile date si iesirea pe mai multi biti.

Observatie 4: este de ajutor sa incepeti sa vedeti circuitele ca avand o cale de date ce sunt procesate si o cale de control care decide cum se face asta. Aici calea de date este alcatuita din intrari si iesire, iar calea de control din firele de selectie. Acest principiu se propaga si in arhitectura microprocesoarelor unde vor aparea magistrale si memorii de date/instructiuni.

Pornind de la exemplul oferit, implementati multiplexorul in cat mai multe variante.

Multiplexorul cu 2 intrari pe un bit fiecare (mux elementar) este desenat in figurile de mai jos: cum este vazut din exterior (prima figura) si alcatuirea lui interna (a doua figura).

Mux2 general (2023).png Mux2 schema interna (2023).png

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

Wavedrom mux2 (2023).png


Exercitiul 2: Implementati un mux4 (multiplexor cu 4 intrari)

Interfata lui este ca in figura de mai jos.

Mux general (2023).png

Pentru varianta lui structurala, mux4 se construieste din mai multe mux2 legate corespunzator. Incercati sa construiti voi aceasta schema.

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

Wavedrom mux4 (2023).png


Exercitiul 3: Demultiplexorul

Demultiplexorul (demux) este un circuit ce are rolul de a selecta catre care iesire se va duce intrarea. Imaginati-va o intersectie in care intra o singura masina si trebuie sa decida daca iese catre stanga/inainte/dreapta.

Pentru acest exercitiu se va implementa un demux2 (cu 2 iesiri).

Observatie 1: celelalte iesiri, cele neselectate iau valoarea 0.

Observatie 2: numarul de iesiri maxim posibile este 2^(nr_biti_selectie)

Interfata demultiplexorului este data mai jos. Incercati sa construiti acest circuit in cat mai multe variante de sintaxa posibile (minim4).

Demux2 general (2023).png

Generati forme de unda pentru a vedea functionarea demultiplexorului.


Exercitiul 4: Sumatorul

Sumatorul are rolul de a scoate la iesire suma celor 2 intrari.

Uzual el mai are si intrare si iesire de "carry" adica de transport (cand se depaseste baza de numarare si e nevoie de inca o cifra, ex in zecimal: 88 + 20 = 108; apare o cifra in plus, cifra sutelor).

Sumator 8b general (2023).png

Observatie: desi scrierea cu assign si + este cea mai usoara si clara pentru un sumator, si celelalte moduri de scriere sunt posibile. La aceasta scriere, daca se doresta ca sumatorul sa aiba carry out, sumatorul se poate face cu un bit mai mare decat intrarile, iar acel bit sa fie conectat la carry out. Practic, obtii un rezultat pe "n" biti + un bit de depasire, anume msb-ul.

Generati formele de unda necesare testarii sumatorului pe 8b.

Pentru a vedea pas cu pas cum se construieste un sumator pe 8b puteti parcurge urmatoarea platforma de laborator: Sumator.pdf


Exercitiul 5: Comparatorul

Comparatorul are rolul de a vedea relatia dintre cele 2 intrari ale sale, daca cele 2 numere sunt egale, daca primul e mai mic sau mai mare.

Astfel el poate avea 3 iesiri dintre care se activeaza doar una:

- iesire de egalitate
- iesire de in0 < in1
- iesire de in0 > in1


Se mai poate face si o varianta de comparator cu 2 iesiri, asa cum veti studia la arhitectura microprocesoarelor.

- iesire de egalitate
- iesire care este 0 daca in0<in1 sau 1 daca in0>in1

Aceasta abordare este folosita la AMP la flag-urile din procesoare, folosind operatia de scadere si flag-urile de "zero" si "negativ".

Scrieti modulul de comparator, in ambele variante de interfata. Intrarile pot avea oricat de multi biti (comparator mai mic sau mai mare), pentru acest exercitiu sa il faceti pe 4b.

Scrieti un testbench care sa genereze toate combinatiile posibile de date de la intrare.

Discutie: Daca acest comparator are datele pe 32b sau mai mult (sau daca circuitul devine mult mai complex), nu se mai poate face o astfel de testare exhaustiva a combinatiilor posibile de date de la intrare pentru ca simularea ar dura prea mult. De situatia asta se ocupa inginerii de verificare ce lucreaza in paralel cu inginerii de design. Pentru mai multe detalii puteti cauta "constrained random functional verification". Incercati sa identificati cazurile/situatiile ce ar trebui testate pentru un comparator pe 32b inainte de a spune ca acesta functioneaza corect (minim 10).