Znajoma składnia, inny model myślenia
if/else wygląda dokładnie jak w C:
if (condition) begin
// ... statements ...
end else begin
// ... statements ...
end
Reguły są jednak inne, bo Verilog to nie oprogramowanie. Warto pamiętać o dwóch rzeczach:
if/elseistnieje tylko wewnątrz bloku proceduralnego. Nie da się napisać samodzielnegoifna poziomie modułu.- To, do czego syntezuje się
if/else, zależy od typu bloku. W bloku kombinacyjnym staje się multiplekserem albo koderem priorytetowym. W bloku taktowanym staje się przerzutnikiem z logiką warunkowej aktualizacji.
W bloku kombinacyjnym
Kombinacyjny blok always @(*) uruchamia się ponownie, gdy zmieni się a, b albo c. Łańcuch if/else wybiera jedną gałąź, przypisuje max i blok się kończy. Ponieważ max jest zawsze przypisywane (każda ścieżka ma przypisanie), syntezator tworzy czystą logikę kombinacyjną, bez zatrzasku.
Zwróć uwagę, że max jest zadeklarowane jako reg, choć w sprzęcie nie ma przerzutnika. Obowiązuje ta sama reguła co zawsze: wszystko, co jest przypisywane wewnątrz always, musi być reg.
Pułapka zatrzasku
To najczęstszy błąd w kodzie kombinacyjnym początkujących:
// WRONG - infers a latch
always @(*) begin
if (enable)
out = data;
// no else! when `enable` is low, what does `out` do?
end
Syntezator odczytuje "gdy enable jest w stanie niskim, out nie jest przypisywane" i uznaje, że out musi pamiętać poprzednią wartość. Pamiętanie wartości wymaga komórki pamięci, więc narzędzie wstawia zatrzask. Zatrzaski w projektach synchronicznych powodują problemy czasowe, trudno je resetować i prawie nigdy nie są tym, co zamierzasz.
Są dwa sposoby, żeby to naprawić:
Oba dają ten sam sprzęt kombinacyjny: multiplekser 2 na 1. Wzorzec "wartość domyślna na początku" lepiej się skaluje, gdy masz wiele warunkowych przypisań do tego samego sygnału.
W bloku taktowanym
Zwróć uwagę, co się różni od przypadku kombinacyjnego:
- Blok to
always @(posedge clk), czyli terytorium przerzutników. - Przypisanie używa
<=(nieblokującego). - Nie ma
elsedla przypadku "ani reset, ani enable". To w porządku. W bloku taktowanym, gdy żadna gałąź się nie wykona, przerzutnik po prostu trzyma poprzednią wartość, a dokładnie to fizycznie robi przerzutnik. Zatrzask nie powstaje, bo sygnał już jest rejestrem.
To jedyne miejsce, w którym pominięcie else jest bezpieczne. Poza blokami taktowanymi zawsze obsługuj każdą ścieżkę.
Łańcuch else if: koder priorytetowy
Łańcuch instrukcji else if ma wbudowany priorytet: wcześniejsze warunki mają pierwszeństwo przed późniejszymi:
requests[0] ma najwyższy priorytet: jeśli jest ustawiony, wynik to 0 niezależnie od bitów o wyższych numerach. Syntezator zamienia łańcuch w kaskadę multiplekserów: najpierw sprawdza bit 0, potem bit 1, potem bit 2, potem bit 3. Każdy poziom dodaje niewielkie opóźnienie.
Jeśli warunki wzajemnie się wykluczają (na przykład przy dekodowaniu wejścia one-hot), instrukcja case (następny artykuł) daje bardziej płaski i szybszy sprzęt niż łańcuch else if. Używaj case, gdy nie ma rzeczywistej potrzeby priorytetu.
if bez else w kodzie taktowanym
Blok taktowany nie potrzebuje else, bo "zachowaj poprzednią wartość" to zachowanie domyślne. Tak buduje się wejścia enable:
always @(posedge clk) begin
if (load) target <= incoming;
// no else: when load is low, target keeps its value
end
To rejestr z wejściem ładowania. Większość rejestrów potoków, liczników i rejestrów konfiguracyjnych używa tego wzorca.
begin/end i pojedyncze instrukcje
Tak jak w C, przy pojedynczej instrukcji możesz pominąć begin/end:
if (a) out = 1;
else out = 0;
Przy więcej niż jednej instrukcji użyj bloku:
if (a) begin
out = 1;
flag = 1;
end else begin
out = 0;
flag = 0;
end
Oba wzorce można swobodnie mieszać. Przewodniki stylu zwykle zalecają, żeby zawsze używać begin/end, bo wtedy dodanie drugiej instrukcji jest bezbolesne.
Co dalej
Następny artykuł, Instrukcja case, omawia case, czyli właściwe narzędzie do dekodowania wielu wariantów (maszyny stanów, wybór na podstawie opcode, tablice ROM). Potem przyjdą pętle for, które subtelnie różnią się od swoich odpowiedników z programowania, bo są rozwijane na etapie elaboracji.
Najczęściej zadawane pytania
Jak działa instrukcja if w Verilogu?
if (cond) statement; wykonuje statement, gdy cond jest niezerowe. Kilka instrukcji możesz objąć begin ... end. Dodaj else statement; dla gałęzi alternatywnej albo utwórz łańcuch z else if (other_cond) .... if/else istnieje tylko wewnątrz bloków proceduralnych, initial lub always, a nie na najwyższym poziomie modułu.
Czym jest niechciany zatrzask (inferred latch) w Verilogu?
To zatrzask, który narzędzie do syntezy utworzyło bez twojej prośby, bo kombinacyjny blok always nie przypisywał sygnału w każdej ścieżce. Narzędzie widzi 'jeśli a, to out = 1' bez else, uznaje, że w nieprzypisanym przypadku trzeba zapamiętać poprzednią wartość, i tworzy zatrzask. Zatrzaski prawie zawsze są błędem. Rozwiązanie to nadanie każdemu sygnałowi wartości domyślnej na początku bloku albo jawne else.
Jak uniknąć niechcianych zatrzasków w Verilogu?
W kombinacyjnym bloku always @(*) upewnij się, że każdy wyjściowy reg jest przypisywany w każdej ścieżce kodu. Najczystszy wzorzec to ustawienie wartości domyślnych na początku bloku, a potem warunkowe ich nadpisywanie. Kompilator zwykle ostrzega, gdy tworzy zatrzask. Traktuj to ostrzeżenie jak błąd.
Co powstaje z łańcucha if-else po syntezie w Verilogu?
Koder priorytetowy. Pierwszy if ma najwyższy priorytet, kolejny else if jest sprawdzany tylko wtedy, gdy pierwszy jest fałszywy, i tak dalej. W sprzęcie staje się to łańcuchem multiplekserów z wbudowaną kolejnością priorytetów. Jeśli warunki wzajemnie się wykluczają, instrukcja case z tą samą logiką często syntezuje się do bardziej płaskiego sprzętu i czyta się czytelniej.