Skip to content

Operator Precedence

Complete precedence table, associativity rules, classic precedence traps.

Module 4 · Page 4.10

Why Precedence Bugs Are Particularly Nasty

A type error fails at compile time. A width mismatch shows up in simulation. A precedence bug produces the wrong result silently, in clean two-state simulation, with no warning from any tool. The expression evaluates — just not the way you intended. RTL passes synthesis. Simulation matches the (wrong) expected model. The hardware works differently.

The three expressions that cause the most real-world bugs:

Expression as writtenHow SV parses itWhat engineer intended
a & b == ca & (b == c)== binds tighter than &(a & b) == c
a | b && ca | (b && c)&& binds tighter than |(a | b) && c
sel ? a : b + csel ? a : (b + c)+ binds tighter than ?:(sel ? a : b) + c

Complete Precedence Table — Highest to Lowest

Higher precedence means the operator binds first — it grabs its operands before lower-precedence operators do. Operators on the same row have equal precedence and are resolved by associativity (left-to-right unless noted).

LevelOperatorsTypeAssociativityNotes
1 (Highest)( )   [ ]   { }   {{ }}Grouping / index / concat / replicateExplicit grouping always wins
2+   -   !   ~   &   ~&   |   ~|   ^   ~^   ^~   ++   --Unary operators (sign, logic, reduction, inc/dec)Right-to-leftAll unary ops — same level, right-associative
3**ExponentiationRight-to-left2**3**2 = 2**(3**2) = 512
4*   /   %Multiply, divide, moduloLeft-to-rightStandard arithmetic priority
5+   -Binary add, subtractLeft-to-rightLower than multiply
6<<   >>   <<<   >>>Shift operatorsLeft-to-rightBelow arithmetic — a + b << 2 = (a+b) << 2
7<   <=   >   >=Relational (less/greater)Left-to-rightBelow shift
8==   !=   ===   !==   ==?   !=?Equality / case equality / wildcard equalityLeft-to-rightHigher than bitwise — the C-programmer trap
9&Binary bitwise ANDLeft-to-rightBelow equality — a & b == c = a & (b==c)
10^   ~^   ^~Binary bitwise XOR / XNORLeft-to-rightBelow bitwise AND
11|Binary bitwise ORLeft-to-rightBelow XOR — AND | XOR | OR from top to bottom
12&&Logical ANDLeft-to-rightBelow bitwise OR — a | b && c = a | (b && c)
13||Logical ORLeft-to-rightBelow logical AND
14insideSet membershipLeft-to-rightSame level as relational operators conceptually
15? :Conditional (ternary)Right-to-leftVery low — sel ? a : b + c = sel ? a : (b+c)
16 (Lowest)=   +=   -=   *=   /=   %=   &=   |=   ^=   <<=   >>=Assignment operatorsRight-to-leftAlways last — entire RHS evaluates before assignment

Step-by-Step Evaluation — Seeing Precedence in Action

The Classic Bitwise-vs-Equality Trap

ExpressionStep 1 (higher prec first)Step 2Final result
data & 8'hF0 == 8'hA08'hF0 == 8'hA01'b0 (equality first)data & 1'b08'h008'h00 — wrong!
(data & 8'hF0) == 8'hA0data & 8'hF0 → masked datamasked == 8'hA0 → 0 or 1Correct boolean

Logical vs Bitwise Mixing Trap

ExpressionParsed asResult (a=8'hF0, b=8'h0F, c=1'b1)
a | b && ca | (b && c)&& first8'hF0 | (8'h0F && 1) = 8'hF0 | 1 = 8'hF1
(a | b) && cBitwise OR first, then logical AND(8'hFF) && 1 = 1 (logical: non-zero AND 1)

Conditional Trap — Lower Operand Captured

ExpressionParsed asIf sel=1, a=5, b=3, c=10
sel ? a : b + csel ? a : (b + c)sel=1 → result = a = 5
(sel ? a : b) + cTernary first, then add csel=1 → (a) + c = 5 + 10 = 15

Shift and Arithmetic Interaction

ExpressionParsed asWith a=2, b=3, n=2
a + b << n(a + b) << n — add before shift(2+3) << 2 = 5 << 2 = 20
a + (b << n)Shift before add2 + (3 << 2) = 2 + 12 = 14

Code Examples — Precedence Traps in Practice

Example 1 — The Bitwise Mask Check Bug

Precedence Trap 1 — & vs ==
module tb_prec_bitwise;
 
logic [7:0] data = 8'hA5;   // 1010_0101
 
initial begin
 
// BUGGY: engineer wants to check if upper nibble == 0xA
// Written: data & 8'hF0 == 8'hA0
// Parsed:  data & (8'hF0 == 8'hA0) = data & 0 = 8'h00
$display("BUGGY:   data & 8'hF0 == 8'hA0  = %0h",
data & 8'hF0 == 8'hA0);   // 0 — wrong!
 
// CORRECT: parentheses around the mask operation
$display("CORRECT: (data & 8'hF0) == 8'hA0 = %0b",
(data & 8'hF0) == 8'hA0); // 1 — upper nibble is A
 
// Verify the difference visually
$display("8'hF0 == 8'hA0       = %0b", 8'hF0 == 8'hA0);  // 0
$display("data & 0            = %0h",  data & 1'b0);     // 0
$display("data & 8'hF0         = %0h",  data & 8'hF0);   // A0
 
$finish;
end
 
endmodule

Expected output:

Simulation Output
BUGGY:   data & 8'hF0 == 8'hA0  = 0
CORRECT: (data & 8'hF0) == 8'hA0 = 1
8'hF0 == 8'hA0       = 0
data & 0            = 0
data & 8'hF0         = A0

Example 2 — Conditional and Arithmetic Precedence

Precedence Trap 2 — Conditional vs Arithmetic
module tb_prec_conditional;
 
logic       sel = 1'b1;
logic [7:0] a   = 8'd5;
logic [7:0] b   = 8'd3;
logic [7:0] c   = 8'd10;
logic [7:0] result;
 
initial begin
 
// BUGGY intent: add c to whichever of a or b is selected
// Written:  sel ? a : b + c
// Parsed:   sel ? a : (b + c)  — + has higher precedence than ?:
result = sel ? a : b + c;
$display("BUGGY  sel?a:b+c   = %0d  (parsed as sel?a:(b+c))", result);  // 5
 
// CORRECT: parentheses enforce ternary-first
result = (sel ? a : b) + c;
$display("CORRECT (sel?a:b)+c = %0d", result);   // 15 (5 + 10)
 
// Same trap with sel=0
sel = 1'b0;
result = sel ? a : b + c;     // = 3 + 10 = 13 (b+c)
$display("sel=0: sel?a:b+c   = %0d", result);   // 13
result = (sel ? a : b) + c;   // = 3 + 10 = 13 (same — different path, same result)
$display("sel=0: (sel?a:b)+c  = %0d", result);   // 13
// Bug is only visible when sel=1 — classic intermittent bug!
 
$finish;
end
 
endmodule

Expected output:

Simulation Output
BUGGY  sel?a:b+c   = 5  (parsed as sel?a:(b+c))
CORRECT (sel?a:b)+c = 15
sel=0: sel?a:b+c   = 13
sel=0: (sel?a:b)+c  = 13

Example 3 — Negation and Reduction Operator Interaction

Precedence Trap 3 — Unary Negation vs Reduction
module tb_prec_unary;
 
logic [3:0] data = 4'hA;   // 1010
 
initial begin
 
// Trap: ~&data vs ~(&data)
$display("&data        = %0b",  &data);       // 0  — AND reduction: 1010
$display("~(&data)     = %0b",  ~(&data));     // 1  — negate the AND result
$display("~&data       = %0b",  ~&data);       // 1  — NAND reduction: same as ~(&data)
// ~& is a single NAND reduction operator, not ~ followed by & reduction
 
// The dangerous one: !&data
$display("!&data       = %0b",  !&data);       // 1  — logical not of AND reduction
$display("!data        = %0b",  !data);        // 0  — logical not of 4-bit data (non-zero → 0)
// !&data parsed as !(&data) = !(0) = 1
// Very different from !(data) = !(8'hA) = 0
 
// Right-associativity of unary operators
$display("~~data       = %04b", ~~data);       // 1010 — double invert = original
$display("!!data       = %0b",  !!data);       // 1  — !(!(non-zero)) = !(0) = 1
 
$finish;
end
 
endmodule

Example 4 — Full Verification: All Traps in One Testbench

Example 4 — Comprehensive Precedence Validation
module tb_precedence_all;
 
logic [7:0] a = 8'hAA;   // 1010_1010
logic [7:0] b = 8'h55;   // 0101_0101
logic       s = 1'b1;
logic [7:0] r;
 
initial begin
 
// ── Arithmetic before shift ───────────────────────────────────
r = 8'd2 + 8'd3 << 1;     // (2+3)<<1 = 5<<1 = 10
$display("2+3<<1       = %0d  (expect 10)", r);
 
// ── Equality above bitwise AND ─────────────────────────────────
r = a & b == 8'h00;        // a & (b==0) = AA & 0 = 0
$display("a&b==0        = %0h  (expect 0, NOT 1)", r);
r = (a & b) == 8'h00;      // (AA&55)=00, 00==00 → 1
$display("(a&b)==0      = %0b  (expect 1)", r);
 
// ── Bitwise OR below logical AND ──────────────────────────────
r = a | b && 1'b0;          // a | (b && 0) = AA | 0 = AA
$display("a|b&&0        = %0h  (expect AA)", r);
r = (a | b) && 1'b0;        // (FF) && 0 = 0
$display("(a|b)&&0      = %0b  (expect 0)", r);
 
// ── Conditional: ? : is right-associative ────────────────────
// a ? b ? c : d : e  =  a ? (b ? c : d) : e
logic [7:0] x = 8'h01, y = 8'h02, z = 8'h03;
r = s ? s ? x : y : z;     // s=1: s?(s?x:y):z = 1?(1?x:y):z = x = 0x01
$display("s?s?x:y:z     = %0h  (expect 01)", r);
 
// ── Exponentiation is right-associative ───────────────────────
r = 2 ** 3;                 // 2**3 = 8
$display("2**3          = %0d  (expect 8)", r);
 
$finish;
end
 
endmodule

How the Simulator Resolves Complex Expressions

Associativity — When Precedence Is Equal

When two operators have equal precedence, associativity determines the grouping. Almost all binary operators are left-associative — they group left-to-right. The exceptions are unary operators, exponentiation, the conditional ?:, and assignment operators — these are right-associative.

ExpressionAssociativityGrouped asNote
a - b - cLeft(a - b) - cStandard math grouping
a && b && cLeft(a && b) && cShort-circuits left to right
2 ** 3 ** 2Right2 ** (3 ** 2) = 512NOT (2**3)**2 = 64
a ? b : c ? d : eRighta ? b : (c ? d : e)Nested ternary chains naturally
a = b = cRighta = (b = c)c assigned to b first, then a

Width Determination and Precedence

Precedence determines the parse tree, but the result width at each node is determined by the operand widths and operator rules. Parentheses that change grouping can therefore also change the result width of intermediate sub-expressions. Example: 8'hFF + 1 inside a 16-bit context yields 16'h100, but if you wrap just the addition: (8'hFF + 1) evaluates in an 8-bit context first → 8'h00 (overflow), then zero-extended to 16 bits. Precedence and width are coupled.

Classic Precedence Bugs Engineers Commit

Bug 1 — Mask Check Without Parentheses

Bug Collection — All Classic Precedence Traps
logic [7:0] data  = 8'hA5;
logic [7:0] mask  = 8'hF0;
logic [7:0] exp   = 8'hA0;
logic        flag  = 1'b1;
logic        valid = 1'b1;
logic [7:0] sel_a = 8'h10;
logic [7:0] sel_b = 8'h20;
logic [7:0] offset = 8'h05;
logic [7:0] result;
 
// ── BUG 1: Bitwise mask check ──────────────────────────────────────
if (data & mask == exp)                // BUGGY: data & (mask==exp) = data & 0 = 0
$display("upper nibble is A");         // never fires
if ((data & mask) == exp)              // CORRECT
$display("upper nibble is A");         // fires
 
// ── BUG 2: Flag guard combined with bitwise ────────────────────────
if (valid & data == 8'hA5)             // BUGGY: valid & (data==A5) = 1 & 1 = 1'b1 ← width 1!
$display("guarded check");             // fires by accident — valid & 1-bit result
if (valid && (data == 8'hA5))          // CORRECT: use logical &&
$display("guarded check");             // fires correctly
 
// ── BUG 3: Ternary with offset ────────────────────────────────────
result = valid ? sel_a : sel_b + offset;   // BUGGY: valid ? sel_a : (sel_b+offset)
// valid=1: result = sel_a = 0x10 — offset never added!
result = (valid ? sel_a : sel_b) + offset; // CORRECT: 0x10 + 0x05 = 0x15
 
// ── BUG 4: NOT of comparison result ───────────────────────────────
if (!data == 8'h00)                    // BUGGY: (!data) == 8'h00 = 0 == 0 = 1 always!
$display("data is zero");              // fires even when data=0xA5
if (!(data == 8'h00))                  // CORRECT: negate the equality result
$display("data is non-zero");          // fires when data != 0
// !data = logical not = 1'b0 when data is non-zero; then 0 == 8'h00 → always 1
 
// ── BUG 5: Shift in constraint arithmetic ─────────────────────────
logic [7:0] base = 8'd4;
result = base + 2 << 3;               // BUGGY intent: base + (2<<3) = 4+16 = 20
// Parsed: (base + 2) << 3 = 6 << 3 = 48
result = base + (2 << 3);             // CORRECT: 4 + 16 = 20

Interview Questions

Beginner Level

Q1: How is a & b == c parsed in SystemVerilog? As a & (b == c). Equality == has higher precedence than bitwise AND & in SystemVerilog. This is the opposite of C, where & has higher precedence than ==. To get the intended (a & b) == c, you must add explicit parentheses. Q2: Is the conditional operator ?: left or right associative, and what does that mean for a ? b : c ? d : e? Right-associative. a ? b : c ? d : e is parsed as a ? b : (c ? d : e). The second ternary is the "false" branch of the first. This allows natural chaining: if a is true, return b; else evaluate the inner conditional. This is how priority chains are built with ternary operators.

Intermediate Level

Q3: What is the result of !data == 8'h00 when data = 8'hA5? 1 — and it fires even though data is not zero. Parsed as (!data) == 8'h00. Unary ! has higher precedence than ==. !8'hA5 = logical NOT of a non-zero value = 1'b0. Then 1'b0 == 8'h00 — the 1-bit 0 is zero-extended to 8'h00, which equals 8'h00 → result is 1. The correct form is !(data == 8'h00). Q4: In an RTL always block, what is the precedence risk in if (err_flag | timeout_flag && ~reset_n)?&& binds tighter than |, so this is parsed as err_flag | (timeout_flag && ~reset_n). If the intent was "either error flag or timeout flag, both gated by not-reset," the correct form is (err_flag | timeout_flag) && ~reset_n. As written, err_flag is not gated by reset at all — the error flag alone would trigger the block even during reset, which is typically a bug.

Experienced Engineer Level

Q5: A scoreboard has if (got & mask == expected) for checking masked fields. It passes all tests but the DUT has a field-packing bug. Why might this check never catch it? Because got & (mask == expected) is what executes. mask == expected evaluates first, producing a 1-bit 0 or 1. Then got & 1'b0 = 8'h00 or got & 1'b1 = got[0]. Either way, the masked comparison is never performed — the check tests one bit of got against 0 or 1, not the masked field against the expected value. The DUT can output any corrupted value in the masked field and this check will not catch it. Fix: if ((got & mask) == expected).

Best Practices — Writing Unambiguous Expressions

  • Parenthesize any bitwise + equality mix — Any expression mixing &, |, ^ with == or != needs explicit parentheses. Always. No exception.
  • Use && / || for boolean conditions — In if conditions combining multiple signals, use && and ||, not & and |. The precedence of logical operators vs bitwise differs and mixes unexpectedly.
  • Parenthesize ternary when adding/shifting result — sel ? a : b + c is almost never what you want. If you need to operate on the ternary result, always wrap it: (sel ? a : b) + c.
  • Don't rely on precedence — use parentheses — If you have to think about precedence when reading an expression, add parentheses. The compiler generates identical hardware. Future readers will not need to remember the table.
PatternWrite thisNot this
Masked field check(data & mask) == expecteddata & mask == expected
Guard conditionvalid && (data == exp)valid & data == exp
Add to ternary result(sel ? a : b) + csel ? a : b + c
Negate a comparison!(data == 8'h00) or data != 8'h00!data == 8'h00
Multi-flag condition(a_flag || b_flag) && enablea_flag | b_flag && enable
Shift then compare(data >> 4) == 4'hAdata >> 4 == 4'hA — relational binds tighter than shift? No — shift is above relational, so this is actually (data >> 4) == 4'hA correctly. But be explicit anyway.

Summary — The Table You Actually Need to Memorize

You do not need to memorize all sixteen precedence levels. You need to memorize the three non-obvious rules that cause real bugs, and parenthesize everything else when mixing operator types.

RuleCounter-intuitive factSafe pattern
Equality above bitwise== binds tighter than &, |, ^ — opposite of CAlways parenthesize: (a & b) == c
Logical AND above bitwise OR&& binds tighter than |Use || / && for booleans; parenthesize mixed expressions
Conditional is very lowAlmost everything binds tighter than ?:Parenthesize ternary when it appears inside another expression
  • Unary operators first. Sign, logical NOT, bitwise NOT, reduction, increment — all bind before any binary operator.
  • Arithmetic before shift before relational before equality. These follow standard mathematical intuition.
  • Equality is above all bitwise binary operators. This is the SystemVerilog-specific trap. Memorize it.
  • Bitwise AND → XOR → OR → Logical AND → Logical OR — that exact order from high to low within the bitwise/logical group.
  • Conditional ?: is right-associative and near the bottom. Nested ternaries chain right-to-left naturally.
  • When in doubt, parenthesize. It costs nothing in hardware and everything in readability.