Moduł to podstawowa jednostka Veriloga
Wszystko w Verilogu mieszka wewnątrz module. Moduł obejmuje fragment układu: deklaruje, jakie sygnały wchodzą, jakie wychodzą i jakie przewody, rejestry i logika są między nimi. Każdy chip, jaki kiedykolwiek widziano, to drzewo modułów tworzących instancje innych modułów, aż do prymitywów bramek dostarczanych przez producenta.
Kształt jest zawsze taki sam:
module name(port_list);
// deklaracje: wire, reg, parameter
// ciało: przypisania assign, instancje, bloki always
endmodule
Kompletny moduł zbudujemy etapami.
Najmniejszy użyteczny moduł: dwuwejściowa bramka AND
Potrzebujemy czegoś prostego i samodzielnego. Dwuwejściowa bramka AND nadaje się idealnie: dwa wejścia, jedno wyjście, jedna linia logiki.
Kliknij Uruchom. Powinny pojawić się cztery wiersze tablicy prawdy bramki AND. Przejdźmy przez każdy element.
Czytanie deklaracji modułu
module and_gate(
input wire a,
input wire b,
output wire y
);
module and_gatedeklaruje moduł o nazwieand_gate. Pod tą nazwą inne moduły będą tworzyć jego instancje.- Lista w nawiasach to lista portów, czyli sygnały widoczne z zewnątrz.
input wire a:ato port wejściowy typuwire(sterowany z zewnątrz).output wire y:yto port wyjściowy sterowany przez coś wewnątrz modułu.
Jeśli chcesz pisać zwięźlej, możesz użyć input a zamiast input wire a, bo sam kierunek ustawia domyślny typ wire. Warto jednak wyrobić sobie nawyk pisania jawnie. Pełny zestaw form portów omawia strona Porty modułu.
Ciało
assign y = a & b;
To przypisanie ciągłe. Mówi "za każdym razem, gdy zmieni się a lub b, oblicz y na nowo jako bitowe AND obu sygnałów". Nie ma zegara ani timingu: ta zależność jest prawdziwa zawsze. To czysta logika kombinacyjna.
endmodule zamyka blok. Moduł jest gotowy.
Testbench
Samego and_gate nie da się uruchomić. Potrzebujesz drugiego modułu, który steruje jego wejściami i obserwuje wyjścia. To właśnie testbench, a zwyczajowo nazywa się go test, tb albo <design>_tb.
module test;
reg a, b;
wire y;
and_gate dut(.a(a), .b(b), .y(y));
...
endmodule
Warto zauważyć trzy rzeczy:
- Brak listy portów. Testbench to szczyt symulacji: ponad nim nie ma niczego.
reg a, biwire y. Wejścia testowanego projektu (DUT) są w testbenchu typureg, bo sterujemy nimi z bloku proceduralnego. Wyjście jest typuwire, bo steruje nim DUT.and_gate dut(.a(a), .b(b), .y(y)). To tworzenie instancji. Wybijamy kopięand_gatei nazywamy jądut(popularna nazwa od "design under test"). Składnia.a(a)mówi "podłącz port instancji o nazwieado lokalnego sygnału o nazwiea". Więcej szczegółów znajdziesz na stronie Tworzenie instancji modułów.
Pobudzenie
initial begin
a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
$finish;
end
Blok initial wykonuje się raz, na początku symulacji. W jego wnętrzu:
a = 0; b = 0;ustawia wejścia. To przypisania blokujące: kolejność ma znaczenie, a każde wykonuje się przed następnym.#1przesuwa czas symulacji o 1 jednostkę. Jest to potrzebne, żebyyzdążyło się ustalić po zmianie wejść. Bez#1$displaywypisałoby poprzednią wartośćy.$display(...)wypisuje tekst w konsoli symulacji. Ciąg formatujący działa jakprintfw C:%bto zapis binarny,%ddziesiętny,%hszesnastkowy, a%tto czas symulacji.$finishkończy symulację. Bez niego symulator przesuwałby czas w nieskończoność, czekając na zdarzenie, które nigdy nie nadejdzie.
Spróbuj coś zepsuć
Zmień moduł w bramkę OR (zamień & na |) i uruchom ponownie. Tablica prawdy się zmieni. Teraz spróbuj XOR:
Ten sam szkielet. Inny operator. Na tym polega cała zabawa z logiką kombinacyjną: zadeklaruj porty, napisz przypisania assign, przejdź przez wszystkie wejścia, obserwuj wyjścia.
Moduł z dwoma wyjściami
Moduły mogą mieć więcej niż jedno wyjście. Oto półsumator: dwa wejścia, suma i przeniesienie:
Dwie instrukcje assign stoją obok siebie, ale działają równolegle: nie ma "najpierw oblicz sum, potem carry". Obie są prawdziwe zawsze. To współbieżność, o której mówiliśmy na stronie Sprzęt a oprogramowanie, tym razem w konkretnej postaci.
Co już wiesz
Znasz cały szkielet pliku Verilog: moduł projektu z zadeklarowanymi portami i ciałem oraz moduł testbencha, który tworzy jego instancję, steruje wejściami w bloku initial i raportuje wyniki. Prawie każdy plik źródłowy Veriloga, jaki przeczytasz, pasuje do tego szablonu. Reszta języka to już tylko bogatsza logika, sygnały wielobitowe, zachowanie taktowane zegarem i większe testbenche.
Następnie: komentarze i styl kodu, żeby moduły pozostały czytelne, gdy rosną.
Najczęściej zadawane pytania
Jaki jest najprostszy moduł w Verilogu?
Najmniejszy poprawny moduł w Verilogu to po prostu module name; endmodule, bez portów i bez ciała. Najmniejszy użyteczny to moduł z jednym wyjściem: module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. To prawdziwy fragment logiki kombinacyjnej, który możesz wstawić do dowolnego większego projektu.
Jak uruchomić moduł w Verilogu?
Samego modułu nie da się uruchomić, bo to opis układu, a nie program. Piszesz moduł testbencha, który tworzy instancję twojego projektu i steruje jego wejściami, potem kompilujesz oba przez iverilog -o sim design.v test.v i uruchamiasz vvp sim. Edytor w przeglądarce na tej stronie robi oba kroki za ciebie po kliknięciu Uruchom.
Czym jest testbench w Verilogu?
Testbench to drugi moduł, zwykle bez portów, którego zadaniem jest przetestowanie twojego projektu. Tworzy instancję projektu, zmienia jego wejścia w bloku initial, obserwuje wyjścia przez $display lub $monitor i na końcu wywołuje $finish. Testbenche nie są syntezowalne: istnieją tylko po to, żeby weryfikować zachowanie.
Dlaczego kod w Verilogu potrzebuje $finish?
Bo sprzęt nigdy się nie zatrzymuje. Symulator udaje, że płynie czas, i bez jawnego $finish przesuwałby go w nieskończoność, czekając na nowe zdarzenia. $finish mówi symulatorowi 'koniec, zakończ działanie czysto'. W testbenchu to ostatnia linia bloku initial: uruchom test, a potem zakończ.