Skip to content

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

SystemVerilog — real and shortreal Syntax
// ── 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
Propertyreal (double)shortreal (float)
IEEE 754 standard64-bit double precision32-bit single precision
Significant decimal digits~15–17~6–9
Exponent bits118
Mantissa bits5223
Max positive value~1.8×10³⁰⁸~3.4×10³⁸
Smallest positive normal~2.2×10⁻³⁰⁸~1.2×10⁻³⁸
Init value0.00.0
X/Z possibleNoNo
SynthesizableNoNo
rand keywordNot supportedNot supported
Bit conversion$realtobits / $bitstoreal$shortrealtobits / $bitstoshortreal

Visual — IEEE 754 Layout and Precision Limits

IEEE 754 Double Precision Bit Layout (real)

Bit 63Bits 62–52Bits 51–0
Sign 0 = positive 1 = negativeExponent (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

OperationExact mathematical resultreal resultDifference
0.1 + 0.20.30.30000000000000004~4.4×10⁻¹⁷
0.1 * 30.30.30000000000000004~4.4×10⁻¹⁷
1.0 / 3.0 * 3.01.01.0 (exact in this case)0
$sqrt(2.0) * $sqrt(2.0)2.02.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

Valuereal (64-bit)shortreal (32-bit)Precision loss
3.141592653589793.14159265358979 (exact to 15 digits)3.14159274 (only 7 digits)From 9th digit onward
1.234567890123451.234567890123451.23456788From 8th digit onward
0.10.1000000000000000055...0.100000001...shortreal has larger error

Code Examples — From Basic Arithmetic to Protocol Timing

Example 1 — Beginner: Declarations, Math, and Conversions

Example 1 — real and shortreal Basics
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
 
endmodule

Expected output:

Simulation 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.141592741012573

Example 2 — Intermediate: Timing Calculation and Frequency Analysis

Example 2 — Protocol Timing Verification
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
 
endmodule

Example 3 — Verification: Coverage Weight and Probability Calculation

Example 3 — Coverage Metrics and Hit Rate
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
 
endmodule

Example 4 — Corner Case: $realtobits and Floating-Point Equality Trap

Example 4 — Equality Trap and Bit Conversion
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
 
endmodule

Simulation 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.

ScenarioResultBehavior
real r (uninitialized)0.02-state: starts at 0.0, not X
1.0 / 0.0+InfinityIEEE 754 positive infinity
-1.0 / 0.0-InfinityIEEE 754 negative infinity
0.0 / 0.0NaNNaN propagates through further operations
NaN == NaNfalse (0)IEEE 754: NaN never equals anything including itself
NaN != NaNtrue (1)Use this to detect NaN: if (x != x)
real in constraintCompile/runtime errorrand real is not supported

Where real and shortreal Appear in Verification Work

Verification Patterns Using real
// ── 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 microamps

Bugs Engineers Hit With Floating-Point Types

Bug 1 — Using == to Compare real: Check That Never Fires

Bug 1 — Direct == Comparison on real
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 correctly

Bug 2 — Integer Division Loses Fraction Before real Conversion

Bug 2 — int/int Division Before real Cast
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 division

Bug 3 — real Truncates When Cast to int (Not Rounds)

Bug 3 — Truncation vs Rounding on real-to-int
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

Bug 4 — real Accidentally Used in RTL Module
// 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
endmodule

Interview 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.
TaskCorrect approachCommon mistake
Compare two real values$abs(a - b) < EPSILONa == b
Integer to real for divisionreal'(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 NaNif (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.

  • real is IEEE 754 64-bit double; shortreal is 32-bit float. Both are 2-state, init to 0.0, and simulation-only.
  • Never compare real values with ==. Use $abs(a - b) < EPSILON with 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. Use int'($round(r)) when rounding is needed.
  • NaN != NaN. Detect NaN with if (x != x).
  • Use $realtobits to pass real through logic ports. No other portable mechanism exists.