Due ruoli per lo stesso simbolo
&, |, ^ e i loro fratelli invertiti compaiono in due forme distinte:
- Forma binaria (due operandi):
a & b, operazione bit a bit tra vettori della stessa larghezza. - Forma unaria (un operando):
&a, riduzione di tutti i bit diaa un solo bit.
Il compilatore le distingue contando gli operandi. Per convenzione si parla di "operatori bitwise" per la forma binaria e di "operatori di riduzione" per quella unaria.
Bitwise: un bit alla volta
L'insieme completo:
| Operatore | Nome | Bit di uscita in posizione 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] |
Nota che ~&, ~|, ~^ si scrivono con la tilde prima e l'operatore dopo. Sono un unico token, senza spazi in mezzo.
Quando i due operandi hanno larghezze diverse, quello più stretto viene esteso con zeri a sinistra per pareggiare. Se vuoi l'estensione del segno, usa esplicitamente operandi con segno tramite $signed().
Riduzione: da molti bit a uno
Le forme unarie degli stessi operatori comprimono un vettore in un solo bit:
Cosa significa ciascuna:
&datarestituisce1se tutti i bit didatavalgono 1, altrimenti0. Utile per verificare "è tutto a uno?".|datarestituisce1se almeno un bit didatavale 1, altrimenti0. Utile per verificare "è diverso da zero?".^datarestituisce la parità, cioè lo XOR di tutti i bit:1se il numero di 1 è dispari,0se è pari.~&data,~|data,~^datasono gli inversi dei precedenti.
Li troverai ovunque nel codice reale:
wire empty = ~|fifo_count; // empty solo se count vale zero
wire all_ones = &mask; // tutti i bit a 1
wire parity_bit = ^data; // parità di un byte
wire any_request = |request_vector; // c'è qualche richiesta?
Le forme di riduzione sono concise e si sintetizzano in alberi di porte evidenti: & diventa una porta AND a più ingressi, | una OR a più ingressi, ^ un albero di XOR (che in hardware è anche il generatore di parità).
Pattern comuni
Impostare e azzerare bit
wire [7:0] data;
wire [7:0] mask = 8'b0000_1000;
wire [7:0] set = data | mask; // forza il bit 3 a 1
wire [7:0] cleared = data & ~mask; // forza il bit 3 a 0
wire [7:0] toggled = data ^ mask; // inverte il bit 3
wire [7:0] tested = data & mask; // zero se il bit 3 era 0, diverso da zero altrimenti
Sono gli stessi idiomi di manipolazione dei bit che useresti in C. Si sintetizzano in operazioni a porta singola.
Verificare se un valore è tutto a uno
wire is_max = &counter; // 1 se ogni bit di counter vale 1
Una riduzione AND con una sola porta, invece di scrivere counter == 8'hFF (che funzionerebbe comunque: di solito i sintetizzatori producono hardware identico).
Generare un bit di parità
Rilevare un qualsiasi segnale attivo
wire any_pending = |request_vector;
Se request_vector è largo (diciamo 64 richiedenti), la riduzione OR lo comprime in un solo segnale che puoi mandare a un priority encoder o a un arbitro.
Operatori di shift
Già che parliamo di operatori a livello di bit, ecco gli shift:
a << Nspostaaa sinistra diNposizioni, riempiendo con zeri a destra.a >> Nspostaaa destra diNposizioni, riempiendo con zeri a sinistra.a <<< Nè lo shift aritmetico a sinistra (uguale a<<per i valori senza segno).a >>> Nè lo shift aritmetico a destra: riempie con il bit di segno quandoaè con segno.
Gli shift di una quantità costante in hardware sono gratis (è solo un diverso cablaggio). Gli shift di una quantità variabile a runtime producono un barrel shifter, più grande ma comunque economico.
Cosa viene dopo
Ora hai visto tutti gli operatori che restituiscono un singolo valore. La prossima pagina, Concatenation and Replication, tratta la sintassi {} e {N{...}} che costruisce vettori più larghi a partire da pezzi: la userai di continuo per collegare moduli di larghezze diverse.
Domande frequenti
Cosa sono gli operatori bitwise in Verilog?
Gli operatori bitwise combinano due vettori della stessa larghezza posizione per posizione. a & b fa l'AND del bit 0 di a con il bit 0 di b, del bit 1 con il bit 1 e così via, producendo un vettore largo quanto gli ingressi. L'insieme completo è & (AND), | (OR), ^ (XOR), ~ (NOT) e le forme invertite ~&, ~|, ~^ (NAND, NOR, XNOR).
Cos'è un operatore di riduzione in Verilog?
Un operatore di riduzione è la forma unaria di un operatore bitwise, che comprime un intero vettore in un solo bit. &data restituisce 1 solo se ogni bit di data vale 1. |data restituisce 1 se almeno un bit vale 1. ^data restituisce lo XOR di tutti i bit, cioè la parità. Le forme di riduzione non hanno un operando a sinistra, solo a destra.
Qual è la differenza tra & e && in Verilog?
& è l'AND bitwise: accoppia i bit posizione per posizione e il risultato ha la stessa larghezza degli operandi. && è l'AND logico: tratta ogni operando come un booleano (zero oppure diverso da zero) e restituisce un risultato di 1 bit. 4'b1100 & 4'b0011 vale 4'b0000; 4'b1100 && 4'b0011 vale 1.
Come si calcola la parità in Verilog?
Usa l'operatore di riduzione XOR: parity = ^data. Fa lo XOR di tutti i bit di data. Per un vettore di 8 bit equivale a data[7] ^ data[6] ^ ... ^ data[0]. Il risultato è 1 se il numero di bit a 1 è dispari, 0 se è pari. Invertendo con ~^ ottieni la parità pari.
Cosa fa ~ in Verilog?
~ è il NOT bitwise: inverte ogni bit dell'operando. ~4'b1100 vale 4'b0011. Non confonderlo con ! (NOT logico), che riduce l'operando a un booleano di un bit e poi lo inverte. !4'b1100 vale 1'b0 (l'operando è diverso da zero, e negare il valore di verità dà 0).