Menu

Tworzenie instancji modułów w Verilogu: łączenie podmodułów

Jak utworzyć instancję jednego modułu w drugim, czym różnią się połączenia portów po nazwie i po pozycji oraz jakie wzorce z wieloma instancjami służą do budowy prawdziwych projektów.

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

Moduły w modułach

Projekt w Verilogu to drzewo modułów. Moduł najwyższego poziomu (testbench albo główna obudowa układu) tworzy instancje modułów niższego poziomu, które tworzą instancje modułów jeszcze niższego poziomu, aż do prymitywów bramek dostarczanych przez producenta. Tworzenie instancji to składnia tego zagnieżdżania.

Ten kształt już znasz, bo użyliśmy go w artykule Twój pierwszy moduł:

and_gate dut(.a(a), .b(b), .y(y));

Ta linia tworzy jedną instancję and_gate, nadaje jej nazwę dut i łączy jej porty z lokalnymi sygnałami. Rozłóżmy ją na części.

Kształt instancji

module_name instance_name (port_connections);
  • module_name musi zgadzać się z nazwą z deklaracji module gdzieś w projekcie. Verilog rozróżnia wielkość liter.
  • instance_name to wybrana przez ciebie etykieta, zwykle opisująca rolę tej instancji. Użyjesz jej w ścieżkach hierarchicznych i w widoku przebiegów.
  • port_connections łączą porty instancji z lokalnymi sygnałami. Można to zapisać na dwa sposoby.

Połączenia portów po nazwie (używaj ich)

Forma z nazwami wygląda tak:

my_module instance_name(
    .clk    (clk),
    .reset  (reset_n),
    .data_in(in_bus),
    .data_out(out_bus),
    .valid  (out_valid)
);

Każda para .port(signal) oznacza "połącz port tej instancji o nazwie port z lokalnym sygnałem o nazwie signal". Kolejność nie ma znaczenia. Jeśli dodasz nowy port do deklaracji modułu, istniejące instancje się nie zepsują, o ile nowy port ma wartość domyślną albo zaktualizujesz każde miejsce użycia.

Dwie praktyczne uwagi:

  • Nazwa portu (przed nawiasem) musi dokładnie odpowiadać deklaracji modułu.
  • Nazwa sygnału (w nawiasie) jest lokalna dla miejsca, w którym znajduje się instancja, zwykle modułu nadrzędnego.

Jeśli port ma zostać niepodłączony, zostaw nawias pusty: .optional_port(). Sygnał wewnątrz instancji pozostaje wtedy nieustalony (z). Niektóre narzędzia do syntezy ostrzegają, ale większość to akceptuje.

Połączenia portów po pozycji (unikaj ich)

Krótsza forma wypisuje sygnały w kolejności listy portów:

my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);

Jest krótsza, ale krucha. Zmień kolejność portów modułu (a taki refaktor naprawdę się zdarza), a każda instancja pozycyjna po cichu podłączy się źle. Nie sięgaj po styl pozycyjny, chyba że lista portów ma dokładnie jeden lub dwa elementy i raczej nigdy się nie zmieni.

Gdzie jest nadal do przyjęcia: w malutkich modułach pomocniczych, w których kolejność portów jest częścią API. Bramka o dwóch wejściach może być podłączana pozycyjnie. Kontroler pamięci z 30 portami to proszenie się o kłopoty.

Kompletny przykład hierarchiczny

To prawdziwa trzypoziomowa hierarchia: test → full_adder → dwie instancje half_adder. Każda instancja ma własną kopię bramek z wnętrza half_adder, a narzędzie do syntezy utworzy osobny układ dla każdej instancji.

Wiele instancji tego samego modułu

Gdy tworzysz kilka instancji tego samego modułu, każda z nich to niezależny sprzęt. Nie dzielą stanu ani bramek. Wyobraź sobie każdą instancję jako świeżą kopię odbitą z projektu.

adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));

To cztery osobne sumatory działające równolegle. Gdyby adder zawierał rejestr, każda instancja miałaby własną kopię tego rejestru z własnym stanem.

Pętle generate: powielanie sprzętu

Ręczne wypisanie czterech instancji jest w porządku. Wypisanie 64 jest żmudne. Blok generate pozwala, żeby to elaborator pisał za ciebie:

Trzy nowe elementy składni:

  • genvar i deklaruje zmienną pętli, której można używać w generate. To nie jest sygnał w czasie działania: istnieje tylko na etapie elaboracji.
  • generate ... endgenerate obejmuje pętlę. Niektóre narzędzia akceptują pętle generate bez jawnego słowa generate, ale jego zapisanie czyni intencję oczywistą.
  • begin : invert_loop nadaje etykietę zakresowi generate. Etykieta staje się częścią hierarchicznej nazwy każdej wygenerowanej instancji (dut.invert_loop[0].u_inv, dut.invert_loop[1].u_inv itd.).

Syntezator rozwija pętlę i tworzy WIDTH kopii bit_inverter. Każda kopia to niezależny sprzęt.

Nadpisywanie parametrów przy tworzeniu instancji

Jeśli moduł ma parametry, możesz je nadpisać za pomocą #(.PARAM(value)) między nazwą modułu a nazwą instancji:

counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));

Obie instancje używają tego samego kodu counter, ale mają różne szerokości. Składnię omówiliśmy w artykule Parametry, a tutaj po prostu wpasowuje się w tworzenie instancji.

Nazwy hierarchiczne

Gdy masz już hierarchię, każdy sygnał ma ścieżkę hierarchiczną:

test.dut.ha0.sum

Czyta się to tak: w module test, wewnątrz instancji dut, wewnątrz instancji ha0, sygnał o nazwie sum. Takie ścieżki zobaczysz w przeglądarkach przebiegów, komunikatach o błędach i w wywołaniach $display, które z testbencha zaglądają głęboko do podmodułu:

$display("internal carry1 = %b", dut.carry1);

Takie odwołania hierarchiczne służą tylko testbenchom i debugowaniu. Syntezowalny RTL nie sięga do wnętrza innych modułów.

Typowe błędy

Niezgodna nazwa portu. .clk_in(clk) łączy lokalne clk z portem o nazwie clk_in. Jeśli port modułu nazywa się w rzeczywistości clk, parser ci to powie (niektóre narzędzia wyraźniej niż inne).

Niezgodna szerokość portu. Podłączenie 4-bitowego sygnału do 8-bitowego portu po cichu rozszerza go zerami, a w odwrotnym przypadku po cichu go obcina. Większość narzędzi ostrzega. Jeśli nie widzisz ostrzeżeń, poszukaj dokładniej.

Brak # przy parametrze. counter (.WIDTH(8)) c(.clk(clk)) wygląda jak nadpisanie, ale nim nie jest: parser próbuje potraktować (.WIDTH(8)) jako połączenie portu i zgłasza błąd. Poprawnie: counter #(.WIDTH(8)) c(.clk(clk)).

Powtórzona nazwa instancji. Dwie instancje w tym samym zakresie nie mogą mieć tej samej nazwy. Komunikat o błędzie jest zwykle jasny, a problemy sprawia pokusa kopiowania i wklejania.

Co dalej

Potrafisz już łączyć moduły w prawdziwą hierarchię. Następny artykuł, Przypisanie ciągłe, domyka strukturalną stronę Veriloga i dokładniej omawia instrukcję assign, której swobodnie używamy od pierwszego rozdziału.

Najczęściej zadawane pytania

Jak utworzyć instancję modułu w Verilogu?

Napisz nazwę modułu, potem nazwę instancji, a potem listę połączeń portów w nawiasach: my_module instance_name(.port(signal), ...);. Najczęstszy styl to połączenia po nazwie (.port(signal)), które dopasowują się po nazwie portu niezależnie od kolejności. Krótszy styl pozycyjny (my_module instance(signal1, signal2)) zależy od kolejności listy portów i jest ryzykowny w utrzymaniu.

Jaka jest różnica między połączeniami portów po nazwie a po pozycji?

Połączenia po pozycji wypisują sygnały w tej samej kolejności co lista portów modułu: pierwszy sygnał łączy się z pierwszym portem, drugi z drugim i tak dalej. Połączenia po nazwie używają .port_name(signal_name) i dopasowują się po nazwie. Styl z nazwami jest dłuższy, ale odporny na zmianę kolejności portów i sam dokumentuje miejsce użycia. Używaj go zawsze, gdy portów jest więcej niż dwa lub trzy.

Czy ten sam moduł w Verilogu można użyć wiele razy?

Tak, i właśnie o to chodzi. Każda instancja to niezależny sprzęt z własnym stanem. Mając moduł adder, możesz utworzyć 64 jego instancje w jednostce SIMD, każdą z innymi wejściami. Pętla generate to kanoniczna składnia, gdy instancje są podobne i indeksowane.

Czym jest blok generate w Verilogu?

generate ... endgenerate to konstrukcja etapu kompilacji, która powiela sprzęt. Pętla for wewnątrz generate tworzy N instancji tego, co jest w jej ciele. generate działa na etapie elaboracji, przed startem symulacji: to nie jest pętla w czasie działania, tylko generator kodu dla syntezatora.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ