assign opisuje stałą prawdę
Deklaracja wire tworzy sygnał. assign opisuje, co nim steruje. Ta zależność jest ciągła: lewa strona jest równa temu, co jest po prawej stronie, w każdej chwili czasu symulacji:
wire y;
assign y = a & b;
Z tej pary linii wynikają dwie rzeczy:
yistnieje w układzie jako przewód (wire).yjest w każdej chwili bitowym AND zaib. Zmień którekolwiek wejście, aypodąży za nim.
Nie ma zegara ani zdarzenia wyzwalającego aktualizację. Symulator widzi zmianę a albo b, oznacza y jako nieaktualne i ponownie oblicza wyrażenie. W sprzęcie odpowiada to kilku bramkom AND: logika kombinacyjna, bez stanu, natychmiastowa.
Forma skrócona
Deklarację i przypisanie możesz połączyć w jednej linii:
wire y = a & b;
To to samo co dwie linie powyżej. Przydaje się dla lokalnych przewodów, które poza tym zakresem nikogo nie interesują. Wiele przewodników stylu wręcz woli formę skróconą, bo umieszcza deklarację tuż obok równania.
Czym może sterować assign
Celem assign musi być typ sieciowy. W zwykłym Verilogu to wire (albo jeden z rzadszych kuzynów, jak tri, wand, wor). Nie może to być reg. Jeśli przypadkiem zadeklarujesz cel jako reg:
reg y;
assign y = a & b; // ERROR: cannot drive reg with assign
Kompilator ci o tym powie. Zmień cel na wire albo przenieś logikę do bloku always @(*), w którym reg jest dozwolonym celem.
Prawa strona może być czymkolwiek, co daje wartość: literałami, sygnałami, parametrami, wyrażeniami z operatorami, wywołaniami funkcji. Może łączyć wejścia o różnych szerokościach, a obowiązują standardowe reguły poszerzania.
Kiedy assign, a kiedy always
Oba mogą tworzyć logikę kombinacyjną. Wybór dotyczy głównie tego, jak czyta się kod:
assignsprawdza się najlepiej, gdy zależność to jedno wyrażenie. Sumatory, proste multipleksery zbudowane z?:, maski, bity parzystości, wszystko, co zapiszesz w jednej linii.always @(*)sprawdza się najlepiej, gdy potrzebujesz instrukcji proceduralnych. Wielowariantowe instrukcjecase, zagnieżdżoneif/else if, wszystko, co zyskuje na nazwanych pośrednichreg. Omawiamy to w artykule Blok always.
Oto ten sam multiplekser 4 na 1 zapisany na oba sposoby:
Oba moduły syntezują się w zasadzie do tego samego multipleksera. Wersja z assign to jedna linia kodu, wersja z always to sześć. Przy czterech wariantach różnica jest niewielka, ale przy szesnastu blok case czyta się wyraźnie łatwiej.
Typowe wzorce
Zwykła logika kombinacyjna
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
Jedno wyrażenie, jeden przewód. Chleb powszedni assign.
Multiplekser 2 na 1
assign out = sel ? a : b;
Jedno wyrażenie warunkowe, które syntezator zamienia w jeden multiplekser 2 na 1. Najczystszy możliwy zapis "wybierz między a i b".
Pakowanie bitów
assign status = {error, overflow, ready, busy, 4'b0};
Konkatenacja po prawej stronie assign to sposób na spakowanie flag w bajt statusu. Wynik jest obliczany i podawany w sposób ciągły.
Wyjście trójstanowe
assign data_pin = output_enable ? data_out : 1'bz;
Gdy output_enable jest w stanie wysokim, pin jest sterowany. Gdy w niskim, pin przechodzi w stan wysokiej impedancji. To kanoniczny wzorzec na pinach układu, gdzie kilka źródeł może dzielić jeden przewód.
Współbieżność: kilka assign to nie sekwencja
Przypomnienie, które nigdy nie traci aktualności: kilka instrukcji assign w tym samym module działa równolegle. Nie tworzą sekwencji:
assign y = a & b; // exists at all times
assign z = a | b; // also exists at all times, independently
Kolejność w pliku nie ma znaczenia. Oba równania są prawdziwe jednocześnie. Syntezator może umieścić bramkę AND przed bramką OR albo po niej, bez znaczenia, bo obie bramki działają ciągle.
Jeśli chcesz zachowania wyglądającego na sekwencyjne, sięgasz po blok always (i zapewne zegar). To temat na inny rozdział.
Wiele źródeł: wzorzec magistrali
Jeden wire może być celem więcej niż jednego assign, ale prawie nigdy tego nie chcesz, z wyjątkiem magistral trójstanowych. Dwa źródła walczące o jeden przewód dają niezdefiniowane zachowanie:
assign y = a;
assign y = b; // BAD - two drivers, simulator picks one or x's it out
Prawidłowy wzorzec: każde źródło zwalnia linię do z, gdy jest nieaktywne, a w danej chwili aktywne jest najwyżej jedno.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
To działa, bo w każdej chwili najwyżej jedno z dwóch wyrażeń warunkowych daje wartość inną niż z. Faktyczna wartość przewodu pochodzi z tego źródła, które nie zwalnia linii.
W logice wewnętrznej, czyli wszędzie poza pinami układu i współdzielonymi magistralami w układzie, każdy przewód ma jedno źródło. Błędy z wieloma źródłami są paskudne do debugowania.
Czego assign nie potrafi
Do niektórych rzeczy assign jest złym narzędziem:
- Przechowywanie.
assignopisuje zależności kombinacyjne i nie może wprowadzić przerzutnika. Jeśli wartość ma być pamiętana między cyklami zegara, potrzebujesz blokualways @(posedge clk). - Wieloetapowa logika proceduralna. Wewnątrz
assignnie napiszeszif/elseanicase. Najbliżej jest łańcuch?:, który powyżej trzech gałęzi robi się brzydki. - Sterowanie rejestrami z wnętrza bloku proceduralnego. Cele typu
regwymagają przypisania proceduralnego, a nieassign.
Znajomość tych ograniczeń pozwala zdecydować, kiedy przejść na always.
Co dalej
Znasz już całą strukturalną stronę Veriloga: deklarowanie modułów, tworzenie ich instancji i łączenie logiki kombinacyjnej za pomocą assign. Następny rozdział przechodzi do bloków proceduralnych, czyli konstrukcji initial i always, w których zaczynają mieć znaczenie czas i kolejność.
Najczęściej zadawane pytania
Czym jest przypisanie ciągłe w Verilogu?
assign target = expression; deklaruje stałą, ciągłą zależność: target jest zawsze równy expression. Gdy zmieni się dowolny sygnał w wyrażeniu, symulator ponownie oblicza prawą stronę i aktualizuje target. Nie ma zegara ani zdarzenia: zależność jest prawdziwa w każdej chwili.
Co może być celem assign w Verilogu?
assign może sterować wire, ale nigdy reg. Cel musi być typu sieciowego (net). Jeśli zamiast tego chcesz przypisywać coś w bloku always, zadeklaruj to jako reg. Kompilator odrzuci assign x = ..., gdy x jest reg, i odrzuci x = ... wewnątrz always, gdy x jest wire.
Kiedy używać assign, a kiedy bloku always?
Używaj assign do prostej logiki kombinacyjnej: jedno wyrażenie na wejściu, jeden sygnał na wyjściu, bez potrzeby if/else. Używaj always @(*), gdy logika wymaga instrukcji proceduralnych (case, łańcucha if/else if, pętli for). Oba tworzą sprzęt kombinacyjny, a wybór dotyczy czytelności.
Czy w Verilogu można mieć kilka assign dla tego samego wire?
Tylko wtedy, gdy modelujesz magistralę trójstanową, na której każde źródło zwalnia linię do z, gdy jest nieaktywne. Dwa assign, które jednocześnie próbują ustawić określone wartości, powodują konflikt: symulator może wybrać jedną z nich albo ustawić sygnał na X, zależnie od narzędzia. W zwykłej logice kombinacyjnej każdy wire ma jedno źródło.