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.
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.
| 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. |
&, |, ^, ~, ~^, ^~&, |, ^ (unary)<<, >>, <<<, >>>%===, !===?=, !?=%=, &=, |=, ^=, <<=, >>=// 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.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.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.
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).
// 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
// 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)
// 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)
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.The signedness rules in SystemVerilog follow Verilog-2001 exactly — not C. This is an important distinction because C’s rules are different.
s modifier is used (e.g. 4'sb1010).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)
-1 > 0u is true in SV (the signed -1 is treated as 0xFFFF…FF which is larger than 0u).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
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)
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.
// 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
// 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
$stop, $random, $time).() are optional: arr.size and arr.size() are both legal.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()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.| Category | Supported on real | NOT supported |
|---|---|---|
| Arithmetic | + – * / ** ++ — | % |
| Compound assign | += -= *= /= | %= &= |= ^= <<= >>= |
| Comparison | < <= > >= == != | === !== =?= !?= |
| Bitwise / shift | None | & | ^ ~ << >> <<< >>> |
N'(expr) to explicitly set intermediate bit width and suppress warnings.shortreal coerced to integer gives a signed result.obj.method()): operation is specific to a data type, has no meaningful side-effects, and the data is the natural subject of the call.$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).