SystemVerilog Series — SV-07a: Operators & Expressions — VLSI Trainers
VLSI Trainers SV Series · 11 / 44
SystemVerilog Series · SV-07a

Operators & Expressions

New assignment operators, increment/decrement, the three equality operator families, wildcard comparisons, type rules for mixed logic/bit expressions, and operator precedence — all the SV additions to Verilog-2001 operators explained with worked examples.

💡 What SV Adds to Verilog Operators

Verilog-2001 had a complete operator set for hardware description, but was missing several conveniences common in C. SystemVerilog adds them without removing anything from Verilog-2001.

Verilog-2001 — what was missing

  • No compound assignment: a = a + 1 every time
  • No increment/decrement: i = i + 1 in every loop
  • Only ==/=== — no wildcard comparison
  • No way to match X/Z bits as wildcards without casex
  • Mixed logic/bit type results undefined

SystemVerilog additions

  • 13 compound assignment operators: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, <<<=, >>>=
  • 4 increment/decrement forms: ++i, --i, i++, i--
  • 2 wildcard operators: =?=, !?=
  • Defined result types for mixed logic/bit, int/integer
  • Assignment usable as an expression (like C)

Assignment Operators

SystemVerilog adds twelve compound assignment operators. Each is semantically equivalent to a blocking assignment of the form a = a op b, with one important difference: the left-hand side index expression is evaluated only once.

+=
Add-assign
a += ba = a + b
-=
Subtract-assign
a -= ba = a - b
*=
Multiply-assign
a *= ba = a * b
/=
Divide-assign
a /= ba = a / b
%=
Modulo-assign
a %= ba = a % b
&=
AND-assign
a &= ba = a & b
|=
OR-assign
a |= ba = a | b
^=
XOR-assign
a ^= ba = a ^ b
<<=
Left-shift-assign
a <<= ba = a << b
>>=
Right-shift-assign
a >>= ba = a >> b
<<<=
Arith left-shift-assign
a <<<= ba = a <<< b
>>>=
Arith right-shift-assign
a >>>= ba = a >>> b

The single-evaluation difference

The key advantage over writing out a[i] = a[i] + 2 is that the index expression i is evaluated exactly once. If evaluating i has a side-effect (like a function call), it only happens once.

// Standard Verilog style — i[expr] evaluated twice
a[get_idx()] = a[get_idx()] + 2;  // get_idx() called TWICE — may return different values!

// SystemVerilog compound operator — i[expr] evaluated once
a[get_idx()] += 2;               // get_idx() called ONCE — safe

// Common RTL and testbench patterns
logic [7:0] cnt = 8'h00;
cnt += 1;          // increment counter
cnt -= 4;          // decrement by 4
cnt <<= 2;         // shift left 2 bits (multiply by 4)
cnt &= 8'hF0;      // mask lower nibble
cnt |= 8'h01;      // set LSB
cnt ^= 8'hFF;      // bitwise invert

// Signed arithmetic shift — preserves sign bit
int s = -128;
s >>>= 1;          // s = -64 (arithmetic right shift, sign extended)
s >>= 1;           // s = 0x7F...C0 (logical shift, no sign extension)
Assignment operators act as blocking assignments. They cannot appear in non-blocking assignment contexts (<=), in event expressions (@(a += 1) is illegal), or in continuous assignment statements (assign a += b is illegal). They are purely procedural.

++ Increment & Decrement

SystemVerilog adds the C-style ++ and -- operators in both prefix and postfix forms. All four forms behave as blocking assignments.

Prefix: ++ / — (evaluate AFTER change)

int i = 5;
int j = ++i;  // i incremented first → i=6, j=6
int k = --i;  // i decremented first → i=5, k=5

// Typical use: loop control
for(int n=0; n<8; ++n)
  arr[n] = n;

Postfix: ++ / — (evaluate BEFORE change)

int i = 5;
int j = i++;  // j gets old i=5, then i=6
int k = i--;  // k gets old i=6, then i=5

// Typical use: index and advance
write(fifo[wr_ptr++]);  // write then advance ptr
data = read(rd_ptr--);  // read then retreat ptr

Four forms at a glance

FormNameWhat is returnedEffect on operand
++iPre-incrementValue of i after adding 1i = i + 1
–iPre-decrementValue of i after subtracting 1i = i – 1
i++Post-incrementValue of i before adding 1i = i + 1
i–Post-decrementValue of i before subtracting 1i = i – 1
Undefined evaluation order warning. When a variable is both written by ++/-- and read in the same expression, the result is undefined. Different simulators may produce different values:

i = 10; j = i++ + (i = i - 1);

After this, j can be 18, 19, or 20 depending on the order the simulator evaluates the sub-expressions. Tools may warn about this. Never mix ++/-- with other reads of the same variable in one expression.

Increment/decrement on real types

// ++ and -- work on real and shortreal — increment by 1.0
real r = 3.14;
r++;              // r = 4.14  (increments by 1.0, not by 1 ULP)
r--;              // r = 3.14

shortreal sf = 0.5;
sf += 1.5;        // shortreal supports +=, -=, *=, /=

📄 Assignment as an Expression

In SystemVerilog, an assignment can be used as an expression (just like in C), as long as it does not contain a timing control and is enclosed in parentheses. The value returned is the value that was assigned, with the type of the left-hand side.

// Assignment in an if condition (must be parenthesised)
if ((a = b))           // a gets b, then if tests whether a is non-zero
  b = (a += 1);        // a incremented, b gets the result (a+1)

// Chained assignment (C-style)
a = (b = (c = 5));    // c=5, b=5, a=5  (evaluated right-to-left)

// Returned type is the type of the LEFT-HAND SIDE
byte x; int y;
y = (x = 300);   // x = byte'(300) = 44 (truncated)
                  // y = 44 (type of x, which is byte — then widened to int)
Where assignment-as-expression is forbidden:
  • In an event expression: @(a += 1) — illegal
  • Inside a procedural continuous assignment: assign a = (b += c) — illegal
  • In any expression that is not inside a procedural statement

Operations on logic and bit Types

When you mix 4-state (logic, integer) and 2-state (bit, int) operands in one expression, SystemVerilog defines the result type precisely.

Type promotion rules for binary operators
Operand AOperand BResult typeWhy
bitlogiclogic4-state wins — X/Z possible in result
bitbitbitBoth 2-state — result is 2-state
logiclogiclogicBoth 4-state — result is 4-state
intintegerinteger4-state (integer) wins over 2-state (int)
// Mixed operands: result type follows the promotion rules
bit   [7:0] b  = 8'hAA;
logic [7:0] l  = 8'hxx;   // contains X bits

bit   [7:0] rb  = b & b;   // bit & bit → bit  : rb = 8'hAA
logic [7:0] rl  = b & l;   // bit & logic → logic: rl = 8'hxx&8'hAA = 8'h0x (mixed)

// == and != with X/Z — returns X, not 0 or 1
logic cmp;
cmp = (l == 8'hAA);   // cmp = X  (l has X bits → == returns X)

// But if assigned to bit or used in if: X becomes 0
bit cmp_b = (l == 8'hAA); // cmp_b = 0  (X converted to 0 on assignment to bit)
if (l == 8'hAA) ...         // false (X treated as false in if)
X in a condition is always false: If == or != returns X because an operand contains X or Z, and that X result is used in a boolean context (if, while, ?:), the X is treated as 0 (false). This means a comparison involving X always takes the else branch — which may or may not be the correct behaviour. Use === if you need to explicitly check whether a value equals X.

& Unary Reduction Operators & Return Type

The unary reduction operators (&, ~&, |, ~|, ^, ~^) reduce a multi-bit packed expression to a single bit. The return type depends on whether the operand is 2-state or 4-state.

// 2-state operand → result type is bit
int  i   = 32'hDEAD_BEEF;
bit  b   = &i;       // AND-reduction of int → bit (2-state result)
bit  p   = ^i;       // XOR-reduction (parity) → bit
bit  any = |i;       // OR-reduction (any bit set?) → bit

// 4-state operand → result type is logic
integer j   = 32'hDEAD_BEEF;
logic   c   = &j;    // AND-reduction of integer → logic (can be X)
logic   q   = ^j;    // parity → logic

// Logic with X bits — reduction can produce X
logic [7:0] bus = 8'b1010_x101; // one X bit
logic all1 = &bus;               // &: 1&1&X&... = X  (unknown)
logic any1 = |bus;               // |: 1|0|1... = 1  (known — at least one 1)
logic par  = ^bus;               // ^: parity with X → X

// Practical: check all-zeros / any-set with reduction
bit [7:0] flags;
if (!|flags)          // no flags set?
  $display("all clear");
bit parity = ^flags;  // XOR parity of all 8 bits

= The Three Equality Families

SystemVerilog has three different equality operators, each handling X and Z values differently. Choosing the wrong one is one of the most common sources of simulation/synthesis discrepancies.

OperatorName0 vs 01 vs 10 vs 1X vs anythingZ vs anythingResult type
==Logical equality110XXlogic
!=Logical inequality001XXlogic
===Case equality (4-state)110X===X → 1Z===Z → 1bit
!==Case inequality001X!==0 → 1Z!==0 → 1bit
=?=Wild equality (SV new)110X on RHS → wildcard 1Z on RHS → wildcard 1bit
!?=Wild inequality (SV new)001X on RHS → wildcard 0Z on RHS → wildcard 0bit
Key differences at a glance: == — can return X; X/Z operands produce unknown result.
=== — always returns 0 or 1; X and Z are treated as exact bit values to match.
=?= — always returns 0 or 1; X and Z in the right-hand operand act as wildcards matching any bit.
logic [3:0] a = 4'b1X10;

// == : returns X because a has X bits
logic eq1  = (a == 4'b1010);     // X  (unknown)
logic eq2  = (a == 4'b1110);     // X  (unknown)

// === : exact 4-state comparison — X and Z must match exactly
bit ceq1  = (a === 4'b1X10);    // 1  (exact X match)
bit ceq2  = (a === 4'b1010);    // 0  (X ≠ 0)

// =?= : X and Z on RHS are wildcards matching any value
bit weq1  = (a =?= 4'b1X10);   // 1  (X on RHS matches 1 in a[2])
bit weq2  = (a =?= 4'b1Z10);   // 1  (Z on RHS matches 1 in a[2])
bit weq3  = (a =?= 4'bXXXX);   // 1  (all X on RHS match anything)
bit weq4  = (a =?= 4'b0X10);   // 0  (MSB 1≠0 — mismatch on bit 3)

Wild Equality & Inequality in Depth

The =?= and !?= operators are the programmatic equivalent of casex/casez comparisons — without the synthesis risks of those statements. They are especially useful in testbench code for checking that a response matches a mask pattern where some bits are “don’t care”.

=?= wild equality

// X or Z in the RIGHT operand = wildcard
// matches any value (0, 1, X, Z) in the LEFT
bit r;
r = (4'b1010 =?= 4'b10X0); // 1 (X matches 1)
r = (4'b1010 =?= 4'b10Z0); // 1 (Z matches 1)
r = (4'b1010 =?= 4'b1000); // 0 (bit2: 1≠0)
r = (4'b1X10 =?= 4'b1010); // X (LHS has X, RHS is 0 — no wildcard)
r = (4'b1X10 =?= 4'b1X10); // 1 (X on RHS wildcard-matches X on LHS)

!?= wild inequality

// Logical negation of =?=
bit r;
r = (4'b1010 !?= 4'b10X0); // 0 (wildcard matched → NOT unequal)
r = (4'b1010 !?= 4'b1000); // 1 (no wildcard — 1010 ≠ 1000)
r = (4'b1010 !?= 4'bXXXX); // 0 (all wildcards → always equal)

Wild equality vs casex/casez — why =?= is preferred

// casex can accidentally treat X in the SUBJECT as wildcard (simulation hazard)
// =?= only treats X/Z in the RIGHT operand as wildcard
// The subject (left side) is compared exactly, giving predictable simulation

// Pattern: check that a DUT response matches a mask
// Bits set to X in the mask are "don't-care" positions
function automatic bit matches_mask(
  logic [7:0] actual,
  logic [7:0] mask    // X = don't care, 0/1 = must match
);
  return (actual =?= mask);
endfunction

// Check various DUT outputs against a mask
matches_mask(8'b1010_0011, 8'b10XX_00X1)  // 1 — X positions match anything
matches_mask(8'b1010_0001, 8'b10XX_00X1)  // 1
matches_mask(8'b0010_0011, 8'b10XX_00X1)  // 0 — MSB 0≠1
Wild equality never returns X. Both =?= and !?= always return 0 or 1 — never X. This is the same guarantee as ===/!==. For testbench code comparing DUT outputs, this makes the result always usable in if statements without worrying about X propagation masking a bug.

Unequal operand widths

// When operands differ in width, the shorter one is extended
// using the same rules as === / !==:
// — zero-extended for unsigned types
// — sign-extended for signed types
bit r;
r = (4'b1010 =?= 6'bXX1010); // 4'b1010 zero-extends to 6'b001010
                                // 001010 =?= XX1010 → 1 (Xs match leading 0s)

📈 Operator Precedence Table

Operator precedence determines which operation is performed first when there are no explicit parentheses. Higher precedence binds more tightly. When two operators have equal precedence, associativity determines the order (left = left-to-right, right = right-to-left).

PrecedenceOperatorsAssoc.Category
Highest[] :: .leftSelection, scope, member access
() ! ~ & ~& | ~| ^ ~^ ^~ ++ — (unary) + – (unary)rightUnary operators, grouping
**leftPower
* / %leftMultiply, divide, modulo
+ – (binary)leftAdd, subtract
<< >> <<< >>>leftShift operators
< <= > >= inside distleftRelational & set membership
== != === !== =?= !?=leftEquality operators (all three families)
& (binary)leftBitwise AND
^ ~^ ^~ (binary)leftBitwise XOR / XNOR
| (binary)leftBitwise OR
&&leftLogical AND
||leftLogical OR
?: (conditional)rightTernary
->rightImplication (constraint expressions)
Lowest= += -= *= /= %= &= |= ^= <<= >>= <<<= >>>= <= (NBA) := :/ {} {{}}noneAssignment operators, non-blocking, concatenation

Green = new in SystemVerilog. Green row = highest precedence. Red row = lowest.

Practical precedence gotchas

// Unary minus vs power: -2**4 = -(2**4) = -16, NOT (-2)**4 = 16
int a = -2**4;      // a = -16  (** binds tighter than unary -)
int b = (-2)**4;    // b = 16   (parentheses force the grouping)

// Bitwise vs logical: & vs &&
bit x = (4 & 3) == 0;  // 4&3=0, 0==0=1: x=1  (& higher than ==)
bit y = 4 & (3 == 0);  // 3==0=0, 4&0=0: y=0  (explicit parens)

// Addition before shift: a << 1 + 2 = a << 3, NOT (a<<1)+2
logic [7:0] r = 8'h01 << 1 + 2; // = 8'h01 << 3 = 8'h08  (+ before <<)

// Ternary is right-associative: a?b:c?d:e = a?b:(c?d:e)
int z = (1)?2:(0)?3:4;   // z = 2  (first condition true)

📋 Quick Reference

Assignment operators — all 12

// Arithmetic    a+=b  a-=b  a*=b  a/=b  a%=b
// Bitwise       a&=b  a|=b  a^=b
// Shift         a<<=b  a>>=b  a<<<=b  a>>>=b
// Inc/dec        ++i  --i  i++  i--

Equality operator decision guide

Type result rules

Coming next: SV-07b covers the remaining operator topics — real operators, size and sign rules, operator precedence edge cases, concatenation enhancements, and unpacked array expressions (sections 7.6–7.14).
Attributes☰ SV Series IndexReal Operators, Size, Sign & Precedence
Scroll to Top