Brak produktów
Liczniki to układy o bardzo wszechstronnym zastosowaniu. Zliczanie impulsów, mierzenie czasu, itp. Liczniki zbudowane są z przerzutników, ale na szczęście nie trzeba dokładnie znać, w jaki sposób są połączone ze sobą. Dzięki językowi Verilog, można tworzyć skomplikowane liczniki w bardzo prosty sposób.
Na początek bardzo prosty licznik 4 bitowy. Ma tylko jedno wejście zegarowe clk oraz jedno wyjście danych, w postaci 4 bitowej magistrali. Słówko reg oznacza, że mamy do czynienia z układem wyposażonym w pamięć, a dokładniej w cztery przerzutniki. Licznik reaguje na zbocze rosnące sygnału zegarowego, o czym świadczy posedge clk. Gdyby miał reagować na zbocze malejące, należałoby wpisać negedge clk. Nasz prosty licznik liczy do 15, a potem znów zaczyna od zera. Dlaczego 15? Licznik jest 4-bitowy i daje to 16 kombinacji możliwych stanów, od 0, do 15. Można wyliczyć to prostym wzorkiem 2^4-1.
module counter( input clk, output reg [3:0] licznik ); always @(posedge clk) licznik <= licznik + 1; endmodule
Jeżeli chcemy mieć wpływ na to, czy licznik liczy w górę czy w dół, musimy dodać jeszcze jedno wejście. Nazwałem je dir. Jeżeli dir=1 to licznik liczy w górę. Jeżeli nie, to liczy w dół. Co się stanie jeżeli licznik jest w stanie 0, a otrzyma polecenie odjęcia 1 od zera? Wtedy licznik przyjmie wartość maksymalną - w tym przypadku 15.
module counter( input clk, input dir, output reg [3:0] licznik ); always @(posedge clk) if(dir) licznik <= licznik + 1; else licznik <= licznik - 1; endmodule
Synchroniczny reset to zerowanie wartości licznika tylko wtedy, jeżeli wystąpi odpowiednie zbocze sygnału zegarowego. Ważny z tego wniosek - jeżeli sygnał resetujący pojawia się tylko na krótką chwilę między zboczami, to zostanie zignorowany. Taka konstrukcja jednak zabiera mniej zasobów logicznych niż reset asynchroniczny i nie występują problemy z metastabilnością. Więcej na ten temat na ASIC WORLD.
Wystarczy jedynie rozbudować drzewko decyzyjne if-else o kolejną pozycję - jeżeli reset=1 to ma nastąpić przypisanie licznikowi wartości 0. Poszedłem tutaj na skróty, ponieważ zaleca się, by przy stałych liczbach podawać także ilość bitów, jakie liczba zajmuje. W tym przypadku byłaby to 4-bitowa liczba 0 zapisana w formacie binarnym, czyli 4'b0000. Jednak ISE od Xilinxa jest na tyle rozgranięte, program połapie się o co chodzi.
module counter( input clk, input dir, input reset, output reg [3:0] licznik ); always @(posedge clk) if(reset) licznik <= 0; else if(dir) licznik <= licznik + 1; else licznik <= licznik - 1; endmodule
Asynchroniczny reset to natychmiastowe wyzerowanie licznika, niezależnie od stanu sygnału zegarowego. Jest to bardzo proste! Słówko always@ mówi nam, na co licznik ma reagować. Musimy więc do nawiasów dopisać jeszcze po przecinku posegde reset. Układy XC9572XL wyposażone są w specjalną linię GSR (Global Set Reset), która służy do prowadzenia sygnałów resetu.
module counter( input clk, input dir, input reset, output reg [3:0] licznik ); always @(posedge clk, posedge reset) if(reset) licznik <= 0; else if(dir) licznik <= licznik + 1; else licznik <= licznik - 1; endmodule
Rozwijamy dalej nasz licznik - dodajmy do niego możliwość ustawiania wartości początkowej. Wartość ta będzie pobierana z 4-bitowej magistrali dane, jeżeli na linii load pojawi się 1. Myślę, że kod nie wymaga komenarza.
module counter( input clk, input dir, input reset, input load, input [3:0] dane, output reg [3:0] licznik ); always @(posedge clk, posedge reset) if(reset) licznik <= 0; else if(load) licznik <= dane; else if(dir) licznik <= licznik + 1; else licznik <= licznik - 1; endmodule
W przypadku trochę bardziej rozbudowanych projektów, często zachodzi potrzeba szybkiej zmiany niektórych ustawień, bez grzebania w kodzie. Np. można w ten sposób ustawić rozmiar pamięci, stopień podziału w dzielniku, a w tym przykładzie będzie to liczba bitów naszego licznika i tym samym zakres liczenia. Wygodnym rozwiązaniem są parametry. Rzuć okiem na kod.
Pojawił się zapis #(parameter bity = 4), co oznacza, że licznik jest 4-bitowy. Jeżeli zachodzi potrzeba zmiany, można to zrobić na dwa sposoby. Pierwszy sposób - po prostu w tym pliku zminić 4 na inną liczbę. Drugi sposób - w wywołaniu instancji licznika ustawić inną wartość parametru, a 4 zostawić w spokoju. Syntezator potraktuje wówczas czwórkę jako wartość domyślną, która została nadpisana przez inną wartość. Może brzmi trochę mętnie, ale opiszę to dokładniej w przyszłości.
Zmienił się też sposób definiowania szerokości magistral. Wcześniej było output reg [3:0] licznik, a teraz jest output reg [bity-1:0] licznik. Sprawa jest bardzo prosta - syntezator podstawia 4 pod słówko bity, potem odejmuje 1 i dostaje w wyniku 3, tak jak było wcześniej.
module counter #(parameter bity = 4)( input clk, input dir, input reset, input load, input [bity-1:0] dane, output reg [bity-1:0] licznik ); always @(posedge clk, posedge reset) if(reset) licznik <= 0; else if(load) licznik <= dane; else if(dir) licznik <= licznik + 1; else licznik <= licznik - 1; endmodule
Aby uprościć przykład, usunąłem sygnał reset, load i inne niepotrzebne rzeczy. Ten licznik liczy naprzemiennie w górę i w dół pomiędzy wartością maksymalną i minimalną. Aby licznik wiedział, w którą stronę ma liczyć, dodałem przerzutnik dir. Jeśli dir=0 to liczymy w górę, w jeśli dir=1 to licznik liczy w dół.
Zmiana kierunku liczenia następuje, jeżeli wartość licznika zrówna się z parametrami min lub max. W tym celu utworzyłem jeszcze jeden blok always, który reaguje na każdą zmianę licznika. Jeżeli licznik osiągnął wartość maksymalną, wówczas dir zmienia się na 1. Jeżeli licznik=min, to dir=0, a jeżeli wartość licznika nie jest równa ani min, ani max, to nic się nie dzieje.
Wartości min i max ustawione są w dwóch parametrach. Widać, że licznik liczy od 5 do 10 i z powrotem. Proszę pamiętać, że po włączeniu CPLD do zasilania, licznik jest wyzerowany, tak więc na początku będzie 0, 1, 2, 3... Nic nie stoi na przeszkodzie, by wartość max była mniejsza od min. Można zrobić np. taką sekwencję: 14, 15, 0, 1, 2 i w z powrotem.
module counter( input clk, output reg [3:0] licznik ); parameter max = 4'd10; parameter min = 4'd5; reg dir; always @(posedge clk) if(dir) licznik <= licznik - 1; else licznik <= licznik + 1; always @(licznik) if(licznik == max) dir <= 1; else if(licznik == min) dir <= 0; endmodule
Układ po włączeniu zasilania może mieć pewną wartość początkową, określoną w bloku initial. W tym przykładzie licznik rozpocznie liczenie od wartości 0011, czyli od 3.
module counter( input clk, output reg [3:0] licznik ); always @(posedge clk) licznik <= licznik + 1; initial begin licznik = 4'b0011; end endmodule