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:
- Syntezator nie zawsze potrafi wykazać, że warianty są wyczerpujące.
- W symulacji selektor może mieć wartość
xalboz, która nie pasuje do żadnego jawnego wariantu. - 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:
caseto 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 ifto ł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.