Tutorial 04 · Verilog Series

Verilog Operators

Verilog has a rich operator set covering arithmetic, logic, bitwise, reduction, shift, comparison, and structural operations. Each maps directly to real hardware — understanding which operator synthesizes to which gate or adder is the core of RTL design.

Arithmetic (+,-,*,%) Bitwise (&,|,^,~) Logical (&&,||,!) Reduction (&a, |a, ^a) Shift (>>, >>>) Conditional ?: Concatenation {}
Verilog Operators → Synthesized Hardware a + b → Adder / ALU a & b → AND gates a | b → OR gates a ^ b → XOR gates s ? a : b → Multiplexer a >> n → Barrel Shifter | a → N-input OR {a, b} → Wire join (free) a * b → DSP / Multiplier ==, <, > → Comparator
Every Verilog operator maps to specific hardware — synthesis turns these expressions into gates and macros.

1. Operator Quick-Reference

CategoryOperatorsReturnsSynthesizes To
Arithmetic+  -  *  /  %  **VectorAdder, subtractor, multiplier, divider
Relational<  >  <=  >=1-bitComparator
Equality==  !=  ===  !==1-bitComparator (== / !=); sim-only (=== / !==)
Logical&&  ||  !1-bitAND/OR/NOT on boolean operands
Bitwise&  |  ^  ~^  ~VectorGate arrays (AND/OR/XOR/XNOR/NOT)
Reduction&a  |a  ^a  ~&a  ~|a1-bitN-input gate tree
Shift<<  >>  <<<  >>>VectorBarrel shifter (variable); wires (constant)
Conditional? :VectorMultiplexer
Concatenation{ }VectorWire joins (zero cost)
Replication{n{ }}VectorWire joins (zero cost)

2. Arithmetic Operators

verilog
wire [7:0] a = 8'd50, b = 8'd20;

wire [7:0] add_r  = a + b;    // 70  (truncated to 8 bits)
wire [8:0] add_nc = a + b;    // 70  (9-bit: carry won't overflow)
wire [7:0] sub_r  = a - b;    // 30
wire [15:0] mul_r = a * b;    // 1000 (need 16 bits for N*M result)
wire [7:0] div_r  = a / b;    // 2   (integer division; avoid in RTL)
wire [7:0] mod_r  = a % b;    // 10  (remainder; avoid in RTL)
wire [7:0] pow_r  = 2 ** 3;    // 8   (power; avoid in RTL)
Overflow: Verilog arithmetic wraps silently. If you need carry-out, declare the result one bit wider: wire [8:0] sum = {1'b0, a} + {1'b0, b}; — the MSB captures the carry. Division and modulo synthesize to huge divider circuits — avoid them in RTL; use shift operators for powers of 2.

3. Relational & Equality Operators

verilog
wire [3:0] x = 4'd9, y = 4'd5;

// Relational — return 1-bit result
wire lt  = x <  y;   // 0 (9 < 5: false)
wire gt  = x >  y;   // 1 (9 > 5: true)
wire lte = x <= y;   // 0
wire gte = x >= y;   // 1

// Equality
wire eq  = x ==  y;  // 0  (logical equal; X input → X result)
wire neq = x !=  y;  // 1
wire ceq = x === y;  // 0  (case equal; X matches X exactly)
wire cne = x !== y;  // 1  (case not-equal)
=== vs ==: Use === only in testbenches to catch X values: if (result === 8'hxx) $error("X detected");. It is not synthesizable. In RTL, always use ==.

4. Logical Operators

Logical operators treat their operands as single-bit booleans (0 = false, non-zero = true) and always return 1 bit.

verilog
wire [3:0] a = 4'b1010;  // non-zero → true
wire [3:0] b = 4'b0000;  // zero → false

wire land = a &&  b;    // 0 (true && false = false)
wire lor  = a ||  b;    // 1 (true || false = true)
wire lnot = !a;           // 0 (not true = false)

// Common RTL usage: enable conditions
wire go = valid && !stall && ready;
Logical vs Bitwise trap: 4'b1010 & 4'b0101 = 4'b0000 (bitwise AND, all bits differ), but 4'b1010 && 4'b0101 = 1 (both non-zero, so logical AND is true). Using & where you meant && in an enable condition is a classic bug.

5. Bitwise Operators

Bitwise operators apply the operation to each corresponding bit pair, returning a vector of the same width.

verilog
wire [3:0] a = 4'b1100;
wire [3:0] b = 4'b1010;

wire [3:0] bw_and  = a &  b;   // 4'b1000  (AND each bit)
wire [3:0] bw_or   = a |  b;   // 4'b1110  (OR each bit)
wire [3:0] bw_xor  = a ^  b;   // 4'b0110  (XOR each bit)
wire [3:0] bw_xnor = a ~^ b;   // 4'b1001  (XNOR: ~(a^b))
wire [3:0] bw_not  = ~a;        // 4'b0011  (invert all bits)

6. Reduction Operators

Reduction operators take a single vector and collapse it to 1 bit by applying the operation across all bits.

verilog
wire [7:0] d = 8'b10110101;

wire r_and  = &d;    // 0  (AND all bits: 1&0&1&1&0&1&0&1 = 0)
wire r_or   = |d;    // 1  (OR: at least one bit is 1)
wire r_xor  = ^d;    // 1  (XOR = parity: count of 1s is odd)
wire r_nand = ~&d;   // 1  (NAND = ~AND)
wire r_nor  = ~|d;   // 0  (NOR = ~OR)

// Practical: all-ones detect
wire all_ones = &d;           // 1 only if every bit is 1
// Practical: non-zero detect (any bit set)
wire nonzero  = |d;           // 1 if any bit is 1
// Practical: odd parity bit
wire parity   = ^d;           // XOR of all bits

7. Shift Operators

OperatorNameFill BitsUse Case
<<Logical left shift0 fills LSBsMultiply by 2ⁿ (unsigned)
>>Logical right shift0 fills MSBsDivide by 2ⁿ (unsigned)
<<<Arithmetic left shift0 fills LSBs (same as <<)Signed multiply by 2ⁿ
>>>Arithmetic right shiftSign-bit fills MSBsSigned divide by 2ⁿ (preserve sign)
verilog
wire [7:0] a = 8'd20;  // 0001_0100
wire [7:0] sll = a << 2; // 0101_0000 = 80  (×4)
wire [7:0] slr = a >> 2; // 0000_0101 = 5   (÷4)

reg signed [7:0] s = -8;  // 1111_1000
wire signed [7:0] sar = s >>> 1; // 1111_1100 = -4 (sign extended)
wire signed [7:0] slr2= s >>  1; // 0111_1100 = 124 (zero-filled: wrong for signed)

// Variable shift (barrel shifter in hardware)
wire [7:0] vshift = a << shift_amt; // shift_amt is a runtime signal

8. Conditional Operator (?:)

The ternary conditional operator synthesizes directly to a multiplexer. It is the only way to write a mux in a continuous assignment.

verilog
// 2:1 mux
assign out = sel ? a : b;

// 4:1 mux (nested)
assign out4 = (sel == 2'b00) ? d0 :
               (sel == 2'b01) ? d1 :
               (sel == 2'b10) ? d2 : d3;

// Tri-state (Z output)
assign bus = oe ? data : 8'bz;

9. Concatenation & Replication

verilog
wire [3:0] hi = 4'b1010;
wire [3:0] lo = 4'b0101;

// Concatenation: join signals into wider vector
wire [7:0] byte = {hi, lo};        // 8'b10100101

// Sign extension: extend 8-bit signed to 16-bit
wire [7:0]  s8  = 8'shFF;          // -1
wire [15:0] s16 = {{8{s8[7]}}, s8}; // 16'hFFFF (-1 in 16-bit)

// Replication: {count{value}}
wire [7:0] zeros = {8{1'b0}};        // 8'b00000000
wire [7:0] ones  = {8{1'b1}};        // 8'b11111111

// Left-hand side concatenation (split a bus into parts)
wire [7:0] in_bus;
wire [3:0] msn, lsn;
assign {msn, lsn} = in_bus;         // split into two nibbles

10. Operator Precedence

Higher precedence evaluates first. When in doubt, use parentheses — they are free in hardware and prevent bugs.

PrecedenceOperators
Highest (1)!  ~  &(unary)  |(unary)  ^(unary)
2**
3*  /  %
4+  -
5<<  >>  <<<  >>>
6<  >  <=  >=
7==  !=  ===  !==
8& (binary)
9^  ~^ (binary)
10| (binary)
11&&
12||
Lowest (13)? :

11. RTL Design Examples

Parameterizable Barrel Shifter

verilog
module barrel_shift #(parameter W=8) (
    input  [W-1:0]       data,
    input  [2:0]         amt,
    input                 dir,   // 0=left, 1=right
    output [W-1:0]       result
);
    assign result = dir ? (data >> amt) : (data << amt);
endmodule

Parity Generator

verilog
module parity_gen #(parameter N=8) (
    input  [N-1:0] data,
    output         odd_parity,
    output         even_parity
);
    assign even_parity = ^data;       // XOR reduction
    assign odd_parity  = ~^data;      // XNOR reduction
endmodule

Priority Encoder (3 inputs)

verilog
module priority_enc (
    input  [3:0] req,       // request lines
    output [1:0] grant,     // encoded grant
    output       valid      // at least one request
);
    assign valid = |req;     // reduction OR
    assign grant = req[3] ? 2'd3 :
                   req[2] ? 2'd2 :
                   req[1] ? 2'd1 : 2'd0;
endmodule