Menu

If-else w Verilogu: logika warunkowa w blokach proceduralnych

Jak działa if/else wewnątrz bloku always, pułapka zatrzasku, w którą wpadają początkujący, i koder priorytetowy, który powstaje z łańcucha else if.

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

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:

  1. if/else istnieje tylko wewnątrz bloku proceduralnego. Nie da się napisać samodzielnego if na poziomie modułu.
  2. 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 else dla 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ