Operators and Expressions
Verilog's operators are how the right-hand side of an assign, or any expression inside a procedural block, gets evaluated. Most will look familiar from software languages — the trap is that a handful of them (bitwise vs. logical, in particular) look almost identical but mean very different things in hardware.
Bitwise vs. logical: the operator that trips everyone up
wire [3:0] a = 4'b1100;
wire [3:0] b = 4'b1010;
wire [3:0] bitwise_and = a & b; // 4'b1000 — AND applied bit-by-bit
wire logical_and = a && b; // 1'b1 — "is a nonzero AND is b nonzero?"
& is a bitwise operator — it performs the AND gate's operation independently on every corresponding pair of bits, producing a result the same width as its operands. && is a logical operator — it treats each entire operand as a single true/false value (nonzero = true) and produces a single-bit result. This is the single most common source of subtle Verilog bugs: a & b synthesizes to four physical AND gates; a && b synthesizes to something closer to "OR every bit of a together, OR every bit of b together, then AND those two 1-bit results" — very different hardware for operators that differ by one character.
| Category | Operators | Result width |
|---|---|---|
| Bitwise | ~ & | ^ ^~/~^ | Same as the (widest) operand |
| Logical | ! && || | Always 1 bit |
| Relational | < > <= >= | Always 1 bit |
| Equality | == != === !== | Always 1 bit |
| Arithmetic | + - * / % | Same as the widest operand (see below) |
| Reduction | & | ^ ~& ~| ^~ (unary, one operand) | Always 1 bit |
| Shift | << >> <<< >>> | Same as the left operand |
| Concatenation | {a, b} | Sum of operand widths |
| Replication | {n{a}} | n × width of a |
Logical vs. arithmetic shift: <</>> are logical shifts — every bit shifted in from the vacated end is always 0, regardless of sign. <<</>>> are arithmetic shifts — <<< behaves identically to << (it fills with 0), but >>> fills the vacated high bits with a copy of the original sign bit instead of 0, so a right-shifted signed negative value stays negative (the shift preserves two's-complement sign, the same idea as sign-extension via replication above). >>> only makes a practical difference from >> on a signed operand; on an unsigned vector, >>> and >> produce identical results.
== vs. ===: == is logical equality — if either operand contains an x (unknown) or z bit, the result itself is x, not a real answer. === is case equality — it compares x and z bits literally, and always produces a real 0 or 1. === is a simulation/testbench tool (checking for exact x/z patterns) — it has no synthesis meaning, since real hardware never actually holds an "unknown" value; a signal is always some real voltage. Reach for == in RTL and reserve === for testbenches.
Reduction operators
A reduction operator takes a single multi-bit operand and collapses it to one bit by applying the operator between every pair of adjacent bits — it's the same symbol as the bitwise operator, but used with only one operand instead of two, so context (one operand vs. two) is what tells them apart:
wire [7:0] data;
wire parity = ^data; // XOR every bit of data together — even parity bit
^data is exactly a chain of 7 two-input XOR gates, which is the standard way to generate a parity bit for an 8-bit bus — a good concrete example of a one-character operator standing in for real, synthesized gate-level hardware, not just a simulation convenience.
Concatenation and replication
Concatenation ({}) joins multiple expressions into one wider vector, most-significant operand first:
wire [7:0] full_byte = {upper_nibble, lower_nibble}; // upper_nibble becomes bits [7:4]
Replication ({n{expr}}) repeats an expression n times and concatenates the copies — commonly used for sign-extension, since Verilog has no dedicated "extend" operator:
wire [7:0] narrow = 8'b1111_0110; // -10 in 8-bit two's complement
wire [15:0] extended = {{8{narrow[7]}}, narrow}; // replicate the sign bit 8 times, then append narrow
{{8{narrow[7]}}, narrow} reads as "concatenate: (replicate narrow's top bit 8 times), then narrow itself" — the double braces are required syntax (the outer pair is the concatenation, the inner pair is the replication) and are exactly where beginners miscount braces. This is the direct code equivalent of the sign-extension rule from Number Systems & Codes — replicate the sign bit into every new upper bit.
Worked example: the full adder's equations as expressions
The full adder's minimized equations from Combinational Logic Design —
Sum = A ⊕ B ⊕ Cin
Cout = AB + BCin + ACin
— translate directly into Verilog operators, XOR (^) for ⊕, AND (&) for juxtaposition, OR (|) for +:
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (b & cin) | (a & cin);
Every operator here is bitwise, not logical — a, b, and cin are single bits in this module, so for 1-bit operands bitwise and logical operators happen to produce the same result, but the choice still matters as a matter of habit: writing &&/|| here would silently keep working today and become a bug the moment this code were ever reused with multi-bit operands. Dataflow Modeling picks this exact assign up next and covers what assign itself means beyond just "evaluate this expression."
Operator precedence
Verilog follows a defined precedence order (unary operators bind tightest, then arithmetic, then shift, relational, equality, bitwise, logical, and finally the ternary ?: loosest) — but memorizing the full table is rarely worth it:
The cout expression above is parenthesized explicitly ((a & b) | (b & cin) | (a & cin)) even though Verilog's precedence rules would evaluate it correctly unparenthesized. That's deliberate: explicit parentheses cost nothing and remove any need for a reader (including future you) to recall where & ranks relative to |. Reserve relying on precedence for the few operators everyone already knows cold from arithmetic (* before +); parenthesize everything else.
What's next
Section A has covered enough vocabulary — modules, ports, data types, operators — to read and write basic Verilog. Section B picks up the full adder again and asks a different question: the same circuit can be described structurally (wiring gates together), as dataflow (the assign statements used above), or behaviorally (always/initial procedural code) — the next page starts with the first of those three, gate-level structural modeling.