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 group | Operators | Notes |
|---|---|---|
| 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
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.
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 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)
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
smodifier 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
shortrealconverted 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)
-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.
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.sizeandarr.size()are both legal.
All built-in methods by type
size() — element countdelete() — free storagenum() exists(k) delete([k])first(ref) last(ref) next(ref) prev(ref)size() insert(i,v) delete(i)push_front(v) push_back(v)pop_front() pop_back()len() putc(i,c) getc(i) toupper() tolower() compare(s) icompare(s) substr(i,j) atoi() atoreal() itoa(i) realtoa(r)find find_index find_first find_last find_*_indexmin max unique unique_indexsort rsort reverse shufflesum product and or xorfirst() 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
$ 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
| Category | Supported on real | NOT 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.
shortrealcoerced 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.
inside set-membership operator, streaming operators, and operator overloading (sections 7.12–7.20).
