Deux rôles pour le même symbole
&, |, ^, et leurs frères inversés apparaissent sous deux formes distinctes :
- Forme binaire (deux opérandes) :
a & b- opération bitwise entre vecteurs de même largeur. - Forme unaire (un opérande) :
&a- réduction à travers tous les bits deavers un seul bit.
Le compilateur les distingue en comptant les opérandes. La convention de nommage utilisée par les gens est « les opérateurs bitwise » pour la forme binaire et « les opérateurs de réduction » pour la forme unaire.
Bitwise : un bit à la fois
L'ensemble complet :
| Opérateur | Nom | Bit de sortie en position 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'écrivent avec le tilde en premier et l'opérateur en second. Ce sont des tokens uniques ; pas d'espace entre.
Quand les deux opérandes ont des largeurs différentes, la plus étroite est zero-étendue à gauche pour correspondre. Si tu veux une extension de signe, utilise des opérandes signés explicitement avec $signed().
Réduction : de plusieurs bits à un seul
Les formes unaires des mêmes opérateurs collapsent un vecteur à un seul bit :
Ce que chacun veut dire :
&dataretourne1si tous les bits dedatasont 1, sinon0. Utile pour les checks « est-ce que c'est all-ones ? ».|dataretourne1si un bit quelconque dedataest 1, sinon0. Utile pour les checks « est-ce que c'est non-zéro ? ».^dataretourne la parité - XOR de tous les bits.1si nombre impair de 1,0si pair.~&data,~|data,~^datasont les inverses des précédents.
Tu les verras partout dans le vrai code :
wire empty = ~|fifo_count; // vide si et seulement si count est zéro
wire all_ones = &mask; // tous bits mis
wire parity_bit = ^data; // parité d'un octet
wire any_request = |request_vector; // y a-t-il une requête ?
Les formes réduction sont concises et se synthétisent en arbres de portes évidents : & se réduit à un AND multi-entrées, | à un OR multi-entrées, ^ à un arbre XOR (qui est aussi le générateur de parité en matériel).
Motifs courants
Mettre et effacer des bits
wire [7:0] data;
wire [7:0] mask = 8'b0000_1000;
wire [7:0] set = data | mask; // force le bit 3 à 1
wire [7:0] cleared = data & ~mask; // force le bit 3 à 0
wire [7:0] toggled = data ^ mask; // bascule le bit 3
wire [7:0] tested = data & mask; // zéro si bit 3 était 0, non-zéro sinon
Ce sont les mêmes idiomes de manipulation de bits que tu utiliserais en C. Ils se synthétisent en opérations de porte unique.
Vérifier si une valeur égale all-ones
wire is_max = &counter; // 1 si chaque bit de counter est 1
Une réduction AND avec une porte, vs écrire counter == 8'hFF (qui marcherait aussi ; les synthétiseurs produisent généralement le même matériel).
Générer un bit de parité
Détecter un signal actif
wire any_pending = |request_vector;
Si request_vector est large (disons 64 requérants), la réduction OR collapse à un seul signal que tu peux alimenter dans un encodeur de priorité ou un arbitre.
Opérateurs de shift
Pendant qu'on est sur les opérateurs au niveau bit, les shifts :
a << Ndécaleaà gauche deNpositions de bits, remplit avec des zéros à droite.a >> Ndécaleaà droite deNpositions de bits, remplit avec des zéros à gauche.a <<< Nshift arithmétique gauche (identique à<<pour le non signé).a >>> Nshift arithmétique droit - remplit avec le bit de signe quandaest signé.
Les shifts par une puissance constante sont gratuits en matériel (juste du recâblage). Les shifts par une variable runtime produisent un barrel shifter, plus gros mais encore peu coûteux.
La suite
Tu as maintenant vu chaque opérateur qui retourne une valeur unique. Le prochain document - Concaténation et réplication - couvre la syntaxe {} et {N{...}} qui construit des vecteurs plus larges à partir de morceaux, que tu utiliseras constamment en interfaçant des modules de largeurs différentes.
Questions fréquentes
Que sont les opérateurs bitwise en Verilog ?
Les opérateurs bitwise combinent deux vecteurs de même largeur position par position. a & b fait le AND du bit 0 de a avec le bit 0 de b, du bit 1 avec le bit 1, etc., produisant un vecteur de même largeur que les entrées. L'ensemble complet est & (AND), | (OR), ^ (XOR), ~ (NOT), et les formes inverses ~&, ~|, ~^ (NAND, NOR, XNOR).
Qu'est-ce qu'un opérateur de réduction en Verilog ?
Un opérateur de réduction est une forme unaire d'un opérateur bitwise qui collapse un vecteur entier à un seul bit. &data retourne 1 seulement si chaque bit de data est 1. |data retourne 1 si un bit quelconque est 1. ^data retourne le XOR de tous les bits - la parité. Les formes réduction n'ont pas d'opérande à gauche, seulement à droite.
Quelle est la différence entre & et && en Verilog ?
& est le AND bitwise - il apparie les bits position par position et le résultat a la même largeur que les opérandes. && est le AND logique - il traite chaque opérande comme un booléen (zéro vs non-zéro) et retourne un résultat 1 bit. 4'b1100 & 4'b0011 est 4'b0000 ; 4'b1100 && 4'b0011 est 1.
Comment calculer la parité en Verilog ?
Utilise l'opérateur de réduction XOR : parity = ^data. Il fait le XOR de chaque bit de data. Pour un vecteur 8 bits c'est data[7] ^ data[6] ^ ... ^ data[0]. Le résultat est 1 si un nombre impair de bits est mis, 0 si pair. Inverser avec ~^ donne la parité paire.
Que fait ~ en Verilog ?
~ est le NOT bitwise - il inverse chaque bit de son opérande. ~4'b1100 est 4'b0011. Ne le confonds pas avec ! (NOT logique), qui collapse l'opérande à un booléen 1 bit et inverse ça. !4'b1100 est 1'b0 (l'opérande est non-zéro, NOT de la valeur de vérité donne 0).