Co w Verilogu oznacza "operator"
Operatory przyjmują wyrażenia niosące sygnały i tworzą nowe wyrażenia niosące sygnały. a + b to sprzęt: syntezator zamienia go w sumator. a == b to sprzęt: buduje komparator. Nic z tego nie jest "kodem, który się wykonuje". To wszystko "układy opisane przez operator".
Pełne menu operatorów jest na tyle małe, że da się je zapamiętać. Ten artykuł omawia operatory arytmetyczne, porównania, logiczne i warunkowy. Artykuł Operatory bitowe i redukcyjne omawia operatory działające na bitach, które nie mają bezpośrednich odpowiedników w programowaniu.
Arytmetyka: + - * / %
Trzy rzeczy, które warto wiedzieć:
- Dzielenie jest całkowite.
7/2daje3, a nie3.5. Verilog nie ma typu zmiennoprzecinkowego (no, jestreal, ale tylko w symulacji). - Dzielenie i modulo są w sprzęcie kosztowne.
/4jest w porządku (syntezator zamienia je w przesunięcie w prawo), ale/ndlanzmiennego w czasie działania buduje wielocyklowy dzielnik, którego prawie nigdy nie chcesz. - Przepełnienie się zawija. Dodanie dwóch N-bitowych wartości bez znaku, których suma przekracza N bitów, obcina wynik do N bitów. Jeśli chcesz zachować przeniesienie, zadeklaruj wynik o jeden bit szerszy:
wire [7:0] a, b;
wire [8:0] sum = a + b; // 9-bit sum captures carry out
Porównania: == != < > <= >=
Wszystkie sześć operatorów porównania zwraca wynik jednobitowy: 1, gdy warunek jest prawdziwy, 0, gdy fałszywy, i x, gdy którykolwiek argument ma bit x albo z. Do obsługi przypadku x/z służą === i !== (operatory równości dokładnej), omówione w artykule Wartości X i Z.
<= jest tu operatorem porównania (mniejsze lub równe). Te same znaki oznaczają też przypisanie nieblokujące w bloku proceduralnym (q <= d). Rozróżnia je kontekst: wewnątrz wyrażenia to porównanie, a po lewej stronie przypisywanej wartości to przypisanie. To nakładanie się myli, ale dla parsera jest jednoznaczne.
Operatory logiczne: && || !
Operatory logiczne traktują argumenty jako wartości logiczne (wszystko niezerowe to prawda, zero to fałsz) i dają wynik jednobitowy:
Kluczowe rozróżnienie, które trzeba przyswoić: logiczne && to nie bitowe &.
4'b1100 && 4'b0011 // 1 (both are non-zero, logical AND is true)
4'b1100 & 4'b0011 // 4'b0000 (no bit positions are 1 in both)
To samo dotyczy || i |. Operatorów logicznych używaj w warunkach if i predykatach ?:, a bitowych wtedy, gdy każda pozycja bitu ma być łączona przez AND/OR niezależnie.
&& i || mają skrócone wartościowanie w sensie znanym z C: prawa strona nie jest obliczana, jeśli wynik jest już przesądzony przez lewą. Ma to znaczenie w testbenchach, które sprawdzają wskaźniki albo wywołania funkcji, ale rzadko w syntezowalnym RTL, gdzie i tak wszystko dzieje się równolegle.
Operator warunkowy: ?:
Operator trójargumentowy to twój najlepszy przyjaciel przy budowaniu multiplekserów i wyrażeń warunkowych:
Wzorzec cond ? a : b to w sprzęcie multiplekser 2 na 1 z jednobitowym wyborem. Łańcuchy takich wyrażeń sprawdzają się przy wyborze z 3 i 4 wariantów, ale powyżej tego instrukcja case (omówiona w artykule Instrukcja case) jest dużo czytelniejsza.
Operator ?: to też standardowy sposób na nadanie rejestrowi wartości domyślnej, którą tylko czasem nadpisujesz:
next_value = update ? new_data : next_value;
Reguły szerokości: pułapka dla początkujących
Reguły szerokości dla operatorów arytmetycznych w Verilogu są znane z tego, że zaskakują. W skrócie:
- Szerokość wyniku to maksimum szerokości wejść.
- Jeśli wynik jest przypisywany do czegoś szerszego, jest rozszerzany zerami (albo bitem znaku, jeśli jest ze znakiem).
- Szerokość obliczeń pośrednich to także szerokość wyniku, co oznacza, że przepełnienie następuje już w obliczeniu pośrednim.
Pierwsze mnożenie się przepełnia, bo oba argumenty mają 8 bitów, więc wynik też ma 8 bitów, a 40000 się w nich nie mieści. Drugie działa, bo przed mnożeniem jawnie poszerzamy a.
Techniki naprawy:
- Użyj operatora konkatenacji
{8'b0, a}, żeby rozszerzyć zerami. - Użyj
$unsigned(a)albo$signed(a), żeby wpłynąć na interpretację. - W razie wątpliwości rzutuj wyrażenia pośrednie na większe szerokości.
Priorytet operatorów
Nie zapamiętasz całej tabeli, ale będziesz polegać na kilku regułach:
*,/,%wiążą silniej niż+,-.- Porównania wiążą silniej niż operatory logiczne (
&&,||). - Operator warunkowy
?:ma bardzo niski priorytet, więc w większych wyrażeniach jego gałęzie zwykle wymagają nawiasów. - W razie wątpliwości dodaj nawiasy. Symulator nie liczy opłat za znaki.
out = (mode == 2'd0) ? a + b : a - b; // clearer with parens
Co dalej
Znasz już wszystkie operatory, które wyglądają jak ich odpowiedniki z programowania. Następny artykuł, Operatory bitowe i redukcyjne, omawia operatory bez bezpośrednich odpowiedników w programowaniu: bitowe AND/OR/XOR na każdym bicie oraz formy redukcyjne, które zwijają cały wektor do jednego bitu.
Najczęściej zadawane pytania
Jakie operatory obsługuje Verilog?
Verilog ma cztery główne rodziny operatorów: arytmetyczne (+, -, *, /, %), porównania (==, !=, <, >, <=, >=), logiczne (&&, ||, !) oraz bitowe/redukcyjne (&, |, ^, ~, ~&, ~|, ~^). Do tego operatory przesunięcia (<<, >>, <<<, >>>), konkatenacja {}, replikacja {N{...}} i operator warunkowy ?:.
Jaka jest różnica między && a & w Verilogu?
&& to logiczne AND: traktuje każdy argument jako wartość logiczną (zero lub niezero) i zwraca wynik 1-bitowy. & to bitowe AND: łączy bity w pary pozycja po pozycji. 4'b1100 && 4'b0011 daje 1 (oba są niezerowe), a 4'b1100 & 4'b0011 daje 4'b0000. Używaj && w warunkach, a & do operacji na bitach.
Jak działa dzielenie w Verilogu?
To dzielenie całkowite: część ułamkowa jest odrzucana. 7 / 2 daje 3, a nie 3.5. Operator % daje resztę: 7 % 2 to 1. Dzielenie przez zero daje w symulacji x (wartość nieznaną). Oba operatory są formalnie syntezowalne, ale w FPGA i układach ASIC tworzą bardzo duży i wolny sprzęt, więc unikaj ich w ścieżkach syntezowalnych, chyba że dzielnik jest potęgą 2.
Czym jest operator warunkowy w Verilogu?
?: to operator trójargumentowy, czyli warunkowy, skopiowany z C. cond ? a : b daje a, gdy cond jest prawdziwe (niezerowe), a w przeciwnym razie b. To najczęściej używana konstrukcja do logiki typu multiplekser: out = sel ? in1 : in0 buduje multiplekser 2 na 1. Łańcuchy ?: budują szersze multipleksery, choć powyżej dwóch wejść zwykle czytelniejszy jest case.