~ vs ! in SystemVerilog: The Bug the Compiler Won't Catch

On a single-bit signal, ~ and ! give the same result. On a multi-bit signal, they can give opposite answers — and the compiler won't warn you.

Will this block execute? Yes or no.

logic [1:0] status;
assign status = 1;

if (~status) begin
    // Does this execute?
end

Take a moment before answering.

If you said "no" — think again.

The Setup

This is a question that separates engineers who understand what their code synthesizes to from engineers who rely on intuition. On a single-bit signal, ~ and ! give the same result. That makes the distinction easy to miss — until you hit a multi-bit signal and something breaks in a way that's nearly impossible to find in simulation.

The code above will execute the if block. Not because of a quirk or a compiler bug. Because of exactly what ~ does, and how if evaluates its condition.

Two Operators, Two Different Questions

~ — Bitwise NOT

The tilde operator asks: flip every bit in this vector. It operates on each bit independently and returns a result with the same width as the input.

logic [3:0] input_vec = 4'b1010;
logic [3:0] result;
assign result = ~input_vec;  // result = 4'b0101

If the operand contains x or z bits, those become x in the output. The result is always multi-bit if the input is multi-bit.

! — Logical NOT

The exclamation operator asks: is this entire expression zero? It evaluates the operand as a Boolean condition and returns a single bit.

logic [3:0] some_ones = 4'b0101;
logic result;
assign result = !some_ones;  // result = 1'b0 (non-zero → TRUE → !TRUE = FALSE)

Output is always 1 bit: 1'b1 if the operand is zero, 1'b0 if non-zero, 1'bx if indeterminate.

For a single-bit signal, ~ and ! give the same result. For a multi-bit signal, they can give completely different answers.

Why the Example Executes

status is 2 bits wide. The value 1 is stored as 2'b01.

With ~status:

  1. Bitwise NOT flips every bit: ~(2'b01) = 2'b10
  2. The if statement receives 2'b10
  3. Any non-zero value in an if condition evaluates to TRUE
  4. 2'b10 is not zero → the block executes

With !status:

  1. 2'b01 is non-zero → logically TRUE
  2. !TRUE = 1'b0 (FALSE)
  3. The condition is FALSE → the block does not execute

The intent was almost certainly to check "is status zero?" That requires !. Using ~ gives the opposite answer for this value.

This post is for subscribers only

Already have an account? Sign in.

Subscribe to fpgadesign.io

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe