Menu

Instrukcja case w Verilogu: poprawne dekodowanie wielu wariantów

Jak działa case przy czystym dekodowaniu wielu wariantów, dlaczego nigdy nie warto pomijać default i czym różnią się case, casex i casez.

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

Rozgałęzienie na wiele wariantów

case to w Verilogu płaska konstrukcja wyboru. Podajesz wyrażenie, a ona wybiera pasującą gałąź:

case (expression)
    pattern_1: statement_1;
    pattern_2: statement_2;
    pattern_3: begin
        statement_3a;
        statement_3b;
    end
    default: default_statement;
endcase

Strukturalnie przypomina switch z C, ale:

  • Nie ma break: każdą gałąź domyślnie kończy następna.
  • Wzorcami mogą być wektory, nie tylko liczby całkowite.
  • Syntezator zamienia całość w płaski multiplekser (albo dekoder one-hot, gdy wzorce na to pozwalają).

Przykład: multiplekser 4 na 1

Ciało case ma cztery jawne wzorce oraz default. Syntezator widzi 2-bitowe wejście odwzorowane na jedną z czterech wartości i tworzy multiplekser 4 na 1. Czysto, płasko i szybko.

Dlaczego potrzebujesz default

W kombinacyjnym case pominięcie default to ta sama pułapka, co if bez else: każda niedopasowana wartość wejścia zostawia out bez przypisania, a syntezator tworzy zatrzask.

Dla 2-bitowego sel powyższe wzorce obejmują wszystkie cztery możliwe wartości, więc teoretycznie default jest zbędny. W praktyce:

  1. Syntezator nie zawsze potrafi wykazać, że warianty są wyczerpujące.
  2. W symulacji selektor może mieć wartość x albo z, która nie pasuje do żadnego jawnego wariantu.
  3. Dodanie nowego wariantu później może zostawić zachowanie domyślne nieokreślone.

Zawsze pisz default. W maszynach stanów i logice multipleksera, gdy wiesz, że wariant domyślny jest nieosiągalny, przypisanie 'x:

default: out = 8'bx;

…mówi syntezatorowi "to wartość dowolna, optymalizuj swobodnie", a w symulacji pokazuje jaskrawe x, jeśli nieosiągalny wariant jednak zostanie osiągnięty. Najlepsze z obu światów.

Maszyna stanów w case

Klasyczne zastosowanie case to logika przejść między stanami w automacie skończonym:

Blok case (state) to logika przejść. Każda gałąź decyduje, jaki będzie następny stan i jak długo w nim pozostać. default jest tu nieosiągalny (RED/GREEN/YELLOW obejmują wszystkie potrzebne wartości w przestrzeni 2 bitów), ale stanowi siatkę bezpieczeństwa: jeśli state jakimś cudem przyjmie wartość 2'd3, automat czysto wraca do RED zamiast tworzyć zatrzask.

Artykuł Maszyny stanów omawia ten wzorzec dokładniej.

Kilka wzorców w jednej gałęzi

Możesz wypisać kilka wzorców korzystających z jednej instrukcji, oddzielając je przecinkami:

case (opcode)
    4'h0, 4'h1, 4'h2: result = a + b;
    4'h3, 4'h4:       result = a - b;
    4'h8:             result = a & b;
    default:          result = 8'd0;
endcase

Tutaj dwa opcode oznaczają "odejmij", a trzy "dodaj". Syntezator łączy wzorce przez OR w komparatorze.

casez i casex: dopasowanie z dowolnymi bitami

Czasem chcesz dopasować wzorzec z częścią bitów nieokreśloną, na przykład "każdy opcode zaczynający się od 010":

casez (opcode)
    8'b010?_????: instruction = ALU_OP;
    8'b110?_????: instruction = LOAD_OP;
    8'b1110_????: instruction = JUMP_OP;
    default:      instruction = UNKNOWN;
endcasez

casez traktuje ? (oraz z) we wzorcu jako wartości dowolne. Każdy ? pasuje do 0 i do 1. Przydaje się przy dekodowaniu formatów instrukcji, w których niektóre pozycje bitów są nieużywane w pewnych klasach opcode.

casex rozszerza to i jako dowolne traktuje także x. casex jest niebezpieczne, bo niezainicjowane sygnały (które w symulacji mają wartość x) pasują do każdego wariantu, co daje zaskakujące zachowanie. Większość współczesnych przewodników stylu zaleca casez i zabrania casex.

W SystemVerilogu masz jeszcze case inside, najczystszą wersję ze wszystkich, bo przyjmuje zakresy i listy. Zwykły Verilog kończy się jednak na casez.

case a łańcuch if/else if

Obie konstrukcje wyrażają decyzje wielowariantowe, ale syntezują się do innego sprzętu:

  • case to płaski wybór. Syntezator może utworzyć dekoder one-hot, zrównoważone drzewo multiplekserów albo inne płaskie struktury. Czas obliczenia jest stały.
  • if/else if to łańcuch priorytetów. Syntezator tworzy kaskadę, w której każdy poziom dodaje opóźnienie. Wolniej, logarytmicznie.

Funkcjonalnie się pokrywają. Stylistycznie: używaj case, gdy warunki dotyczą wartości jednego wyrażenia. Używaj if/else if, gdy istnieje prawdziwy priorytet albo gdy warunki dotyczą różnych sygnałów.

// Better as case:
if      (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else                  out = d;

// Better as if/else if:
if      (urgent_event)  next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready)    next_state = PROCESS;
else                    next_state = state;

Pierwsze trzy warunki pytają o to samo: "jaka jest wartość sel?". case czyta się tu naturalniej i syntezuje bardziej płasko. Drugie trzy to niezależne zdarzenia z oczywistym priorytetem, więc lepiej pasuje if/else if.

Co dalej

Ostatni artykuł w tym rozdziale, Pętle for, omawia for w Verilogu i zaskakujące zjawisko, które zachodzi, gdy użyjesz tej pętli w kodzie syntezowalnym. Potem przechodzimy na dobre do logiki sekwencyjnej i automatów skończonych.

Najczęściej zadawane pytania

Czym jest instrukcja case w Verilogu?

case (expr) ... endcase to w Verilogu konstrukcja rozgałęzienia na wiele wariantów. Oblicza wyrażenie raz i przechodzi do gałęzi, która do niego pasuje. To idiomatyczny wybór dla maszyn stanów, dekoderów opcode, selektorów multipleksera i wszystkiego, co wybiera spośród kilku wzajemnie wykluczających się opcji.

Jaka jest różnica między case, casex i casez w Verilogu?

case dopasowuje dokładnie co do bitu, łącznie z wartościami x i z. casez traktuje z (oraz ?) we wzorcach jako dowolne wartości. casex traktuje jako dowolne zarówno x, jak i z. Dopasowanie z dowolnymi bitami przydaje się dla wzorców opcode, w których niektóre pozycje bitów nie mają znaczenia, ale casex jest niebezpieczne w symulacji, bo niezainicjowane sygnały (x) mogą przypadkiem pasować do każdego wariantu.

Dlaczego w instrukcji case w Verilogu potrzebne jest default?

Bez default narzędzie do syntezy widzi możliwość, że żaden wariant nie pasuje, uznaje, że sygnał wyjściowy musi zachować poprzednią wartość, i tworzy niechciany zatrzask. Gałąź default obsługuje każdą niedopasowaną wartość, zwykle ustawiając wyjście na bezpieczną wartość albo oznaczając wariant jako nieosiągalny przypisaniem x. Zawsze ją dodawaj.

Kiedy używać case, a kiedy if-else w Verilogu?

Używaj case, gdy warunki wzajemnie się wykluczają i zależą od wartości jednego wyrażenia: maszyny stanów, dekodery opcode, selektory multipleksera. Używaj if/else, gdy istnieje prawdziwa kolejność priorytetów albo warunki dotyczą różnych sygnałów. case syntezuje się do bardziej płaskiego i szybszego sprzętu niż długi łańcuch else if.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ