CID aplicatii 10 : Aplicatii cu numaratoare
Teorie
Numaratoarele au extrem de multe aplicatii in lumea reala, cateva dintre acestea fiind descrise mai jos.
Exemplu: Numaratul apasarilor unui buton
Un prim sistem simplu cu numarator poate fi cel care contorizeaza de cate ori a fost apasat un buton sau, echivalent, cate persoane/masini au trecut printr-un senzor, de cate ori a fost deschisa o usa etc. Acest sistem se poate apoi dezvolta cu usurinta pentru a numara si oamenii care trec invers prin respectivul set de senzori prin adaugarea functionalitatii de up/down la numarator.
Un astfel de sistem simplu (varianta doar cu numarat intrari) ar arata in felul urmator:
Circuitul de debounce are rolul de a "curata" semnalul de intrare, astfel incat chiar si in prezenta zgomotului sau a unei activari indelungate a senzorului/butonului, sa se contorizeze un singur eveniment. O explicatie mai detaliata puteti gasi aici.
Numaratorul numara evenimentele.
Transcodorul pentru display cu 7 segmente este apoi folosit pentru o mai usoara vizualizare a numarului curent.
Observatie: transcodorul pentru 7seg din exemplul de mai jos este in logica negativa. Daca circuitul fizic este in logica pozitiva trebuie adaugat un inversor la iesirea din acesta. De asemnea consider segmentul "a" pe bitul "0" si segmentul "h" pe bitul "6".
Descrierea debouncer-ului (fisierul debounce.sv):
module debounce
#(
parameter limit = 20'd650000
) (
input logic clock,
input logic in,
output logic out
);
logic [19:0] counter; // observatie: si circuitul de debounce foloseste un numarator
logic hit;
assign out = (counter == limit);
always_ff @(posedge clock)
begin
if(in == 0)
begin
counter <= 0;
hit <= 0;
end
else
begin
if(counter == limit)
begin
hit <= 1;
counter <= counter + 1;
end
else
begin
if(in == 1 & hit == 0)
begin
counter <= counter + 1;
end
end
end
end
endmodule
Descrierea numaratorului pe 4b(fisierul counter_4b.sv):
module counter_4b
(
input logic clock,
input logic reset,
input logic en,
output logic [3:0] out
);
always_ff @(posedge clock)
begin
if(reset == 1)
begin
out <=0;
end
else
begin
if(en == 1)
begin
out <= out + 1;
end
else
begin
out <= out;
end
end
end
endmodule
Descrierea transcodorului pentru afisaj cu 7seg(fisierul transcodor_7seg.sv):
module transcodor_7seg // logica negativa
(
input logic [3:0] in,
output logic [6:0] out
);
always_comb
begin
case(in)
4'd0: out = 7'b1000000;// h....a
4'd1: out = 7'b1111001;
4'd2: out = 7'b0100100;
4'd3: out = 7'b0110000;
4'd4: out = 7'b0011001;
4'd5: out = 7'b0010010;
4'd6: out = 7'b0000010;
4'd7: out = 7'b1111000;
4'd8: out = 7'b0000000;
4'd9: out = 7'b0010000;
4'd10: out = 7'b0001000;
4'd11: out = 7'b0000011;
4'd12: out = 7'b1000110;
4'd13: out = 7'b0100001;
4'd14: out = 7'b0000110;
4'd15: out = 7'b0001110;
default: out = 7'b1111111;//tot stins
endcase
end
endmodule
Descrierea sistemului, modulul de top (fisierul top.sv):
module top
(
input logic clock,
input logic reset,
input logic button,
output logic [6:0] out // logica negativa
);
logic debounce_0_X_out;
logic [3:0] counter_4b_0_X_out;
debounce
#(
.limit(20'd50_000)
) debounce_0 (
.clock(clock),
.in(button),
.out(debounce_0_X_out)
);
counter_4b counter_4b_0
(
.clock(clock),
.reset(reset),
.en(debounce_0_X_out),
.out(counter_4b_0_X_out)
);
transcodor_7seg transcodor_7seg_0 // logica negativa
(
.in(counter_4b_0_X_out),
.out(out)
);
endmodule
Descrierea test bench-ului(fisierul tb.sv):
`timescale 1ns / 1ps
module tb();
logic clock_tb;
logic reset_tb;
logic button_tb;
logic [6:0] out_tb;
top dut
(
.clock(clock_tb),
.reset(reset_tb),
.button(button_tb),
.out(out_tb) // logica negativa
);
initial
begin
clock_tb = 0;
forever
begin
#5 clock_tb = ~clock_tb;
end
end
initial
begin
reset_tb = 0;
button_tb = 0;
#50;
reset_tb = 1;
#50;
reset_tb = 0;
#100;
repeat(5) // vreau 5 apasari de buton
begin
button_tb = 1;
repeat(70_000)
begin
@(posedge clock_tb);
end
button_tb = 0;
repeat(70_000)
begin
@(posedge clock_tb);
end
end
#1000 $stop();
end
endmodule
Exercitii
Exercitiul 1: Divizor de frecventa cu puteri ale lui 2
Divizorul de frecventa are rolul de a genera un semnal de ceas mai lent din semnalul de ceas principal. El este alcatuit doar dintr-un numarator, iar fiecare bit al acestuia va fi practic un semnal de ceas cu frecventa din ce in ce mai mica astfel (la jumate):
bit[0] - frecventa de 2 ori mai mica decat semnalul de ceas
bit[1] - frecventa de 2 ori mai mica decat bit[0], deci de 4 ori mai mica decat semnalul de ceas
bit[2] - frecventa de 2 ori mai mica decat bit[1], deci de 8 ori mai mica decat semnalul de ceas
Acest lucru se poate observa si in poza ce urmeaza:
Implementati acest circuit, si folosind ledurile prezente pe placa, incercati sa conectati bitul corespunzator la leduri astfel incat cel mai din dreapta led sa clipeasca cu o frecventa cat mai apropiata de 1s.
Observatie: Printr-un astfel de divizor de frecventa minimal, un numar foarte mic de frecvente poate fi generat. Daca se doreste generarea unui semnal periodic cu o perioada exacta, diferita de cele posibile prin acest mecanism, se poate construi urmatorul circuit:
Exercitiul 2: Divizor de frecventa ce poate genera orice perioada
Un astfel de divizor de frecventa este construit ca in figura de mai jos:
Prin adaugarea constantei "limit", perioada semnalului de iesire poate sa fie orice multiplu a perioadei semnalului de ceas.
Bistabilul de tip t, se modifica atunci cand limita este atinsa si practic iesirea lui este semnalul periodic dorit (cu observatia ca o perioada a semnalului de iesire necesita 2 numarari pana la limita).
Sa se calculeze valoarea necesara pentru "limit" astfel incat pe leduri sa se genereze semnal cu perioada exact 1s.
Observatie: In FPGA exista fizic trasee speciale pentru reset si ca sinteza sa le foloseasca pe acestea in mod optim nu este recomandat sa se puna logica (cum e aici poarta sau) pe acestea. Modificati schema de mai sus astfel incat sa eliminati respectiva poarta de pe reset. (Sfat: numaratorul va avea acum si intrari de load: semnalul de comanda si data propriu zisa)
Exercitiul 3: PWM (Pulse Width Modulation)
PWM este un concept folosit foarte des in multiple ramuri ale ingineriei, de la transmisiunea informatiei pana la controlul motoarelor sau al intensitatii ledurilor din instalatii de Craciun. El se refera la a avea un semnal periodic cu factor de umplere variabil, asa cum este aratat in poza de mai jos :
Generarea unui astfel de semnal periodic se face printr-un numarator si un comparator, ca in figura de mai jos:
Raportul dintre limita pusa si valoarea la care numaratorul se reseteaza este practic factorul de umplere selectat.
Pentru testare pe placa, cei 6b ai limitei provin de la switch-uri si butoane. Afisarea se face pe led[0].
Testati acest circuit in simulare si apoi implementati pe placa pentru o valoare fixa a limitei. Implementati in paralel mai multe generatoare de semnal PWM cu limite diferite, astfel incat sa observati diferentele de intentistate dintre ledurile comandate de acestea.
Exercitiul 4: PWM cu limita variabila
Un exemplu foarte uzual de folosire a PWM este aprinderea ledurilor cu diverse pattern-uri. Factorul de umplere determina intensitatea cu care ledul este aprins. Un PWM cu o limita variabila automata va face ledul sa para din ce in ce mai stins sau din ce in ce mai luminos (exemplu: instalatii de Craciun). Combinand asta cu leduri RGB se obtin efecte de schimbare a culorii ledului aparent la intamplare.
Un astfel de circuit se realizeaza punand inca un numarator in locul limitei, ca in poza de mai jos:
Pentru o functionalitate suplimentara, anume a pastra un anumit factor de umplere mai multe perioade, am adaugat inca un numarator.
Pentru claritatea desenului, nu am mai tras efectiv firul de ceas catre intrarile unde acesta se duce.
Rezultatul este:
Testati circuitul propus prin simulare si apoi vizualizati implementarea sa pe placa. Alegeti limite potrivite astfel incat sa puteti observa usor rezultatul.
Bonus: Incercati sa adaugati si parametri pentru LIMIT_DUTY_CYCLE_LOW si LIMIT_DUTY_CYCLE_HIGH, care sa permita factorului de umplere sa varieze doar intre ele (numaratorul va avea nevoie de intrari pentru comanda de load si data load, ca sa poata incepe de la orice valoare).
Bonus: Modificati circuitul astfel incat semnalul de iesire sa isi schimbe sensul de variatie al factorului de umplere cand ajunge cu acesta la capat. In forma curenta factorul de umplere creste de la 0% la 100% si apoi se reseteaza brusc la 0%, repetand acest ciclu. Se vrea ca la ajungerea la 100% sa inceapa o scadere treptata catre 0%, urmata apoi de o urcare s.a.m.d.
Exercitiul 5: Ceas
Un ceas poate fi construit cifra cu cifra, folosind numaratoare si comparatoare (si transcodoare pentru display cu 7seg ca sa se vada totul mai bine pe placa).
Incercati sa implementati un ceas cu milisecunde, secunde si minute in varianta comportamentala.
Observatie: Va fi nevoie fie separat, fie in modul de un numarator cu frecventa rezolutiei dorite (aici 1 ms).
Observatie: Codul va contine multe "if" de tipul "daca cifra unitatilor secundelor a ajuns la 10, se face 0 si cresc cifra zecilor", repetate pentru fiecare cifra.
Incercati si o implementare structurala a ceasului, mergand pe aceeasi idee. Fiecare cifra va contine numaratorul ei. Cand numaratorul unei cifre ajunge ajunge la limita sa, va da enable pentru numaratorul cifrei urmatoare. Daca este nevoie se pot folosi si porti aditionale (ex: ca sa creasca minutul cifra zecilor secundelor trebuie sa fie 5 si cifra unitatilor secundelor trebuie sa fie 9).
Observatie: Va fi nevoie de un numarator cu frecventa rezolutiei dorite (aici 1 ms sau 1s).
Observatie: Pentru a scrie mai putin puteti face un numarator doar cu secunde si minute. Principiul de baza este acelasi si daca aveati milisecunde.
Observatie: Simularea a cateva milisecunde sau chiar secunde este un proces indelungat (dureaza minute-ore fizice), astfel strict pentru simulari se recomanda ceasul sa functioneze cu microsecunde sau milisecunde maxim.