CID aplicatii 3 : Circuite combinationale elementare
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 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:
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<<1 == 1 == 4'b0001;
// 1<<2 == 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).
Exercitiul 2: Implementati un mux4 (multiplexor cu 4 intrari)
Interfata lui este ca in figura de mai jos.
Pentru varianta lui structurala, mux4 se construieste din mai multe mux2 legate corespunzator. Incercati sa construiti voi aceasta schema.
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).
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).
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.
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.