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 : bboth 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.