Główne narzędzie behawioralnego Veriloga
assign opisuje logikę kombinacyjną w postaci jednego równania. Gdy tylko potrzebujesz if/else, case albo pamięci, sięgasz po always. Blok always to fragment kodu proceduralnego, który uruchamia się ponownie za każdym razem, gdy zmienią się określone sygnały. Sygnały wyzwalające ponowne uruchomienie tworzą listę czułości.
Najczęściej spotkasz dwie postacie always:
always @(*): uruchamia się ponownie, gdy zmieni się dowolny sygnał odczytywany w bloku. Buduje logikę kombinacyjną.always @(posedge clk): uruchamia się ponownie tylko na zboczu narastającymclk. Buduje taktowaną logikę sekwencyjną (przerzutniki, czyli flip-flopy).
Istnieją też inne formy (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)), ale te dwie powyżej obejmują prawie każdy syntezowalny blok, jaki napiszesz.
Kombinacyjne always @(*)
Trzy rzeczy, na które warto zwrócić uwagę:
outjest typureg. Wszystko, co jest przypisywane wewnątrzalways, musi byćreg. To słowo kluczowe nie oznacza "to jest przerzutnik". Tutaj znaczy tylko "zapisuję to z bloku proceduralnego".always @(*). Gwiazdka*mówi "obudź się, gdy zmieni się cokolwiek, co odczytuję". Symulator sam ustala listę czułości. Możesz wypisać listę ręcznie, na przykładalways @(sel), ale@(*)jest bezpieczniejsze, bo pominięty sygnał to klasyczne źródło błędów.- Brak zegara. Ten blok opisuje logikę kombinacyjną. Syntezator tworzy układ, który oblicza
outbezpośrednio zsel, bez przerzutników i bez wejścia zegarowego.
Gałąź default jest opcjonalna w składni, ale w praktyce nie powinna być pomijana. Bez niej każda nieobsłużona wartość wejścia sprawia, że out zachowuje poprzednią wartość, a to syntezuje się do niezamierzonego zatrzasku (latch). Zawsze dodawaj default.
Sekwencyjne always @(posedge clk)
Najważniejsze różnice względem wersji kombinacyjnej:
always @(posedge clk). Blok uruchamia się ponownie tylko na zboczu narastającymclk. Między zboczami nic się nie dzieje.- Przypisanie nieblokujące
<=. W bloku taktowanym to właściwy operator. Oznacza "zaplanuj, żecountprzyjmie nową wartość na końcu kroku czasowego", a dokładnie tak zachowuje się przerzutnik. Powody i alternatywę znajdziesz w artykule Przypisania blokujące i nieblokujące. defaultnie jest potrzebny.ifobsługuje obie gałęzie (reset i brak resetu), więc nie ma ryzyka zatrzasku.
Syntezator rozpoznaje ten kształt (taktowana lista czułości i przypisanie nieblokujące) i tworzy 4-bitowy rejestr (cztery przerzutniki) oraz logikę kombinacyjną obliczającą count + 1 i multiplekser wybierający między resetem a inkrementacją.
Co widzi synteza
Ten sam kod modułu może opisywać dwa zupełnie różne układy, zależnie od kształtu bloku always:
| Blok | Sprzęt |
|---|---|
always @(*) y = expr; | Czysta logika kombinacyjna. Bez pamięci. |
always @(posedge clk) y <= expr; | Przerzutnik. Zapamiętuje expr raz na cykl zegara. |
always @(*) if (en) y = expr; | Zatrzask, zwykle błąd. W przypadku "else" zostaje stara wartość. |
always @(posedge clk) if (en) y <= expr; | Przerzutnik z wejściem enable. Zapamiętuje tylko, gdy en jest w stanie wysokim. |
Trzeci przypadek to pułapka zatrzasku. Zatrzask to przezroczysta komórka pamięci, która trzyma swoje wyjście, gdy wejście sterujące nie jest aktywne. Bywa przydatny w konkretnych projektach, ale gdy powstaje przypadkiem, prawie zawsze jest błędem. Większość narzędzi do syntezy głośno ostrzega, gdy wykryje zatrzask, o który nie prosisz. Traktuj to ostrzeżenie jak błąd.
Odmiany listy czułości
Spotkasz też kilka rzadszych list czułości:
always @(a or b or c): jawna lista. Verilog-2001 dodał separator,:always @(a, b, c). Działa każdy z zapisów.always @(posedge clk or negedge reset_n): reset asynchroniczny. Blok uruchamia się na zboczu narastającym zegara albo na zboczu opadającym resetu. Stosuje się go, gdy reset musi zadziałać natychmiast, bez czekania na następny takt zegara.always @(negedge clk): taktowanie zboczem opadającym. Rzadkie. Niektóre projekty używają go dla przerzutników wyzwalanych zboczem opadającym zamiast narastającym.
W nowych projektach wybieraj always @(*) dla logiki kombinacyjnej i always @(posedge clk) dla sekwencyjnej. Po reset asynchroniczny sięgaj tylko wtedy, gdy projekt naprawdę go potrzebuje.
Dwa bloki to dwa układy
Kilka bloków always w tym samym module działa niezależnie. Każdy staje się osobnym fragmentem sprzętu:
Blok taktowany tworzy rejestr z przerzutników. Blok kombinacyjny tworzy bramkę XOR. Działają obok siebie i nic o sobie nie wiedzą. Oba wyjścia zmieniają się według zupełnie różnych harmonogramów.
Czego bloki always nie potrafią
Kilka rzeczy wygląda kusząco, ale jest niedozwolonych:
- Przypisanie do
wire: cel musi być typureg. Kompilator to wymusza. - Przypisanie do tego samego
regz dwóch różnych blokówalways: w symulacji daje niezdefiniowane zachowanie i nie da się tego zsyntezować. Jeden sygnał, jedno źródło sterowania. - Odczyt i zapis tego samego sygnału w tym samym bloku kombinacyjnym w sposób tworzący pętlę sprzężenia zwrotnego:
always @(*) x = x + 1;to pętla o zerowym opóźnieniu, której symulator nie potrafi rozwiązać.
Dwa pierwsze problemy wyłapie kompilator. Trzeci czasem ujawnia się dopiero w symulacji jako zawieszenie.
Co dalej
Następny artykuł, Blok initial, omawia rodzeństwo always: blok, który wykonuje się dokładnie raz na początku symulacji. To podstawowe narzędzie testbenchy. Potem przyjdą reguły przypisań blokujących i nieblokujących, które decydują o tym, czy blok taktowany robi to, co zamierzasz.
Najczęściej zadawane pytania
Czym jest blok always w Verilogu?
always wprowadza blok proceduralny, który uruchamia się ponownie za każdym razem, gdy zmienią się sygnały z jego listy czułości. Są dwa warianty: always @(*) buduje logikę kombinacyjną (uruchamia się ponownie przy zmianie dowolnego wejścia), a always @(posedge clk) buduje logikę sekwencyjną (uruchamia się przy każdym zboczu narastającym clk). Ciało bloku always może zawierać if, case, for i przypisania proceduralne.
Jaka jest różnica między always @(*) a always @(posedge clk)?
always @(*) reaguje na dowolny sygnał odczytywany w bloku i tworzy logikę kombinacyjną bez pamięci. always @(posedge clk) reaguje tylko na zbocze narastające clk i tworzy przerzutniki, które zapamiętują stan raz na cykl zegara. Pierwszy nie ma ani zegara, ani rejestru, drugi ma jedno i drugie.
Czym jest lista czułości w Verilogu?
To lista sygnałów po @, która określa, kiedy blok always uruchamia się ponownie. @(*) to skrót od 'każdy sygnał odczytywany w bloku'. @(posedge clk) działa tylko na zboczu narastającym clk. @(posedge clk or negedge reset_n) działa przy każdym z tych dwóch zdarzeń i służy do resetów asynchronicznych. Błędna lista czułości to jedno z najczęstszych źródeł rozbieżności między symulacją a syntezą.
Czy można przypisać wartość do wire wewnątrz bloku always?
Nie. Bloki always mogą przypisywać tylko do reg (albo logic w SystemVerilogu). Kompilator to wymusza. Jeśli wire ma być wyjściem logiki proceduralnej, zadeklaruj pośredni reg, steruj nim wewnątrz always i poza blokiem podłącz wire do tego reg przez assign. Możesz też po prostu zmienić wire na reg.