SYSTEMVERILOG SERIES · SV-07B

SystemVerilog Series — SV-07b: Real Operators, Size, Sign, Precedence & Built-in Methods — VLSI Trainers
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

  • Bitwise: &, |, ^, ~, ~^, ^~
  • Reduction: &, |, ^ (unary)
  • Shift: <<, >>, <<<, >>>
  • Modulo: %
  • Case equality: ===, !==
  • Wild equality: =?=, !?=
  • Compound bitwise/shift: %=, &=, |=, ^=, <<=, >>=
// 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
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.
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

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)

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)

🔄 Using Casts to Fix Size Context

SystemVerilog adds size castingN'(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)
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

  • An expression is signed only if all operands are signed. One unsigned operand makes the whole expression unsigned.
  • Unsized literals are signed integers. Sized literals are unsigned unless the s modifier is used (e.g. 4'sb1010).
  • A part-select is always unsigned, even from a signed variable.
  • A function return value is signed only if the function return type is declared signed.
  • A shortreal converted to an integer by type coercion is treated as signed.
// 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)
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)

🔨 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

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

The design principle

  • Use a built-in method when the operation applies to a specific data type and the data is naturally the object of the call.
  • Use a system task/function ($) when the operation has side-effects with no specific data type, or operates on no data (e.g. $stop, $random, $time).
  • Built-in methods cannot be redefined by users via PLI — only things the user should not override are candidates for built-in methods.
  • When no arguments are needed, the empty parentheses () are optional: arr.size and arr.size() are both legal.

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
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 / shift None & | ^ ~ << >> <<< >>>

Size and sign rules to remember

  • If the destination is wider than the expression — silent zero-extension (or sign-extension for signed).
  • If the destination is narrower — silent truncation (MSBs lost); tools may warn.
  • Unsized decimal literals are at least 32 bits — they silently promote the whole expression to 32 bits.
  • Use size cast N'(expr) to explicitly set intermediate bit width and suppress warnings.
  • One unsigned operand → entire expression is unsigned (Verilog rule, not C rule).
  • Part-selects are always unsigned, even from a signed variable.
  • shortreal coerced to integer gives a signed result.

Built-in method vs system task — decision rule

  • Method (obj.method()): operation is specific to a data type, has no meaningful side-effects, and the data is the natural subject of the call.
  • System task ($task()): operates on no specific data type, has side-effects, or existed in Verilog-2001.
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).

Leave a Comment

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

Scroll to Top