Real & Shortreal Types
IEEE 754, precision, epsilon comparison, $realtobits, timing calculations.
Module 2 · Page 2.3
Where Floating Point Actually Appears in Verification
Most digital verification engineers go months without thinking about floating-point types. Then they hit one of four specific scenarios: timing calculations that use fractional nanoseconds, signal integrity checks with voltage thresholds, coverage weight computations, or analog/mixed-signal verification where the DUT output is a real-valued voltage or current. In each of these cases, integer arithmetic loses precision in ways that matter.
SystemVerilog's real is a 64-bit IEEE 754 double-precision float — the same as C's double. shortreal is 32-bit IEEE 754 single-precision — C's float. Both are 2-state (no X, no Z), both initialize to 0.0, and both are strictly simulation constructs. No synthesis tool will accept them.
The bug every engineer hits eventually: writing if (r == 0.1) and having it fail even though the variable was just assigned 0.1. This is not a SystemVerilog bug — it's the fundamental reality of floating-point representation. 0.1 cannot be represented exactly in binary. The variable holds the nearest representable value, and the literal 0.1 in the comparison holds the nearest representable value to the nearest representable value — they may differ by one ULP (unit in the last place). Use epsilon-based comparisons.
IEEE 754 — What You Actually Need to Know
Every real value is stored as three fields: a sign bit, an 11-bit exponent, and a 52-bit mantissa (for 64-bit double). The value represented is approximately sign × 2^exponent × mantissa. Most decimal fractions — 0.1, 0.3, 0.7 — cannot be represented exactly in this format. The computer stores the closest binary fraction it can. When you write real r = 0.1, r holds 0.1000000000000000055511151231257827021181583404541015625, not exactly 0.1.
In practice for verification, this means: use real for calculations where fractional precision matters (timing, voltage), store results in real variables, and compare with epsilon tolerance. For protocol field values, addresses, and anything that's inherently integer, stick to integer types.
- real — 64-bit double — IEEE 754 double precision. ~15–17 significant decimal digits. Range: ±1.8×10³⁰⁸. The default floating-point type in SV. Use for timing, voltage, probability calculations.
- shortreal — 32-bit float — IEEE 754 single precision. ~6–9 significant decimal digits. Range: ±3.4×10³⁸. Less memory per element in large arrays. Use when 6 digits of precision is enough.
- Simulation-only — Neither type is synthesizable. Synthesis tools reject real outright. Keep them strictly in testbench, UVM components, and simulation utilities.
- 2-state, init to 0.0 — No X or Z. Uninitialized real starts at 0.0 — unlike logic. Cannot be randomized with rand keyword.
Syntax, Conversions, and System Functions
// ── Declarations ─────────────────────────────────────────────────
real freq_ghz = 2.4; // 64-bit double — initializes to 0.0 if not set
real voltage = 1.8; // VDD
real pi = 3.14159265358979;
shortreal approx = 3.14159; // 32-bit float — less precision
// ── Arithmetic: all standard operators ───────────────────────────
real period_ns = 1.0 / freq_ghz; // 0.41667 ns
real half_vdd = voltage / 2.0; // 0.9 V
// ── Math functions (built-in) ─────────────────────────────────────
$sqrt(2.0) // 1.41421356...
$pow(2.0, 10.0) // 1024.0
$log(10.0) // natural log: 2.302585...
$exp(1.0) // e^1 = 2.71828...
$abs(-3.14) // 3.14
$floor(3.7) // 3.0
$ceil(3.2) // 4.0
$sin(0.0) // 0.0 (radians)
$cos(0.0) // 1.0
// ── Conversions between real and integer ─────────────────────────
real r = 3.9;
int i = int'(r); // truncate: i = 3 (not rounded)
int j = int'($round(r)); // rounded: j = 4
real back = real'(i); // int → real: 3.0
// ── Bit-level access — for passing real through logic ports ───────
logic [63:0] bits;
real val = 1.5;
bits = $realtobits(val); // real → 64-bit pattern
val = $bitstoreal(bits); // 64-bit pattern → real
logic [31:0] sbits;
shortreal sval = 1.5;
sbits = $shortrealtobits(sval); // shortreal → 32-bit
sval = $bitstoshortreal(sbits); // 32-bit → shortreal
// ── CORRECT comparison: epsilon-based ─────────────────────────────
parameter real EPSILON = 1e-9;
function automatic bit real_eq(real a, b, real eps = EPSILON);
return ($abs(a - b) < eps);
endfunction| Property | real (double) | shortreal (float) |
|---|---|---|
| IEEE 754 standard | 64-bit double precision | 32-bit single precision |
| Significant decimal digits | ~15–17 | ~6–9 |
| Exponent bits | 11 | 8 |
| Mantissa bits | 52 | 23 |
| Max positive value | ~1.8×10³⁰⁸ | ~3.4×10³⁸ |
| Smallest positive normal | ~2.2×10⁻³⁰⁸ | ~1.2×10⁻³⁸ |
| Init value | 0.0 | 0.0 |
| X/Z possible | No | No |
| Synthesizable | No | No |
| rand keyword | Not supported | Not supported |
| Bit conversion | $realtobits / $bitstoreal | $shortrealtobits / $bitstoshortreal |
Visual — IEEE 754 Layout and Precision Limits
IEEE 754 Double Precision Bit Layout (real)
| Bit 63 | Bits 62–52 | Bits 51–0 |
|---|---|---|
| Sign 0 = positive 1 = negative | Exponent (11 bits) Biased by 1023 Range: 2⁻¹⁰²² to 2¹⁰²³ | Mantissa / Fraction (52 bits) Implicit leading 1 ~15 decimal digits of precision |
The Floating-Point Equality Problem — Why 0.1 ≠ 0.1
| Operation | Exact mathematical result | real result | Difference |
|---|---|---|---|
0.1 + 0.2 | 0.3 | 0.30000000000000004 | ~4.4×10⁻¹⁷ |
0.1 * 3 | 0.3 | 0.30000000000000004 | ~4.4×10⁻¹⁷ |
1.0 / 3.0 * 3.0 | 1.0 | 1.0 (exact in this case) | 0 |
$sqrt(2.0) * $sqrt(2.0) | 2.0 | 2.0000000000000004 | ~4.4×10⁻¹⁶ |
This is why 0.1 + 0.2 == 0.3 evaluates to false in most languages, including SystemVerilog. The values are close but not identical at the binary level.
real vs shortreal Precision Loss
| Value | real (64-bit) | shortreal (32-bit) | Precision loss |
|---|---|---|---|
| 3.14159265358979 | 3.14159265358979 (exact to 15 digits) | 3.14159274 (only 7 digits) | From 9th digit onward |
| 1.23456789012345 | 1.23456789012345 | 1.23456788 | From 8th digit onward |
| 0.1 | 0.1000000000000000055... | 0.100000001... | shortreal has larger error |
Code Examples — From Basic Arithmetic to Protocol Timing
Example 1 — Beginner: Declarations, Math, and Conversions
module tb_real_basics;
real r = 0.0;
shortreal sr = 0.0;
int i;
initial begin
// ── Basic arithmetic ──────────────────────────────────────────
r = 2.4e9; // 2.4 GHz in Hz
$display("Freq: %0.3e Hz", r);
real period_ns = 1.0e9 / r; // period in ns
$display("Period: %0.6f ns", period_ns); // 0.416667 ns
// ── Math functions ────────────────────────────────────────────
$display("sqrt(2) = %0.10f", $sqrt(2.0)); // 1.4142135624
$display("2^10 = %0.0f", $pow(2.0, 10.0)); // 1024
$display("abs(-5) = %0.1f", $abs(-5.0)); // 5.0
// ── int → real conversion ─────────────────────────────────────
i = 7;
r = real'(i);
$display("int 7 → real: %0.1f", r); // 7.0
// ── real → int: truncates, does NOT round ─────────────────────
r = 3.9;
i = int'(r);
$display("real 3.9 → int (truncate): %0d", i); // 3 (not 4!)
i = int'($round(r));
$display("real 3.9 → int (round): %0d", i); // 4
// ── real vs shortreal precision ───────────────────────────────
real pi_d = 3.14159265358979;
shortreal pi_f = 3.14159265358979;
$display("real pi: %0.15f", pi_d); // 3.141592653589790
$display("shortreal pi: %0.15f", pi_f); // 3.141592741012573 (precision loss)
$finish;
end
endmoduleExpected output:
Freq: 2.400e+09 Hz
Period: 0.416667 ns
sqrt(2) = 1.4142135624
2^10 = 1024
abs(-5) = 5.0
int 7 → real: 7.0
real 3.9 → int (truncate): 3
real 3.9 → int (round): 4
real pi: 3.141592653589790
shortreal pi: 3.141592741012573Example 2 — Intermediate: Timing Calculation and Frequency Analysis
module tb_timing_check;
parameter real CLK_FREQ_GHZ = 1.0; // 1 GHz clock
parameter real CLK_PERIOD_NS = 1.0 / CLK_FREQ_GHZ; // 1.0 ns
parameter real SETUP_TIME_NS = 0.18; // setup time requirement
parameter real HOLD_TIME_NS = 0.05; // hold time requirement
parameter real EPSILON = 1e-9; // comparison tolerance
real data_arrival_ns;
real clk_edge_ns;
real slack_ns;
function automatic bit check_setup(real data_t, clk_t);
real arrival_before_clk = clk_t - data_t;
if (arrival_before_clk < SETUP_TIME_NS - EPSILON) begin
$error("SETUP VIOLATION: data arrives %.3fns before clk, need %.3fns",
arrival_before_clk, SETUP_TIME_NS);
return 0;
end
return 1;
endfunction
function automatic bit check_hold(real data_t, clk_t);
real data_after_clk = data_t - clk_t;
if (data_after_clk < HOLD_TIME_NS - EPSILON) begin
$error("HOLD VIOLATION: data changes %.3fns after clk, need %.3fns",
data_after_clk, HOLD_TIME_NS);
return 0;
end
return 1;
endfunction
initial begin
clk_edge_ns = 10.0; // clock edge at 10 ns
// Test 1: data arrives 0.25 ns before clock — passes setup (need 0.18)
data_arrival_ns = clk_edge_ns - 0.25;
$display("Test1 setup: %0s", check_setup(data_arrival_ns, clk_edge_ns) ? "PASS" : "FAIL");
// Test 2: data arrives 0.10 ns before clock — FAILS setup (need 0.18)
data_arrival_ns = clk_edge_ns - 0.10;
$display("Test2 setup: %0s", check_setup(data_arrival_ns, clk_edge_ns) ? "PASS" : "FAIL");
// Test 3: data changes 0.08 ns after clock — passes hold (need 0.05)
real data_change_ns = clk_edge_ns + 0.08;
$display("Test3 hold: %0s", check_hold(data_change_ns, clk_edge_ns) ? "PASS" : "FAIL");
$finish;
end
endmoduleExample 3 — Verification: Coverage Weight and Probability Calculation
module tb_coverage_metrics;
int total_bins = 256;
int covered_bins = 0;
int hit_counts [256];
real coverage_pct;
real entropy; // Shannon entropy: measure of distribution uniformity
initial begin
// Simulate some bins being hit
foreach (hit_counts[i])
hit_counts[i] = (i < 200) ? (i % 5 + 1) : 0; // 200 bins covered
// Count covered bins
covered_bins = hit_counts.sum() with (item > 0);
// Coverage percentage: real division needed for precision
coverage_pct = (real'(covered_bins) / real'(total_bins)) * 100.0;
$display("Coverage: %0.2f%% (%0d/%0d bins)",
coverage_pct, covered_bins, total_bins);
// Compute total hits for probability calculation
int total_hits = hit_counts.sum();
real p;
entropy = 0.0;
// Shannon entropy: H = -sum(p * log2(p)) for non-zero bins
foreach (hit_counts[i]) begin
if (hit_counts[i] > 0) begin
p = real'(hit_counts[i]) / real'(total_hits);
entropy += -p * ($log(p) / $log(2.0)); // log2(p)
end
end
$display("Entropy: %0.4f bits (max=%.4f for uniform)",
entropy, $log(real'(covered_bins)) / $log(2.0));
$finish;
end
endmoduleExample 4 — Corner Case: $realtobits and Floating-Point Equality Trap
module tb_real_corners;
parameter real EPS = 1e-9;
initial begin
// ── The equality trap ─────────────────────────────────────────
real a = 0.1 + 0.2;
real b = 0.3;
if (a == b)
$display("== : EQUAL");
else
$display("== : NOT EQUAL (a=%.20f, b=%.20f)", a, b);
// Prints: NOT EQUAL — 0.1+0.2 ≠ 0.3 in binary float
if ($abs(a - b) < EPS)
$display("epsilon: EFFECTIVELY EQUAL"); // prints
// ── $realtobits — pass real through logic interface ───────────
real send_val = 3.14159;
logic [63:0] raw_bits;
real recv_val;
raw_bits = $realtobits(send_val);
recv_val = $bitstoreal(raw_bits);
$display("Send: %.10f Bits: %h Recv: %.10f",
send_val, raw_bits, recv_val);
// send and recv will be identical (lossless round-trip)
// ── Special float values ──────────────────────────────────────
real pos_inf = 1.0 / 0.0; // +Infinity (tool-dependent — may warn)
real nan_val = 0.0 / 0.0; // NaN (Not a Number)
$display("1/0 = %f", pos_inf); // inf
$display("0/0 = %f", nan_val); // nan
// NaN comparisons: nan == nan → FALSE (always!)
$display("nan == nan: %0b", nan_val == nan_val); // 0 — NaN never equals itself
// ── int cast truncates, not rounds ────────────────────────────
$display("int'(3.9) = %0d", int'(3.9)); // 3
$display("int'(-3.9) = %0d", int'(-3.9)); // -3 (toward zero)
$display("int'(3.1) = %0d", int'(3.1)); // 3
$finish;
end
endmoduleSimulation Behavior — What You Must Understand
No X, No Z, No Synthesis
real and shortreal are 2-state types — there is no X state, no Z state. An uninitialized real variable starts at 0.0, not X. Division by zero produces inf (positive or negative infinity). Zero divided by zero produces NaN (Not a Number). Both are legal IEEE 754 special values that propagate through further arithmetic in specific ways.
NaN — The Floating-Point X
NaN is the floating-point equivalent of X in a sense — it represents an invalid or undefined result. But NaN has a specific and surprising property: NaN == NaN evaluates to false, and NaN != NaN evaluates to true. This is part of the IEEE 754 standard. If you need to detect NaN in SV, use: if (x != x) — a variable that is not equal to itself is always NaN.
| Scenario | Result | Behavior |
|---|---|---|
real r (uninitialized) | 0.0 | 2-state: starts at 0.0, not X |
1.0 / 0.0 | +Infinity | IEEE 754 positive infinity |
-1.0 / 0.0 | -Infinity | IEEE 754 negative infinity |
0.0 / 0.0 | NaN | NaN propagates through further operations |
NaN == NaN | false (0) | IEEE 754: NaN never equals anything including itself |
NaN != NaN | true (1) | Use this to detect NaN: if (x != x) |
| real in constraint | Compile/runtime error | rand real is not supported |
Where real and shortreal Appear in Verification Work
// ── 1. PARAMETERIZED CLOCK TIMING ─────────────────────────────────
parameter real CLK_FREQ_GHZ = 1.2;
parameter real CLK_HALF_NS = 1000.0 / (2.0 * CLK_FREQ_GHZ * 1000.0);
// CLK_HALF_NS = 0.4167 ns — use with timeprecision 1ps
// ── 2. VOLTAGE THRESHOLD CHECK ────────────────────────────────────
real vdd_nominal = 1.8;
real margin = 0.1; // ±10%
real measured_v;
// Check if voltage is within tolerance:
// if ($abs(measured_v - vdd_nominal) > margin)
// $error("Voltage %.3fV outside tolerance", measured_v);
// ── 3. $realtime FOR SUB-NANOSECOND TIMESTAMPS ───────────────────
real t_req, t_resp, latency_ns;
// t_req = $realtime; // captures fractional ns
// ... wait for response ...
// t_resp = $realtime;
// latency_ns = t_resp - t_req;
// ── 4. PASS real THROUGH LOGIC PORT USING $realtobits ─────────────
// Some modules pass real-valued samples through logic vectors:
function automatic logic [63:0] pack_real(real val);
return $realtobits(val);
endfunction
function automatic real unpack_real(logic [63:0] bits);
return $bitstoreal(bits);
endfunction
// ── 5. COVERAGE METRICS ───────────────────────────────────────────
real coverage_pct(int covered, total);
return (real'(covered) / real'(total)) * 100.0;
endfunction
// Note: int/int gives 0 for covered<total — always cast to real first
// ── 6. AMS VERIFICATION: VAMS / UVM-AMS integration ─────────────
// real used to represent analog output values from mixed-signal DUT:
// real vout_v; // output voltage from ADC reference model
// real iout_ua; // output current in microampsBugs Engineers Hit With Floating-Point Types
Bug 1 — Using == to Compare real: Check That Never Fires
real measured = 0.1 + 0.2; // = 0.30000000000000004
real expected = 0.3; // = 0.29999999999999998...
// BUGGY: direct == comparison silently fails
if (measured == expected)
$display("PASS"); // NEVER fires even though "logically" equal
else
$error("FAIL"); // always fires — false mismatch
// FIXED: epsilon comparison
parameter real EPS = 1e-9;
if ($abs(measured - expected) < EPS)
$display("PASS"); // fires correctlyBug 2 — Integer Division Loses Fraction Before real Conversion
int covered = 75;
int total = 100;
// BUGGY: integer division happens first, then converts to real
real pct_wrong = covered / total * 100.0;
$display("Wrong pct: %0.1f%%", pct_wrong); // 0.0% — 75/100=0 in int division!
// FIXED: cast to real BEFORE dividing
real pct_right = (real'(covered) / real'(total)) * 100.0;
$display("Right pct: %0.1f%%", pct_right); // 75.0%
// Rule: whenever you divide integers and expect a fractional result,
// cast at least one operand to real BEFORE the divisionBug 3 — real Truncates When Cast to int (Not Rounds)
real cycles_per_byte = 1000.0 / 8.0; // 125.0 — exact
real period_frac = 1.0 / 3.0; // 0.333... — repeating
real slack = 0.9999999999; // just under 1.0
// BUGGY: engineer expects rounding, gets truncation
int periods_needed = int'(slack * 10.0); // 9.999... → int'() = 9 (not 10)
$display("Periods needed: %0d", periods_needed); // 9 — expected 10
// FIXED: use $round() before casting
int correct = int'($round(slack * 10.0)); // rounds to 10
$display("Correct: %0d", correct); // 10
// Rule: always think about whether you want truncation or rounding
// int'(x) → truncate toward zero (floor for positive)
// int'($round(x)) → round to nearest integer
// int'($floor(x)) → floor (round toward -infinity)
// int'($ceil(x)) → ceiling (round toward +infinity)Bug 4 — Using real in RTL: Synthesis Fails Silently or Incorrectly
// BUGGY: real used inside synthesizable RTL
module bad_rtl (input logic [7:0] a, b, output logic [7:0] result);
real temp;
always_comb begin
temp = real'(a) * 1.5; // synthesis FAILS — real not supported
result = logic'(temp);
end
endmodule
// CORRECT: scale using fixed-point integer arithmetic
module good_rtl (input logic [7:0] a, output logic [8:0] result);
// ×1.5 = ×3/2 = (a + a>>1) — exact fixed-point equivalent
assign result = {1'b0, a} + {1'b0, a[7:1]}; // a + a/2 = a*1.5
endmoduleInterview Questions
Beginner Level
Q1: What is the difference between real and shortreal? Both are IEEE 754 floating-point types. real is 64-bit double precision with ~15–17 significant decimal digits. shortreal is 32-bit single precision with ~6–9 significant digits. real is the default choice for all timing, voltage, and probability calculations. Use shortreal only when memory is a concern (large arrays of floating-point values) and 6 digits of precision is sufficient. Q2: Why does 0.1 + 0.2 == 0.3 evaluate to false in SystemVerilog? 0.1, 0.2, and 0.3 cannot be represented exactly in binary floating-point. The simulator stores the nearest binary approximation. The sum of the approximations of 0.1 and 0.2 does not equal the approximation of 0.3 at the bit level. This is not a SV bug — it's fundamental to IEEE 754. The fix: use epsilon comparison: $abs(a - b) < 1e-9. Choose epsilon based on the precision of your calculation.
Intermediate Level
Q3: A coverage calculation shows 0% even though bins were clearly hit. What type error caused this? Integer division happened before the real conversion. covered / total * 100.0 computes covered / total as integer division first (result: 0 when covered < total), then converts 0 to real and multiplies by 100.0, giving 0.0. Fix: cast before dividing: (real'(covered) / real'(total)) * 100.0. This is the real equivalent of the C integer division gotcha.
Experienced Engineer Level
Q4: How do you pass a real value across a module port declared as logic [63:0]? Use $realtobits() to convert the real value to its 64-bit IEEE 754 bit pattern before driving the port, and $bitstoreal() to reconstruct the real value from the received bits. This is a lossless round-trip — the bit pattern is the exact memory representation of the float. This pattern is commonly used in mixed-signal testbenches where an analog output value (real) must be passed through a digital interface (logic bus) to a comparator or coverage model. Q5: How do you detect NaN in a real variable, given that NaN == NaN evaluates to false? Use the IEEE 754 property that NaN is the only value not equal to itself: if (x != x) is true if and only if x is NaN. Alternatively, check the bit pattern directly: use $realtobits(x) and check that the exponent field is all 1s (bits [62:52] = 11 1's) and the mantissa is non-zero. In practice, NaN appears when you compute 0.0/0.0 or sqrt of a negative number — guard inputs to math functions to avoid triggering it.
Best Practices & Coding Guidelines
- Always use epsilon comparison — Define a parameter real EPSILON per module or class. Never use == for real comparisons in production verification code. Choose epsilon based on the domain (1e-9 for ns timing, 1e-3 for voltage).
- Cast before integer division — Any calculation that divides two integers but expects a fractional result: cast at least one operand to real first. real'(a) / b — not a / b.
- Use $round() explicitly — int'(r) truncates, it does not round. Whenever rounding is the correct behavior, write int'($round(r)) explicitly — it communicates intent and prevents precision bugs.
- Never use real in RTL — Synthesis tools reject real. Use fixed-point arithmetic (shift + add) for scaling operations in RTL. Keep real strictly in testbench, utility functions, and UVM components.
| Task | Correct approach | Common mistake |
|---|---|---|
| Compare two real values | $abs(a - b) < EPSILON | a == b |
| Integer to real for division | real'(a) / real'(b) | a / b (integer division) |
| real to int (truncate) | int'(r) | Assuming it rounds |
| real to int (round) | int'($round(r)) | int'(r) |
| Pass real through logic port | $realtobits() / $bitstoreal() | Direct assignment (type error) |
| Detect NaN | if (x != x) | if (x == NaN) (always false) |
| Sub-ns simulation timestamp | $realtime → real | $time → integer (loses fractional ns) |
Summary
real and shortreal occupy a narrow but important niche in verification: timing with sub-nanosecond precision, voltage thresholds, coverage weight computation, and analog/mixed-signal testbenches. Outside those specific use cases, stick to integer types. The three rules that prevent almost every real-related bug: never compare with ==, always cast before dividing integers, and keep floating-point out of synthesizable RTL entirely.
realis IEEE 754 64-bit double;shortrealis 32-bit float. Both are 2-state, init to 0.0, and simulation-only.- Never compare real values with
==. Use$abs(a - b) < EPSILONwith an appropriate tolerance. - Integer division loses fraction before real conversion. Cast to real before dividing:
real'(a) / real'(b). int'(r)truncates, it does not round. Useint'($round(r))when rounding is needed.- NaN != NaN. Detect NaN with
if (x != x). - Use
$realtobitsto pass real through logic ports. No other portable mechanism exists.