Verilog · lesson 4

Operators You Will Actually Use

Bitwise versus logical operators, shifts, concatenation and replication, the conditional operator, and two width and sign pitfalls that pass simulation silently.

11 min read · Free, no login

Bitwise versus logical: the mistake every beginner makes once

Verilog has two families of AND/OR/NOT that look similar and do very different things. The bitwise operators (&, |, ~, ^) operate on a vector one bit at a time, producing a result the same width as the operands โ€“ this is the gate-level AND/OR/XOR from the Digital Electronics path's second lesson, applied across every bit position independently. The logical operators (&&, ||, !) treat their entire operand as a single true/false value (any nonzero vector is "true") and always produce a single-bit result.

a4 & b4   (bitwise AND) = 1000
a4 && b4  (logical AND) = 1

With a4 = 1100 and b4 = 1010, the bitwise AND computes bit by bit โ€“ only bit 3 is set in both operands, so the result is 1000. The logical AND asks two yes/no questions instead ("is a4 nonzero?", "is b4 nonzero?"), both true, so the answer is the single bit 1. The two operators answer genuinely different questions, and using & where && was meant (or the reverse) compiles without complaint every time, silently producing a differently-shaped, differently-meaning result.

Reduction operators

A reduction operator takes a single vector and collapses it to one bit by applying the same operator between every pair of bits โ€“ it is written as a unary prefix, which is what distinguishes it from the bitwise form of the same symbol.

&mask (reduction AND) = 0   |mask (reduction OR) = 1

With mask = 1011, &mask asks "are all four bits 1?" (no, so 0), and |mask asks "is at least one bit 1?" (yes, so 1). Reduction XOR, ^mask, is the one worth remembering specifically: it computes the parity of the whole vector, and is exactly the check used by the parity-bit scheme in the Digital Electronics path's eleventh lesson, written as a single operator instead of a chain of XOR gates.

Shifts, concatenation, and replication

4'b0011 << 2 = 1100
4'b1100 >> 2 = 0011 (logical shift, zero-fills)

<< and >> are logical shifts on an unsigned vector โ€“ bits shifted off either end are discarded, and the vacated positions are filled with zero, visible above as both directions correctly zero-filling rather than wrapping. This is the same operation the barrel shifter in the Digital Electronics path's ninth lesson builds directly in hardware, one stage per power-of-two shift amount; a shift by a variable amount in Verilog (x << shift_amount) is exactly a request for a barrel shifter to be synthesized, not a sequential shift register.

Concatenation, {a, b}, joins two or more vectors end to end into one wider vector; replication, {3{2'b10}}, repeats a pattern a fixed number of times and concatenates the copies.

{a4, b4}      = 11001010
{3{2'b10}}    = 101010

With a4 = 1100 and b4 = 1010, {a4, b4} is simply those two 4-bit vectors placed side by side into one 8-bit vector. {3{2'b10}} repeats the 2-bit pattern 10 three times, producing the 6-bit result 101010 โ€” the brace-count syntax ({N{pattern}}) is the standard way to build a wide constant or a sign/zero-extension pattern without writing it out bit by bit.

The conditional operator

cond ? a : b selects a when cond is true and b otherwise, evaluating the whole expression in one step rather than branching.

(1 > 0) ? a4 : b4 = 1100

This is worth pausing on specifically because it is not just a convenience: inside an assign statement, cond ? a : b is the standard, direct way to describe a multiplexer, and a synthesis tool reads it exactly that way โ€“ one conditional operator becomes one 2-to-1 mux, chained conditional operators (s1 ? a : s2 ? b : c) become a chain of muxes. Lesson 5 uses this constantly.

Two pitfalls that pass simulation silently

Both of the following compile cleanly, simulate without any warning, and produce the wrong answer โ€” which is exactly why they belong in an interview-prep lesson rather than just a syntax reference.

Width truncation. Verilog does not stop an assignment from losing bits; it simply drops whatever does not fit in the left-hand side's declared width.

4'hF + 4'h1 assigned to 4-bit reg (truncated) = 0 (dropped carry!)

4'hF + 4'h1 is 16 (5'b10000) โ€” five bits are needed to hold it without loss. Assigning that result into a 4-bit register keeps only the bottom four bits and silently discards the carry, producing 0 instead of 16. The fix is not a different operator; it is declaring the destination wide enough for the true result before the addition happens, exactly the discipline the carry-lookahead adder in the Digital Electronics path's eighth lesson is built around โ€” every bit of a sum has to go somewhere, including the final carry-out.

Signed versus unsigned interpretation. A plain reg or wire is unsigned by default; the bit pattern is only ever interpreted as a negative number if the signal is explicitly declared signed, the same two's complement representation the Digital Electronics path's first lesson covers in detail.

reg signed [3:0] sa;   // declared signed
reg        [3:0] ua;   // plain -- unsigned by default
sa = -4'sd3;            // stores the bit pattern 1101
ua = 4'b1101;           // the identical bit pattern, undeclared signedness
$display("signed read:   %d", sa);
$display("unsigned read: %d", ua);
signed read:   -3
unsigned read: 13

sa and ua hold the exact same 4-bit pattern, 1101. Declared signed, %d correctly reads it as −3. The identical bit pattern, held in a plain undeclared vector, reads as the unsigned value 13 instead โ€” with no error or warning marking the difference. The pattern of bits on the wire never changes; only the declared signedness changes how an arithmetic operator or %d interprets it. Mixing a signed and an unsigned operand in the same expression silently treats the whole expression as unsigned, which is a frequent, hard-to-spot source of wrong results in real RTL.

Interview questions

What is the difference between & and && in Verilog? & is bitwise AND, applied independently to each bit of two equal-width vectors, producing a result of the same width. && is logical AND, treating each entire operand as a single true/false value and always producing one bit.

How would you describe a 2-to-1 multiplexer in one line of dataflow Verilog? assign y = sel ? a : b; โ€” the conditional operator maps directly onto a mux in synthesis, and chaining it builds a chain of muxes for more than two inputs.

Why can an addition produce a wrong result in simulation with no error or warning? Verilog truncates an assignment to fit the destination's declared width rather than flagging an overflow. Adding two 4-bit numbers whose true sum needs 5 bits and assigning the result into a 4-bit register silently drops the carry bit.

Key takeaways

  • Bitwise operators (& | ~ ^) act per bit and preserve width; logical operators (&& || !) treat the whole operand as one true/false value and always return one bit.
  • A reduction operator (unary &expr, |expr, ^expr) collapses a whole vector to one bit; reduction XOR computes parity.
  • <</>> are logical shifts that zero-fill; {a,b} concatenates; {N{pattern}} replicates a pattern N times.
  • cond ? a : b both reads as a conditional expression and synthesizes directly as a multiplexer.
  • Verilog truncates an assignment to the destination's declared width with no warning โ€” size the destination for the true result, not just the operands.
  • A signal is unsigned unless declared signed; the bit pattern never changes, only how an operator interprets it โ€” mixing signed and unsigned operands in one expression silently makes the whole expression unsigned.