Verilog · Chapter 8.3 · System Tasks & Functions
Time Functions in Verilog — $time, $realtime, $stime
Three system functions return the current simulation time in Verilog, and they are not interchangeable. One returns a 64-bit integer rounded to the active time unit, one returns a 64-bit real value that is exact to the active precision, and one returns a 32-bit integer with far less headroom. The differences stay hidden until you need sub-unit precision, run a simulation past the 32-bit limit, or want a timestamp that fits neatly in a log line. This lesson explains each function, its return type, and how it interacts with the timescale that sets the units. It covers the rounding rules, the 32-bit overflow trap that bites long regressions, and the print formatting discipline that keeps timing logs readable instead of turning them into unreadable walls of numbers.
Foundation14 min readVerilog$time$realtime$stimeTimescaleSimulation
Chapter 8 · Section 8.3 · System Tasks & Functions
1. The Engineering Problem
A verification engineer adds timestamp prints to a long-running clock-recovery testbench. The simulation is configured with `timescale 1ns / 1ps — a precision fine enough to catch picosecond-scale clock drift. The engineer writes:
initial begin
forever begin
@(posedge sample_clk);
$display("[t=%0t] sample = %h", $time, sample);
end
endThe log fills with prints. But the picosecond-scale drift the engineer is hunting for never appears in the output — every timestamp lands on an integer nanosecond. The 50 ps measurement window is invisible.
The issue: $time returns a 64-bit integer rounded to the active time_unit (1 ns here). Sub-unit precision is gone. To see picosecond resolution, the engineer needs $realtime — which returns a 64-bit real preserving the full time_precision (1 ps).
The fix is one character:
$display("[t=%0t] sample = %h", $realtime, sample);
// ^^^^^^^^^ — real, not integer; preserves 1 ps resolutionSame %0t specifier, different function: $time → integer ns timestamps, $realtime → exact ns.ps timestamps. The clock-drift measurement now works.
This page covers all three time-query functions — their return types, their interaction with \timescale, the rounding semantics that hide sub-precision data, the 32-bit overflow trap that can corrupt long-running regression logs, and the %0t` discipline every readable log relies on.
2. The Three Functions at a Glance
| Function | Returns | Width | Precision | Use case |
|---|---|---|---|---|
$time | integer | 64-bit signed | rounded to time_unit | General-purpose timestamps; the default reach |
$realtime | real | 64-bit IEEE-754 | exact to time_precision | Sub-unit measurements (jitter, propagation delay) |
$stime | integer | 32-bit unsigned | rounded to time_unit | Legacy compatibility; overflow risk at long sim times |
The four differentiators:
- Return type — integer (
$time,$stime) vs real ($realtime). - Bit width — 64-bit (
$time,$realtime) vs 32-bit ($stime). - Precision — rounded to
time_unit($time,$stime) vs exact totime_precision($realtime). - Overflow risk — none for the 64-bit functions; serious for
$stimeafter2³² × time_unithas elapsed.
The 64-bit time_unit count for $time overflows at simulated 2⁶³ × time_unit — for 1ns unit, ~292 years of simulated time. $stime overflows at 2³² × time_unit — for 1ns unit, just ~4.3 seconds of simulated time. Long-running RTL regressions hit $stime's ceiling routinely; $time's ceiling is astronomical.
3. $time — The 64-bit Integer Timestamp
The most-used member of the family.
3.1 Syntax
integer t;
t = $time; // 64-bit signed integer
$display("now = %0t", $time); // typical usage
$display("now = %0d ns", $time); // explicit unit suffix3.2 Semantics
- Returns a 64-bit signed integer. Even though simulation time is non-negative, the integer is signed (Verilog's
integerkeyword is signed by default; the 64-bit width is implementation-defined but specified ≥ 64 bits in IEEE 1364-2005). - Rounded to the active
time_unit. With`timescale 1ns / 1ps,$timeat simulation time 5.5 ns returns6(rounded to the nearest ns). Sub-unit precision is dropped. - No arguments. Always reads the current scope's active timescale.
3.3 The rounding rule
`timescale 1ns / 1ps // unit = 1ns, precision = 1ps
module rounding_demo;
initial begin
#5; $display("$time = %0t", $time); // 5
#0.5; $display("$time = %0t", $time); // 6 (rounds 5.5 → 6)
#0.5; $display("$time = %0t", $time); // 6 (rounds 6.0 → 6)
#0.0001; $display("$time = %0t", $time); // 6 (rounds 6.0001 → 6)
$finish;
end
endmodule$time rounds at the time_unit boundary using banker's rounding (round half to even). Sub-unit delays accumulate inside the simulator (preserved in $realtime) but vanish in the integer return.
3.4 When to use
Almost every general-purpose timestamp print. Log lines, debug snapshots, test-progress banners. The default reach when sub-unit precision isn't needed.
4. $realtime — The 64-bit Real Timestamp
The function for sub-unit measurements.
4.1 Syntax
real t;
t = $realtime; // 64-bit IEEE-754 double
$display("now = %0t", $realtime); // %0t formats reals too
$display("now = %0.3f ns", $realtime); // explicit %f format4.2 Semantics
- Returns a 64-bit IEEE-754 double-precision real. The value is exact to the active
time_precision(e.g., 1 ps under`timescale 1ns / 1ps). - No rounding to
time_unit. Sub-unit delays are preserved. - Unit is still the timescale's
time_unit. A$realtimeof 5.5 under1ns / 1psmeans 5.5 ns, NOT 5.5 ps.
4.3 The precision contract
`timescale 1ns / 1ps
module realtime_demo;
initial begin
#5; $display("$time=%0t $realtime=%0t", $time, $realtime);
// $time=5 $realtime=5
#0.5; $display("$time=%0t $realtime=%0t", $time, $realtime);
// $time=6 $realtime=5.5 ← integer rounded, real preserved
#0.123; $display("$time=%0t $realtime=%0t", $time, $realtime);
// $time=6 $realtime=5.623
$finish;
end
endmodule$realtime preserves the sub-unit detail that $time drops. The trade-off: real arithmetic is slower than integer arithmetic, and IEEE-754 representations have rounding error at large values (millions of nanoseconds suffer floating-point precision loss).
4.4 When to use
Any time you need sub-time_unit resolution — clock jitter analysis, propagation-delay extraction, PLL settling-time measurement, real-time signal-processing simulation. Otherwise prefer $time (faster, integer-clean).
5. $stime — The 32-bit Compatibility Form
The legacy function with the overflow trap.
5.1 Syntax
integer t; // careful — assigning $stime to an integer is fine here
t = $stime; // returns 32-bit unsigned
$display("now = %0t", $stime);5.2 Semantics
- Returns a 32-bit unsigned integer. Same rounding rule as
$time. - Identical to
$timemodulo 2³². When$time≤ 2³² − 1,$stime == $time. When$time≥ 2³²,$stimewraps. - Historically the only time function in early Verilog before 64-bit support was universal.
5.3 The overflow trap
`timescale 1ns / 1ps
module stime_overflow_demo;
initial begin
#4_000_000_000; // 4 billion ns ≈ 4.3 seconds simulated
$display("$time = %0d", $time); // 4000000000 (fine)
$display("$stime = %0d", $stime); // 4000000000 (still fine)
#500_000_000; // additional 0.5 s
$display("$time = %0d", $time); // 4500000000 (fine)
$display("$stime = %0d", $stime); // 205032704 ← WRAPPED
// $time exceeds 2³²-1 = 4_294_967_295; $stime wraps modulo 2³²
$finish;
end
endmodule$stime wraps once simulated time exceeds 2³² × time_unit:
| Timescale unit | $stime overflows at |
|---|---|
1fs | 4.3 µs simulated |
1ps | 4.3 ms simulated |
1ns | 4.3 s simulated |
1us | 71.6 minutes simulated |
1ms | 49.7 days simulated |
For ASIC RTL using 1ns / 1ps timescale, a 4.3-second regression hits the overflow. Long-running stability simulations, post-silicon co-emulation, and gate-level annotated runs routinely exceed it.
5.4 When to use
Almost never in new code. The function exists for legacy compatibility with Verilog-1995 code that pre-dates 64-bit $time. New code should always use $time (or $realtime for sub-precision).
6. Interaction with `timescale
The three functions all read the active timescale to determine their unit and (for $realtime) precision.
6.1 The unit is the divisor
`timescale 1ns / 1ps → $time returns "units of 1 ns" (#10 → $time=10)
`timescale 1us / 1ns → $time returns "units of 1 us" (#10 → $time=10)
`timescale 1ms / 1us → $time returns "units of 1 ms" (#10 → $time=10)A #10 in source is always 10 × time_unit; $time after the delay always returns 10. The integer value doesn't change with the unit — only its meaning does. This is what makes timestamps portable across timescales as raw counts, even though the wall-clock duration each unit represents differs.
6.2 The precision floors $realtime resolution
`timescale 1ns / 1ns → $realtime is also rounded to 1 ns
`timescale 1ns / 1ps → $realtime exact to 1 ps (3 sub-unit digits)
`timescale 1ns / 1fs → $realtime exact to 1 fs (6 sub-unit digits)$realtime's effective resolution is the timescale's time_precision, not the time_unit. Choosing precision finer than needed slows the simulation (covered in the 7.3 timescale-directive page) but gives $realtime more digits to print.
6.3 Cross-module timescale rules apply
Each module's $time / $realtime reflects its own \timescale. A module-A $timeof10under1ns / 1psand a module-B$timeof10under1us / 1ns` represent different wall-clock instants — even though both are simulated at the same simulator step. The 7.3 page covers the multi-module propagation rule; this page just notes that time functions inherit it.
7. Format Specifier %t
The print specifier built specifically for these functions.
7.1 %t vs %0t
$display("t = %t", $time); // "t = 10" — default width is HUGE
$display("t = %0t", $time); // "t = 10" — compactThe default %t width is implementation-defined but typically 20+ characters — every print becomes unreadable. Always use %0t for production log output.
7.2 Explicit format vs %t
$display("t = %0d", $time); // "t = 5500" — no time-aware formatting
$display("t = %0t", $time); // "t = 5500" — same number, time-aware
$display("t = %0.3f ns", $realtime); // "t = 5.500 ns" — explicit unit%t is identical to %d when the argument is an integer ($time, $stime), but the difference appears with $realtime:
$display("%0t", $realtime); // "5500" — integer-style print
$display("%0.3f", $realtime); // "5.500" — decimal-style print
$display("%g", $realtime); // "5.5" — compact-style printUse %0t for uniform time-stamp output; use %0.3f or %g when you need decimal point clarity.
7.3 Vendor-specific $timeformat
initial begin
$timeformat(-9, 3, " ns", 10);
// (-9 = nanoseconds, 3 = decimals, " ns" = suffix, 10 = min width)
$display("now = %t", $realtime); // " 5.500 ns" — auto-formatted
end$timeformat (IEEE 1364-2005 §17.8.1) configures how %t formats time. Sets the unit indicator, decimal precision, suffix, and minimum width. Useful in shared library tasks where every timestamp print should match a project convention.
8. Visual Explanation
Three figures cover the time-function model.
8.1 Visual A — function family at a glance
The three functions and their differentiators.
Time-query family
data flow8.2 Visual B — precision rounding
What $time shows vs what $realtime shows at the same simulator step.
8.3 Visual C — $stime overflow at 4.3 s simulated time
Why long-running regressions can't use the 32-bit function.
9. Industry Use Cases
Five patterns where the time-query family shows up in production.
9.1 Event-time logging
Every meaningful event prints with a timestamp:
always @(posedge clk) begin
if (transaction_complete) begin
$display("[t=%0t] xact %0d complete: data=%h", $time, id, data);
end
endUniversal pattern across every testbench. $time + %0t is the canonical form.
9.2 Sub-precision jitter measurement
Measuring clock jitter requires $realtime:
real prev_edge_time, jitter_ps;
always @(posedge sample_clk) begin
jitter_ps = ($realtime - prev_edge_time) * 1000.0 - NOMINAL_PERIOD_PS;
prev_edge_time = $realtime;
$display("[t=%0t] jitter = %0.3f ps", $realtime, jitter_ps);
end$time would round to nanosecond boundaries; the picosecond-scale jitter signal would vanish.
9.3 Propagation-delay characterisation
Extract propagation delays from event timing:
real input_edge, output_edge;
always @(input_signal) input_edge = $realtime;
always @(output_signal) begin
output_edge = $realtime;
$display("propagation = %0.3f ps", (output_edge - input_edge) * 1000.0);
end9.4 Test-progress tracking
Long-running tests print progress markers:
initial begin
forever begin
#1_000_000; // every 1 ms simulated
$display("[t=%0t] progress: %0d cycles complete", $time, cycle_count);
end
end$time survives even multi-day simulations on a 64-bit integer; $stime would wrap.
9.5 Timeout enforcement
Detect runaway tests:
parameter MAX_TIME = 100_000_000; // 100 ms simulated
always @(posedge clk) begin
if ($time > MAX_TIME) begin
$display("[FATAL] timeout at t=%0t — test ran > %0d", $time, MAX_TIME);
$finish(1);
end
end$time's comparison is integer; $realtime's would be IEEE-754 double — both work, but integer comparison is exact while double comparison can have rounding artefacts at large values.
10. Common Mistakes
Six pitfalls that catch every working engineer at least once.
10.1 %t produces unreadable wide output
$display("t = %t", $time);
// Prints: "t = 100" ← 20+ char field, hard to readDefault %t width is implementation-defined and typically huge. Always use %0t for compact output.
10.2 $time drops sub-unit precision
`timescale 1ns / 1ps
initial begin
#5.5 $display("t = %0t", $time); // prints "t = 6", not "t = 5.5"
end$time rounds to time_unit. For sub-unit values, use $realtime.
10.3 $stime silently wraps in long simulations
`timescale 1ns / 1ps
initial begin
#5_000_000_000;
$display("t = %0d (stime = %0d)", $time, $stime);
// "t = 5000000000 (stime = 705032704)" ← stime wrapped
endUse $time for any simulation that might exceed 2³² × time_unit.
10.4 Comparing real time with integer time
real t_real;
integer t_int;
t_real = $realtime;
t_int = $time;
if (t_real == t_int) // ← may be false even at same simulator step
$display("equal");$time is rounded; $realtime is exact. They equal only when the simulator step is on an integer time_unit boundary. Don't compare values from the two functions directly.
10.5 $realtime precision loss at large values
real t;
t = $realtime;
// At simulated time 1_000_000_000 ns (1 second) with 1ps precision,
// the IEEE-754 double has ~15 significant digits — picosecond resolution
// is preserved. At simulated 100_000_000_000 ns (100 seconds), the
// representable precision degrades to ~10s of picoseconds.Long-running picosecond-precision simulations eventually lose precision in $realtime's IEEE-754 representation. For jitter measurement of relative differences (event-to-event), this rarely matters; for absolute long-time precision it does.
10.6 Forgetting \timescalemakes$time` undefined
// No `timescale at top of file
module no_timescale;
initial #10 $display("t = %0t", $time);
// What unit is "10"? Vendor-defined default. Behaviour varies.
endmodule$time depends on \timescale`. Without it, the unit is implementation-defined. Always declare a timescale (the 7.3 page covers the discipline).
11. Debugging Lab
Three time-function debug post-mortems
12. Coding Guidelines
Six rules teams converge on.
$timeis the default reach. Reach for it first; switch to$realtimeonly when sub-unit precision is required.$realtimefor sub-unit measurements. Jitter, propagation delay, PLL settling time, any value smaller than onetime_unit.- Avoid
$stime. Only use it for legacy compatibility with Verilog-1995 code; new code uses$time(64-bit, no overflow risk). %0tfor every print. Default%twidth is unreadable; the leading0forces minimum-width.- **Always declare
\timescale.** Without it,$time` and its siblings have implementation-defined behaviour. - Don't compare
$timewith$realtimedirectly. Different precisions;==is unreliable. Convert one side to the other's representation first, or use a tolerance band.
13. Interview Q&A
14. Exercises
Three exercises that turn time-function choice into reflex.
Exercise 1 — Predict the output
For each snippet, predict the exact printed output.
// Snippet A
`timescale 1ns / 1ps
initial begin
#5 $display("A: %0t", $time);
#0.5 $display("B: %0t", $time);
#0.5 $display("C: %0t", $time);
#0.001 $display("D: %0t", $time);
$finish;
end
// Snippet B
`timescale 1ns / 1ps
initial begin
#5 $display("A: %0t / %0t", $time, $realtime);
#0.5 $display("B: %0t / %0t", $time, $realtime);
#0.123 $display("C: %0t / %0t", $time, $realtime);
$finish;
end
// Snippet C
`timescale 1ns / 1ps
initial begin
#5_000_000_000 $display("A: time=%0d stime=%0d", $time, $stime);
$finish;
endHints. Apply the rounding rule to A. Apply the $time vs $realtime distinction to B. Apply the 32-bit overflow rule to C.
Exercise 2 — Pick the right function
For each requirement, name the best function (and any format specifier needed).
| # | Requirement |
|---|---|
| 1 | Print a per-cycle bus event timestamp in a log line |
| 2 | Measure the time between two posedge clk events to 1 ps resolution |
| 3 | Detect if simulation has run more than 100 ms |
| 4 | Print the current time formatted as "5.500 ns" |
| 5 | Time-stamp a 5-day-long gate-level regression |
| 6 | Match Verilog-1995 codebase conventions |
| 7 | Compute clock jitter as the deviation from a nominal period |
Exercise 3 — Fix the time bugs
This testbench has four time-function bugs. Identify and fix.
`timescale 1ns / 1ps
module buggy_time_tb;
integer prev_edge, jitter;
real long_sim_t;
always @(posedge clk) begin
jitter = $time - prev_edge - 10; // Bug 1?
$display("t = %t jitter = %0d ps", $time, jitter * 1000); // Bug 2?
prev_edge = $time;
end
initial begin
#4_500_000_000;
$display("end of test at $stime = %0d", $stime); // Bug 3?
end
initial begin
long_sim_t = $time; // Bug 4?
$display("long_sim_t = %0.3f", long_sim_t);
end
endmoduleHints. Bug 1 is a precision issue. Bug 2 is a format issue. Bug 3 is an overflow issue. Bug 4 is a type-mismatch issue.
15. Summary
Verilog's time-query family has three members differentiated by return type, bit width, and precision.
The three functions:
$time— 64-bit signed integer, rounded totime_unit. The default.$realtime— 64-bit IEEE-754 real, exact totime_precision. For sub-unit measurements.$stime— 32-bit unsigned integer; same semantics as$timeminus 32 bits of headroom. Avoid in new code (overflows at2³² × time_unit).
The interaction with \timescale`:
- The unit is the timescale's
time_unit.$time = 10under1ns / 1psmeans 10 ns. - Precision floor:
$timerounds totime_unit;$realtimerounds totime_precision. - Multi-module timescales propagate the same way as in §7.3 — each module's time functions reflect its own timescale.
The %t format discipline:
- Always use
%0t, never bare%t. Default%twidth is implementation-defined (typically 20+ chars) and produces unreadable logs. - Use
%0.3ffor decimal-style$realtimeprints. - Use
$timeformat(-9, 3, " ns", 0)to configure%tglobally.
The overflow risk:
| Timescale unit | $stime overflows at |
|---|---|
1fs | 4.3 µs simulated |
1ps | 4.3 ms simulated |
1ns | 4.3 s simulated |
1us | 71.6 minutes simulated |
1ms | 49.7 days simulated |
$time (64-bit) is essentially overflow-free — at 1ns / 1ps, the ceiling is 292 years of simulated time.
The day-to-day discipline:
$timefor general-purpose timestamps. Reach for it first.$realtimefor sub-unit precision. Jitter, propagation delay, PLL settling.- Avoid
$stime. Legacy only; wraps in long simulations. %0tfor every print. Bare%tis unreadable.- Always declare
\timescale`. Without it, time-function behaviour is implementation-defined.
The next two sub-pages close Chapter 8: 8.4 Conditional Simulation covers the assertion-and-severity layer ($assert, $error, $warning, $fatal, $info), and 8.5 Simulation Control closes the chapter with $finish / $stop / $exit.
Related Tutorials
- System Tasks & Functions — Chapter 8; the parent overview of the eight functional families.
- `timescale — Chapter 7.3; the unit / precision contract that every time function rides on.
- $display vs $monitor vs $strobe vs $write — Chapter 8.1; the display family that consumes timestamps from this family.
- $random — Random Number Generation — Chapter 8.2; the randomization family that pairs with time-based stimulus generation.