Menu

Pierwszy moduł w Verilogu: przewodnik krok po kroku

Napisz od zera swój pierwszy kompletny moduł w Verilogu: deklarację, porty, fragment logiki kombinacyjnej i testbench, który nim steruje. Do uruchomienia w przeglądarce.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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_gate deklaruje moduł o nazwie and_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: a to port wejściowy typu wire (sterowany z zewnątrz).
  • output wire y: y to 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, b i wire y. Wejścia testowanego projektu (DUT) są w testbenchu typu reg, bo sterujemy nimi z bloku proceduralnego. Wyjście jest typu wire, bo steruje nim DUT.
  • and_gate dut(.a(a), .b(b), .y(y)). To tworzenie instancji. Wybijamy kopię and_gate i nazywamy ją dut (popularna nazwa od "design under test"). Składnia .a(a) mówi "podłącz port instancji o nazwie a do lokalnego sygnału o nazwie a". 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.
  • #1 przesuwa czas symulacji o 1 jednostkę. Jest to potrzebne, żeby y zdążyło się ustalić po zmianie wejść. Bez #1 $display wypisałoby poprzednią wartość y.
  • $display(...) wypisuje tekst w konsoli symulacji. Ciąg formatujący działa jak printf w C: %b to zapis binarny, %d dziesiętny, %h szesnastkowy, a %t to czas symulacji.
  • $finish koń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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ