SystemVerilog Series — SV-07b: Real Operators, Size, Sign, Precedence & Built-in Methods — VLSI Trainers
VLSI Trainers SV Series · 12 / 44
SystemVerilog Series · SV-07b

Real Operators, Size, Sign, Precedence & Built-in Methods

How SV handles real and shortreal in expressions, size and sign determination, the full precedence table with new SV operators, and the built-in method system that replaces system tasks for type-specific operations.

🔢 Real & shortreal Operators

SystemVerilog adds shortreal (32-bit IEEE 754 float) as a new type. Both real (64-bit) and shortreal follow the same operator rules, which are a superset of the Verilog real rules.

Operators that work on real / shortreal

Operator groupOperatorsNotes
Arithmetic binary+   –   *   /   **All supported. Division never truncates (unlike integer division).
Arithmetic unary+   –   ++   —++/-- increment or decrement by exactly 1.0, not by 1 ULP.
Compound assignment+=   -=   *=   /=Only these four compound operators. %=, bitwise, and shift are illegal on reals.
Relational<   <=   >   >=Return bit (1 or 0). Comparison follows IEEE 754 rules (NaN always false).
Equality==   !=Return bit. Exact bit-pattern comparison — beware floating-point precision.
Conditional / ternary?:If any branch is real, the result is real. See §Real in Ternary.

Operators NOT available on real / shortreal

// Basic real arithmetic
real      x  = 3.5;
shortreal y  = shortreal'(1.5);

x += 1.0;      // x = 4.5
x++;           // x = 5.5  (increment by 1.0)
x--;           // x = 4.5  (decrement by 1.0)
x *= 2.0;      // x = 9.0
x /= 3.0;      // x = 3.0  (exact real division)

// Accessing real members of structures
struct { real voltage; int node; } measure;
real v = measure.voltage;   // struct member access

// Accessing a real array element
real coeffs[8];
real c0 = coeffs[0];          // array element

// ILLEGAL on real:
// x %= 2.0;     // ERROR — modulo not defined for real
// x &= 8'hFF;  // ERROR — bitwise AND not defined for real
// x << 2;       // ERROR — shift not defined for real
EDARun on EDA Playground
Integer division vs real division: 5 / 2 equals 2 (integer truncation). 5.0 / 2.0 equals 2.5 (real result). 5 / 2.0 equals 2.5 — if either operand is real, the other is implicitly promoted to real. This is the most common source of unexpected results when mixing integer and real arithmetic.

? Real in Ternary & Mixed Expressions

When a real or shortreal value appears anywhere in a binary expression (except as the condition of ?:), it promotes the entire expression result to a floating-point type.

// Result type promotion rules
real      r  = 3.0;
shortreal sr = shortreal'(1.5);
int       i  = 5;

real      a = r  + i;   // real + int → real   (int promoted to real)
shortreal b = sr + i;   // shortreal + int → shortreal
real      c = r  + sr;  // real + shortreal → real (real wins)

// Ternary: condition is NOT promoted — only the branches matter
real val = (i > 3) ? r : 0;  // condition i>3 is integral, OK
                                // 0 is implicitly cast to 0.0 since r is real
real val2= (1.5 > 0) ? r : 0; // condition 1.5>0 is real — this is LEGAL in SV
                                // (SV allows real conditions in ternary)

// Ambiguous X in ternary condition with real branches
logic sel = 1'bx;
real  out = sel ? 3.14 : 2.71;
// Both branches evaluated, result is element-by-element combination.
// For real (singular), if elements differ → returns default (0.0).
// If elements match → returns that value.
EDARun on EDA Playground
Conditional with X/Z on real branches: When the ternary condition evaluates to X or Z, both branches are evaluated and the results are combined element-by-element. For a real result: if the two values match, that value is returned; if they differ, the default uninitialised value (0.0) is returned. This is the same rule as for aggregate types.

↦ Size Determination

The number of bits used to evaluate an expression is determined by the expression’s context following the same rules as Verilog-2001. Understanding size context is essential for avoiding silent bit-truncation bugs and unexpected overflow.

Self-determined vs context-determined

An expression is either self-determined (its size is fixed by the operands themselves, ignoring the destination) or context-determined (the destination forces a width and operands are extended to match).

Self-determined contexts

// Width determined by the operand itself
// — not affected by destination width
4'b1100 >> 1      // 4-bit shift result
4'b1100  & 4'b0011// 4-bit AND
{4{1'b1}}         // 4-bit replication
a[3]              // 1-bit select
int'(expr)         // cast: 32-bit result
EDARun on EDA Playground

Context-determined contexts

// Width set by the assignment destination
// — operands are sign/zero extended to match
logic [15:0] r = 4'hF + 4'hF;
// 4'hF + 4'hF evaluated in 16-bit context
// = 16'h001E (no overflow)

bit [3:0] s = 4'hF + 4'hF;
// 4'hF + 4'hF evaluated in 4-bit context
// = 4'hE (overflow, truncated)
EDARun on EDA Playground

The most dangerous size rule: unsized literals in mixed expressions

// Unsized integers (no prefix) are at least 32 bits.
// They force 32-bit context on the entire expression.

logic [3:0] a = 4'hF;

// What does a + 1 give?
// '1' is unsized (32-bit), so the expression is 32-bit:
logic [3:0] b = a + 1;   // a zero-extended to 32-bit, 0xF + 1 = 0x10
                         // then truncated back to 4 bits: b = 4'h0  (overflow!)

// Safer: use a sized literal
logic [3:0] c = a + 4'h1; // expression in 4-bit context: c = 4'h0 (expected)

// Also safe: explicitly size the destination wider
logic [4:0] d = a + 1;    // 5-bit context: d = 5'h10 = 16 (no overflow)
EDARun on EDA Playground

🔄 Using Casts to Fix Size Context

SystemVerilog adds size casting — N'(expr) — which forces an intermediate value to exactly N bits. This prevents the most common size-mismatch warnings and overflow surprises.

// Problem: 8-bit + 8-bit addition can overflow into a 8-bit result
bit [7:0] a=8'hFF, b=8'h01;
bit [7:0] sum  = a + b;      // 0xFF+0x01=0x100 → truncated to 0x00 (overflow!)

// Solution 1: widen the destination
bit [8:0] sum2 = a + b;       // 9-bit context: sum2 = 9'h100 = 256 (correct)

// Solution 2: use size cast to widen an intermediate value
bit [7:0] sum3 = 9'(a) + b;  // 9'(a) zero-extends a to 9 bits BEFORE adding
                                // result is 9-bit 9'h100, then truncated to 8 bits = 0x00
                                // same problem! — cast must be on destination side

bit [8:0] sum4 = 9'(a + b);   // cast the whole expression to 9 bits — no warning

// Real use case: suppress size-mismatch warnings from tools
wire [7:0] out;
assign out = 8'(in_a + in_b); // explicit 8-bit — no warning even if in_a/b wider

// Sign cast — same size, change sign interpretation
bit [7:0] u  = 8'hFF;        // u = 255 (unsigned)
int        s1 = signed'(u);   // s1 = -1  (8 bits sign-extended to 32)
int        s2 = unsigned'(u); // s2 = 255 (zero-extended to 32)
EDARun on EDA Playground
Size cast vs type cast: 8'(expr) changes only the bit count — the type remains the same (e.g. still logic if the operand was logic). int'(expr) changes the type to int, which also implies a 32-bit width. Use size casts when you want to suppress a specific width warning without changing the underlying type.

± Sign Determination

The signedness rules in SystemVerilog follow Verilog-2001 exactly — not C. This is an important distinction because C’s rules are different.

Core sign rules

// Rule: one unsigned operand forces unsigned result
bit signed [7:0] s  = -1;  // s = 8'hFF (signed -1)
bit         [7:0] u  = 1;   // u = 8'h01 (unsigned)

int r = s + u;   // UNSIGNED addition (u is unsigned → result unsigned)
               // s treated as 8'hFF = 255 (not -1) → r = 256

// Both signed: signed arithmetic
bit signed [7:0] s2 = 1;
int r2 = s + s2; // SIGNED: -1 + 1 = 0

// Part-select always unsigned
int r3 = s[7:0];  // r3 = 255 (unsigned part-select of signed s)

// Sized binary literal: signed only with 's' modifier
bit signed [7:0] t = 8'b1000_0000;  // t = -128 (signed)
bit         [7:0] w = 8'sb1000_0000; // same bit pattern, but literal is signed

// shortreal to int: signed result
shortreal f = -3.7;
int       n = int'(f);   // n = -3  (truncates toward zero, signed)
EDARun on EDA Playground
Verilog sign rules vs C sign rules: In C, if either operand is unsigned, the result is unsigned. In Verilog/SV, if any operand is unsigned, the whole expression becomes unsigned. This is the same rule as Verilog-2001 and is preserved in SV for backward compatibility. The practical consequence: be especially careful when mixing signed and unsigned types in comparisons — -1 > 0u is true in SV (the signed -1 is treated as 0xFFFF…FF which is larger than 0u).

📈 Full Operator Precedence Table

The complete SystemVerilog precedence table from highest (row 1, binds tightest) to lowest (row 17, binds loosest). New SV operators are shown in green.

1
[] :: .
Selection, scope, member
2
() + – ! ~ & ~& | ~| ^ ~^ ^~ ++ — (unary)
Unary, grouping
3
**
Power (right assoc.)
4
*   /   %
Multiply/divide/modulo
5
+   – (binary)
Add/subtract
6
<<   >>   <<<   >>>
Shift
7
<   <=   >   >=   inside   dist
Relational & set ops
8
==   !=   ===   !==   =?=   !?=
Equality (all 3 families)
9
& (binary)
Bitwise AND
10
^   ~^   ^~ (binary)
Bitwise XOR / XNOR
11
| (binary)
Bitwise OR
12
&&
Logical AND
13
||
Logical OR
14
?: (ternary)
Conditional (right assoc.)
15
-> (implication)
Constraint implication
16
= += -= *= /= %= &= |= ^= <<= >>= <<<= >>>= <= (NBA) := :/ {} {{}}
Assignment & concat. (none)

Green = new in SV • Row 1 = highest precedence • Row 16 = lowest

⚠ Precedence Gotchas — Examples with Explanations

The following are the most commonly encountered precedence surprises in practice.

// ── 1. Power (**) binds tighter than unary minus ──────────────
int a = -2**4;         // parsed as -(2**4) = -16,   NOT (-2)**4 = 16
int b = (-2)**4;        // explicit parens: b = 16

// ── 2. Addition (row 5) binds tighter than shift (row 6) ──────
//    YES, that is HIGHER precedence: + before <
logic [7:0] r = 8'h01 << 1 + 2; // = 8'h01 << (1+2) = 8'h01 << 3 = 8'h08
logic [7:0] s = (8'h01 << 1) + 2; // = 8'h02 + 2 = 8'h04

// ── 3. Bitwise AND (row 9) is higher than == (row 8) ─────────
//    Wait, that's backwards from intuition!
//    == is row 8, bitwise & is row 9 → & is LOWER than ==
bit x  = (4 == 4) & 1; // = 1 & 1 = 1  (== first, then &)
bit y  = 4 & (4 == 4); // = 4 & 1 = 0  (explicit parens)

// ── 4. Logical && (row 12) vs bitwise & (row 9) ───────────────
//    && has LOWER precedence than bitwise &
bit z  = 3 & 2 && 1;  // = (3&2) && 1 = 2 && 1 = 1
bit z2 = 3 & (2 && 1); // = 3 & 1 = 1  (same result here but different meaning)

// ── 5. Ternary is right-associative ──────────────────────────
int r2 = (1)?2:(0)?3:4;  // = 2  (first true → 2, nested ternary not reached)
int r3 = (0)?2:(1)?3:4;  // = 3  (first false → evaluate (1)?3:4 → 3)

// ── 6. inside is at row 7 — same as relational operators ──────
bit m = a + 1 inside {[0:10]};  // = (a+1) inside {[0:10]}  — + before inside
bit n = a > 5 && a inside {[0:10]};
// = (a>5) && (a inside {[0:10]})  — relational and inside at same row,
//   then && (row 12)
EDARun on EDA Playground

🔨 Why Built-in Methods?

Before SystemVerilog, language extensions were added as $system_tasks. The problem: system tasks are global, must be prefixed with $, and users cannot redefine them. They also don’t communicate which data type they operate on.

SystemVerilog introduces a built-in method system where type-specific operations are called via the dot-notation: object.method(). This approach is cleaner, more discoverable, and naturally communicates the data type the operation applies to.

Old style: $system_task

// Hard to know what type each applies to
// All look identical in code
$size(dyn_array)         // dynamic array size
$size(assoc_array)       // associative num? different!
$size(some_string)       // string length? confusing
$bits(expr)              // bit width of type
EDARun on EDA Playground

New style: built-in method

// Method clearly shows what data type it operates on
dyn_array.size()        // this is a dynamic array size
assoc_array.num()       // associative: count of entries
some_string.len()       // string: character count
$bits(expr)              // $bits is still a system function
EDARun on EDA Playground

The design principle

All built-in methods by type

Dynamic array
methods
size() — element count
delete() — free storage
Associative array
methods
num() exists(k) delete([k])
first(ref) last(ref) next(ref) prev(ref)
Queue
methods
size() insert(i,v) delete(i)
push_front(v) push_back(v)
pop_front() pop_back()
string
methods
len() putc(i,c) getc(i) toupper() tolower() compare(s) icompare(s) substr(i,j) atoi() atoreal() itoa(i) realtoa(r)
Unpacked arrays
manipulation methods
find find_index find_first find_last find_*_index
min max unique unique_index
sort rsort reverse shuffle
sum product and or xor
Enum
methods
first() last() next([N]) prev([N])
num() name()

📚 The Built-in Package (std)

SystemVerilog provides a built-in package named std that contains system-provided types, variables, tasks and functions. It is implicitly wildcard-imported into every compilation unit — you never need to manually import it.

// The built-in package is automatically available everywhere:
// No import needed — these work in any module, interface, class

// System types from std (e.g. process, semaphore, mailbox)
semaphore  sem  = new(1);
mailbox    mb   = new();
process    p;

// Access names unambiguously via std:: qualifier
// if a user-defined name conflicts with the built-in
std::semaphore safe_sem = new(1);  // unambiguous reference

// Call system-provided functions without the $ prefix:
// (unlike traditional system tasks, these don't need $)
std::some_system_fn();  // explicit std:: scope
EDARun on EDA Playground
std:: vs $: System tasks and functions that existed in Verilog-2001 keep the $ prefix ($display, $time, $random, $finish, etc.). New types and functions added by SystemVerilog through the built-in package do not need $ — they are accessible directly or via std::. The std:: prefix is only needed when a user-defined name would otherwise shadow a built-in name.

📋 Quick Reference

Real / shortreal operator support

CategorySupported on realNOT supported
Arithmetic+   –   *   /   **   ++   —%
Compound assign+=   -=   *=   /=%= &= |= ^= <<= >>=
Comparison< <= > >= == !==== !== =?= !?=
Bitwise / shiftNone& | ^ ~ << >> <<< >>>

Size and sign rules to remember

Built-in method vs system task — decision rule

Coming next: SV-07c covers the remaining operator topics — concatenation enhancements, unpacked array expressions, structure expressions, tagged union expressions, the inside set-membership operator, streaming operators, and operator overloading (sections 7.12–7.20).
Operators & Expressions☰ SV Series IndexConcatenation, Array & Struct Expressions
Scroll to Top