SYSTEMVERILOG SERIES · SV-07A

SystemVerilog Series — SV-07a: Operators & Expressions — VLSI Trainers
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-increment Value of i after adding 1i = i + 1
–iPre-decrement Value of i after subtracting 1i = i – 1
i++Post-increment Value of i before adding 1i = i + 1
i–Post-decrement Value 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
bitlogic logic 4-state wins — X/Z possible in result
bitbit bit Both 2-state — result is 2-state
logiclogic logic Both 4-state — result is 4-state
intinteger integer 4-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.

Operator Name 0 vs 0 1 vs 1 0 vs 1 X vs anything Z vs anything Result type
==Logical equality 110 XX logic
!=Logical inequality 001 XX logic
===Case equality (4-state) 110 X===X → 1Z===Z → 1 bit
!==Case inequality 001 X!==0 → 1Z!==0 → 1 bit
=?=Wild equality (SV new) 110 X on RHS → wildcard 1Z on RHS → wildcard 1 bit
!?=Wild inequality (SV new) 001 X on RHS → wildcard 0Z on RHS → wildcard 0 bit
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 [] :: . left Selection, scope, member access
() ! ~ & ~& | ~| ^ ~^ ^~ ++ — (unary) + – (unary) right Unary operators, grouping
** left Power
* / % left Multiply, divide, modulo
+ – (binary) left Add, subtract
<< >> <<< >>> left Shift operators
< <= > >= inside dist left Relational & set membership
== != === !== =?= !?= left Equality operators (all three families)
& (binary) left Bitwise AND
^ ~^ ^~ (binary) left Bitwise XOR / XNOR
| (binary) left Bitwise OR
&& left Logical AND
|| left Logical OR
?: (conditional) right Ternary
-> right Implication (constraint expressions)
Lowest = += -= *= /= %= &= |= ^= <<= >>= <<<= >>>= <= (NBA) := :/ {} {{}} none Assignment 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

  • Use == / != for RTL — realistic simulation (X propagates, reveals uninitialised logic).
  • Use === / !== in testbenches when you need to check whether a signal is exactly X or Z.
  • Use =?= / !?= in testbenches when you need “don’t-care” bit positions — safer than casex.

Type result rules

  • bit op logic → result is logic (4-state wins).
  • int op integer → result is integer (4-state wins).
  • Unary reduction of 2-state packed → bit. Of 4-state packed → logic.
  • == / != return logic (can be X). === / !== / =?= / !?= return bit (always 0 or 1).
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).

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top