Menu

Operatory bitowe i redukcyjne w Verilogu

Operatory działające na bitach w Verilogu: bitowe AND/OR/XOR, ich odwrócone formy i operatory redukcyjne, które zwijają cały wektor do jednego bitu.

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

Dwie role tego samego symbolu

&, |, ^ i ich odwrócone odpowiedniki występują w dwóch różnych postaciach:

  • Forma dwuargumentowa (dwa argumenty): a & b, czyli operacja bitowa między wektorami o tej samej szerokości.
  • Forma jednoargumentowa (jeden argument): &a, czyli redukcja wszystkich bitów a do jednego bitu.

Kompilator odróżnia je po liczbie argumentów. Przyjęło się mówić "operatory bitowe" o formie dwuargumentowej i "operatory redukcyjne" o formie jednoargumentowej.

Operacje bitowe: bit po bicie

Pełny zestaw:

OperatorNazwaBit wyniku na pozycji N
a & bANDa[N] AND b[N]
a | bORa[N] OR b[N]
a ^ bXORa[N] XOR b[N]
a ~& bNANDNOT (a[N] AND b[N])
a ~| bNORNOT (a[N] OR b[N])
a ~^ bXNORNOT (a[N] XOR b[N])
~aNOTNOT a[N]

Zwróć uwagę, że ~&, ~|, ~^ zapisuje się z tyldą na początku i operatorem po niej. To jeden token, bez spacji w środku.

Gdy argumenty mają różne szerokości, węższy jest rozszerzany zerami z lewej strony do szerokości drugiego. Jeśli chcesz rozszerzenia ze znakiem, użyj jawnie argumentów ze znakiem przez $signed().

Redukcja: z wielu bitów w jeden

Jednoargumentowe formy tych samych operatorów zwijają wektor do jednego bitu:

Co oznacza każdy z nich:

  • &data zwraca 1, jeśli wszystkie bity data są równe 1, w przeciwnym razie 0. Przydaje się do sprawdzania, czy wartość składa się z samych jedynek.
  • |data zwraca 1, jeśli którykolwiek bit data jest równy 1, w przeciwnym razie 0. Przydaje się do sprawdzania, czy wartość jest niezerowa.
  • ^data zwraca parzystość, czyli XOR wszystkich bitów. 1 przy nieparzystej liczbie jedynek, 0 przy parzystej.
  • ~&data, ~|data, ~^data to odwrotności powyższych.

Spotkasz je wszędzie w prawdziwym kodzie:

wire empty       = ~|fifo_count;       // puste wtedy i tylko wtedy, gdy count to zero
wire all_ones    = &mask;              // wszystkie bity ustawione
wire parity_bit  = ^data;              // parzystość bajtu
wire any_request = |request_vector;    // czy jest jakieś żądanie?

Formy redukcyjne są zwięzłe i syntezują się do oczywistych drzew bramek: & do wielowejściowej bramki AND, | do wielowejściowej bramki OR, a ^ do drzewa XOR (które w sprzęcie jest zarazem generatorem parzystości).

Typowe wzorce

Ustawianie i zerowanie bitów

wire [7:0] data;
wire [7:0] mask = 8'b0000_1000;

wire [7:0] set     = data | mask;     // wymusza 1 na bicie 3
wire [7:0] cleared = data & ~mask;    // wymusza 0 na bicie 3
wire [7:0] toggled = data ^ mask;     // odwraca bit 3
wire [7:0] tested  = data & mask;     // zero, jeśli bit 3 był 0, w przeciwnym razie niezero

To te same idiomy manipulacji bitami, których używa się w C. Syntezują się do operacji na pojedynczych bramkach.

Sprawdzanie, czy wartość to same jedynki

wire is_max = &counter;       // 1, jeśli każdy bit counter to 1

Redukcyjne AND z jedną bramką zamiast zapisu counter == 8'hFF (który też zadziała, a syntezatory zwykle tworzą z niego identyczny sprzęt).

Generowanie bitu parzystości

Wykrywanie dowolnego aktywnego sygnału

wire any_pending = |request_vector;

Jeśli request_vector jest szeroki (na przykład 64 zgłaszających), redukcyjne OR zwija go do jednego sygnału, który można podać na koder priorytetowy albo arbiter.

Operatory przesunięcia

Skoro mowa o operatorach bitowych, oto przesunięcia:

  • a << N przesuwa a w lewo o N pozycji bitowych i wypełnia zerami z prawej strony.
  • a >> N przesuwa a w prawo o N pozycji bitowych i wypełnia zerami z lewej strony.
  • a <<< N to arytmetyczne przesunięcie w lewo (dla wartości bez znaku działa jak <<).
  • a >>> N to arytmetyczne przesunięcie w prawo: wypełnia bitem znaku, gdy a jest ze znakiem.

Przesunięcia o stałą liczbę pozycji są w sprzęcie darmowe (to tylko inne połączenie przewodów). Przesunięcia o wartość znaną dopiero w czasie działania tworzą barrel shifter, który jest większy, ale wciąż tani.

Co dalej

Znasz już wszystkie operatory zwracające pojedynczą wartość. Następny artykuł, Konkatenacja i replikacja, omawia składnię {} i {N{...}}, która buduje szersze wektory z kawałków. Będziesz jej używać stale przy łączeniu modułów o różnych szerokościach.

Najczęściej zadawane pytania

Czym są operatory bitowe w Verilogu?

Operatory bitowe łączą dwa wektory o tej samej szerokości pozycja po pozycji. a & b wykonuje AND bitu 0 z a z bitem 0 z b, bitu 1 z bitem 1 i tak dalej, dając wektor o szerokości wejść. Pełny zestaw to & (AND), | (OR), ^ (XOR), ~ (NOT) oraz formy odwrócone ~&, ~|, ~^ (NAND, NOR, XNOR).

Czym jest operator redukcyjny w Verilogu?

Operator redukcyjny to jednoargumentowa forma operatora bitowego, która zwija cały wektor do jednego bitu. &data zwraca 1 tylko wtedy, gdy każdy bit data jest równy 1. |data zwraca 1, jeśli którykolwiek bit jest równy 1. ^data zwraca XOR wszystkich bitów, czyli parzystość. Formy redukcyjne nie mają argumentu po lewej, tylko po prawej.

Jaka jest różnica między & a && w Verilogu?

& to bitowe AND: łączy bity w pary pozycja po pozycji, a wynik ma tę samą szerokość co argumenty. && to logiczne AND: traktuje każdy argument jako wartość logiczną (zero lub niezero) i zwraca wynik 1-bitowy. 4'b1100 & 4'b0011 daje 4'b0000, a 4'b1100 && 4'b0011 daje 1.

Jak obliczyć parzystość w Verilogu?

Użyj redukcyjnego XOR: parity = ^data. Operator wykonuje XOR na wszystkich bitach data. Dla wektora 8-bitowego to data[7] ^ data[6] ^ ... ^ data[0]. Wynik to 1, gdy ustawionych bitów jest nieparzyście wiele, i 0, gdy parzyście. Odwrócenie przez ~^ daje drugi rodzaj parzystości.

Co robi ~ w Verilogu?

~ to bitowe NOT: odwraca każdy bit argumentu. ~4'b1100 daje 4'b0011. Nie myl go z ! (logiczne NOT), które zwija argument do jednobitowej wartości logicznej i ją odwraca. !4'b1100 daje 1'b0 (argument jest niezerowy, więc zaprzeczenie jego wartości logicznej daje 0).

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ