Skip to article

Operators and Expressions

Bitwise vs. Logical Operators in Verilog

Why & and && (and their OR/NOT counterparts) give different results on multi-bit values, and why "if (a & b)" is a classic bug when "if (a && b)" was intended.

3 min read · Updated


& is Verilog's bitwise AND: it compares two vectors bit by bit and produces a result of the same width. && is logical AND: for operands containing only known 0/1 bits, it reduces each operand to a single true/false value first (all-zero is false; any nonzero value is true), then ANDs those two 1-bit truth values. If an operand contains x or z, its logical value can instead be ambiguous. The single-character and double-character forms of | (OR) and the single-character ! (logical NOT) follow the same split. Hardware it becomes: bitwise operators describe one operation per bit, in parallel; logical operators describe reduction logic that feeds a 1-bit decision. Their exact synthesized area and gate structure depend on the operand widths, target technology, and optimization, but logical operators always discard per-bit result information.

Comparison pointBitwise (&, |)Logical (&&, ||, !)
Result widthSame as the wider operandAlways 1 bit
What each operand meansEvery bit individuallyReduced to true (nonzero) or false (all-zero) first
Typical useComputing a value: masks, per-bit logicTesting a condition: if, while, ?: guards
If one operand is xPer bit — see Four-State Logic for when a controlling value still resolves itThe whole result is x unless a controlling operand's own truth value is already determined

Predict before running

a = 4'b0100 and b = 4'b0010 share no set bit. Before running it, predict whether a & b and a && b agree on truthiness even though they compute differently.

module bitwise_and4 (
  input  wire [3:0] a,
  input  wire [3:0] b,
  output wire [3:0] y_and
);
  assign y_and = a & b;
endmodule

module tb;
  reg [3:0] a, b;
  wire [3:0] y_and;
  reg logical_y;

  bitwise_and4 u_and (.a(a), .b(b), .y_and(y_and));

  initial begin
    a = 4'b0100;
    b = 4'b0010;
    logical_y = a && b;
    #1;
    $display("a=%b b=%b", a, b);
    $display("a & b  = %b (bitwise, per-bit)", y_and);
    $display("a && b = %b (logical, each operand reduced to true/false first)", logical_y);

    if (y_and) $display("if (a & b): TAKEN");
    else $display("if (a & b): NOT TAKEN");

    if (a && b) $display("if (a && b): TAKEN");
    else $display("if (a && b): NOT TAKEN");

    $display("PASS");
    $finish;
  end
endmodule
Expected output — reveal after you predict
a=0100 b=0010
a & b  = 0000 (bitwise, per-bit)
a && b = 1 (logical, each operand reduced to true/false first)
if (a & b): NOT TAKEN
if (a && b): TAKEN
PASS

a and b share no set bit, so the bitwise AND is all zero and its if does not take the branch. But both a and b are individually nonzero, so each reduces to "true," and the logical AND — and its if — does. The two operators answer different questions: "which bits do both share?" versus "are both operands nonzero?"

Common mistakes

  • Writing if (a & b) when the intent was if (a && b). This compiles cleanly and is the single most common bug this pair of operators causes — the example above is the exact failure mode: a nonzero-but-disjoint pair of vectors reads as "false" under & and "true" under &&.
  • Using &&/|| to compute a multi-bit value. Logical operators always collapse to 1 bit; if you need a per-bit result, the operator is bitwise, not logical.
  • Assuming !a and ~a are interchangeable. !a is logical NOT (reduces a to true/false, then inverts — always 1 bit). ~a is bitwise NOT (inverts every bit, same width as a).

Sources and verification

Example provenance: SkillLift Labs authored the example for this tutorial. tests/scripts/tutorial-verified-examples.test.ts extracts the complete marked example from this Markdown file, compiles it in Verilog-2005 mode with Icarus Verilog, runs it, and requires the simulator output to match the Expected output block exactly. The same test also carries an independent, code-owned semantic oracle, and exercises the article-to-runner source-splitting path. Automation verifies those stated properties; human technical review for indexing remains a separate gate.