Dois papéis para o mesmo símbolo
&, |, ^ e seus parentes invertidos aparecem em duas formas distintas:
- Forma binária (dois operandos):
a & b- operação bitwise entre vetores de mesma largura. - Forma unária (um operando):
&a- reduction através de todos os bits deaem um único bit.
O compilador as distingue contando operandos. A convenção de nomes que as pessoas usam é "os operadores bitwise" para a forma binária e "os operadores de reduction" para a forma unária.
Bitwise: Um bit por vez
O conjunto completo:
| Operador | Nome | Bit de saída na posição N |
|---|---|---|
a & b | AND | a[N] AND b[N] |
a | b | OR | a[N] OR b[N] |
a ^ b | XOR | a[N] XOR b[N] |
a ~& b | NAND | NOT (a[N] AND b[N]) |
a ~| b | NOR | NOT (a[N] OR b[N]) |
a ~^ b | XNOR | NOT (a[N] XOR b[N]) |
~a | NOT | NOT a[N] |
Note que ~&, ~|, ~^ são escritos com o til primeiro e o operador depois. São um único token; não há espaço entre eles.
Quando os dois operandos têm larguras diferentes, o mais estreito é zero-extended à esquerda para combinar. Se você quer sign-extension, use operandos signed explicitamente com $signed().
Reduction: De muitos bits para um
As formas unárias dos mesmos operadores colapsam um vetor em um único bit:
O que cada um significa:
&dataretorna1se todos os bits dedatasão 1, caso contrário0. Útil para checks "isso é só 1s?".|dataretorna1se qualquer bit dedataé 1, caso contrário0. Útil para checks "isso é não-zero?".^dataretorna a paridade - XOR de todos os bits.1se contagem ímpar de 1s,0se par.~&data,~|data,~^datasão os inversos dos acima.
Você verá essas formas em todo lugar em código real:
wire empty = ~|fifo_count; // empty se e somente se count e zero
wire all_ones = &mask; // todos os bits setados
wire parity_bit = ^data; // paridade de um byte
wire any_request = |request_vector; // alguma coisa requisitada?
As formas de reduction são concisas e sintetizam em árvores de gates óbvias: & reduz para um gate AND multi-entrada, | para um OR multi-entrada, ^ para uma árvore XOR (que também é o gerador de paridade em hardware).
Padrões comuns
Setando e limpando bits
wire [7:0] data;
wire [7:0] mask = 8'b0000_1000;
wire [7:0] set = data | mask; // forca bit 3 para 1
wire [7:0] cleared = data & ~mask; // forca bit 3 para 0
wire [7:0] toggled = data ^ mask; // inverte bit 3
wire [7:0] tested = data & mask; // zero se bit 3 era 0, nao-zero caso contrario
São os mesmos idiomas de manipulação de bit que você usaria em C. Sintetizam para operações de um único gate.
Verificando se um valor é igual a todos 1s
wire is_max = &counter; // 1 se cada bit de counter e 1
Um AND de reduction com um gate, vs escrever counter == 8'hFF (que também funcionaria; sintetizadores geralmente produzem hardware idêntico).
Gerando um bit de paridade
Detectando qualquer sinal ativo
wire any_pending = |request_vector;
Se request_vector é largo (digamos 64 requisitantes), o OR de reduction colapsa em um único sinal que você pode alimentar em um encoder de prioridade ou árbitro.
Operadores de shift
Já que estamos em operadores em nível de bit, os shifts:
a << Nfaz shift deapara a esquerda emNposições de bit, preenchendo com zeros à direita.a >> Nfaz shift deapara a direita emNposições de bit, preenchendo com zeros à esquerda.a <<< Nshift aritmético à esquerda (mesmo que<<para unsigned).a >>> Nshift aritmético à direita - preenche com o sign bit quandoaé signed.
Shifts por uma potência constante são grátis em hardware (só refiação). Shifts por uma variável de runtime produzem um barrel shifter, que é maior mas ainda barato.
O que vem a seguir
Você agora viu todo operador que retorna um único valor. O próximo doc - Concatenation and Replication - cobre a sintaxe {} e {N{...}} que constrói vetores mais largos a partir de pedaços, que você vai usar constantemente ao fazer interface entre modules de diferentes larguras.
Perguntas frequentes
O que são operadores bitwise em Verilog?
Operadores bitwise combinam dois vetores de mesma largura posição por posição. a & b faz AND do bit 0 de a com o bit 0 de b, do bit 1 com o bit 1, e assim por diante, produzindo um vetor da mesma largura que as entradas. O conjunto completo é & (AND), | (OR), ^ (XOR), ~ (NOT), e as formas de inversão ~&, ~|, ~^ (NAND, NOR, XNOR).
O que é um operador de reduction em Verilog?
Um operador de reduction é uma forma unária de um operador bitwise que colapsa um vetor inteiro em um único bit. &data retorna 1 só se cada bit de data é 1. |data retorna 1 se qualquer bit é 1. ^data retorna o XOR de todos os bits - a paridade. As formas de reduction não têm operando à esquerda, só à direita.
Qual a diferença entre & e && em Verilog?
& é AND bitwise - pareia bits posição por posição e o resultado tem a mesma largura dos operandos. && é AND lógico - trata cada operando como um booleano (zero vs não-zero) e retorna um resultado de 1 bit. 4'b1100 & 4'b0011 é 4'b0000; 4'b1100 && 4'b0011 é 1.
Como calculo paridade em Verilog?
Use o operador de reduction XOR: parity = ^data. Ele faz XOR de cada bit de data junto. Para um vetor de 8 bits isso é data[7] ^ data[6] ^ ... ^ data[0]. O resultado é 1 se um número ímpar de bits está setado, 0 se par. Invertendo com ~^ dá paridade par.
O que ~ faz em Verilog?
~ é NOT bitwise - inverte cada bit do seu operando. ~4'b1100 é 4'b0011. Não confunda com ! (NOT lógico), que colapsa o operando em um booleano de bit único e inverte isso. !4'b1100 é 1'b0 (o operando é não-zero, NOT-ando o valor de verdade dá 0).