Verilog · Chapter 8.2 · System Tasks & Functions
$random in Verilog — Random Number Generation
The random system function is Verilog's pseudorandom number generator, returning a 32-bit signed integer drawn from an internal stream. Every constrained-random testbench, stress-test stimulus, and randomised delay rides on it or one of its cousins such as urandom, urandom range, and the distribution functions. The call looks trivial, but it carries subtleties every verification engineer trips on once. These include the signedness of the return value and how it breaks simple modulo range restriction, the seed contract for reproducible runs, the reproducibility discipline that regressions depend on, the per-context stream rule that differs between plain Verilog and SystemVerilog, and the distribution functions for non-uniform draws. This page walks through each one so your random stimulus is both varied and repeatable.
Foundation18 min readVerilog$random$urandomVerificationTestbenchConstrained Random
Chapter 8 · Section 8.2 · System Tasks & Functions
1. The Engineering Problem
A verification engineer writes a stress test for an 8-bit ALU. The goal: hit every opcode + operand combination across thousands of cycles to catch corner-case bugs. The naive approach:
module alu_stress_tb;
reg [7:0] a, b;
reg [2:0] op;
integer i;
initial begin
for (i = 0; i < 10000; i = i + 1) begin
a = $random;
b = $random;
op = $random % 8; // restrict to 0..7
#1;
// ... check result ...
end
$finish;
end
endmoduleThe test runs. The opcode histogram comes back skewed — opcode 0 fires twice as often as the others. The engineer suspects a stuck-bit on the opcode register. Three hours of debugging later, the ALU is fine. The bug is in $random % 8 — $random returns a signed 32-bit value, and the modulo of a negative number in Verilog is implementation-defined. Half the time the modulo result is -N, and the engineer's op register sees a wrong-signed value that wraps to a different bucket.
The right fix uses the unsigned variant:
op = $urandom_range(7, 0); // uniform unsigned 0..7This page covers the randomization family in depth — the $random mechanism, the seed contract, the negative-modulo trap, the $urandom / $urandom_range upgrades that fix the trap by construction, the seven distribution functions for non-uniform draws, and the reproducibility discipline every regression suite depends on.
2. Anatomy of $random
The simplest random source.
2.1 Syntax
integer r;
r = $random; // bare call — uses an internal global seed
r = $random(seed); // seeded call — uses the explicit seed, updates itThe return type is a 32-bit signed integer (-2³¹ to 2³¹-1).
2.2 Semantics
- Pseudorandom, not truly random. The same seed produces the same stream — this is what makes reproducible regressions possible.
- Signed return. The bare
$randomcall can return negative values; this is the source of the% Ndistribution-bias trap. - Seeded form mutates the seed.
$random(seed)advances the seed in place — the variable's value changes after each call, walking through the pseudorandom stream. - Unseeded form uses a global hidden seed. All
$randomcalls without an explicit seed share one global stream; the stream's initial value is implementation-defined (typically 0 or a fixed constant per simulator vendor).
2.3 The signedness contract
integer r;
r = $random;
$display("r = %0d (could be negative)", r);
$display("r = %h (always 32 hex digits)", r);
$display("r = %b (always 32 bits)", r);
// Cast to unsigned 32-bit for typical use
reg [31:0] u;
u = $random;
$display("u = %h (high bit interpretation differs by tool)", u);When assigned to an integer (signed), the value retains its signedness; when assigned to reg [31:0] (unsigned), the bit pattern is identical but the interpretation drops sign. The distinction matters for %d printing and for arithmetic — r % N does NOT behave like u % N when r is negative.
3. Mental Model
4. The Seed Contract
The seed is the random stream's primary key. Two forms.
4.1 The hidden global seed (bare $random)
initial begin
integer i;
for (i = 0; i < 5; i = i + 1)
$display("%0d", $random); // uses hidden global seed
endThe hidden global seed is shared across every bare $random call in the simulation. Two independent always blocks both calling bare $random interleave their consumption — the order depends on event scheduling and isn't guaranteed across runs even with no source change.
4.2 The explicit-seed form ($random(seed))
initial begin
integer i;
integer seed = 42; // reproducible — same seed every run
for (i = 0; i < 5; i = i + 1)
$display("%0d", $random(seed));
end
// Output (vendor-dependent, but identical run-to-run with seed=42):
// 303379748
// -1064739199
// -1748305800
// 1543339977
// -1129508905$random(seed) walks an explicit stream. The seed variable is mutated by each call. Critically, each initial/always/task that owns its own seed variable gets its own stream — independent from the hidden global stream and from other explicit streams.
4.3 Reproducibility discipline
// In every regression-grade testbench
module my_tb;
integer seed;
initial begin
// Read seed from command line, default to 0
if (!$value$plusargs("SEED=%d", seed))
seed = 0;
$display("Using seed = %0d", seed);
// ... use $random(seed) or $urandom(seed) throughout ...
end
endmoduleRun with vsim +SEED=12345 my_tb to fix a specific seed; rerun with the same seed to reproduce any bug found. Most production regression suites log the seed of every failing run so it can be replayed bit-identically.
5. The Negative-Modulo Trap
The most common $random bug in production code.
integer r;
reg [2:0] op;
r = $random; // signed: could be -2³¹ to +2³¹-1
op = r % 8; // INTENDED: 0..7 uniform
// ACTUAL: -7..7 — values 1..7 each from positive
// AND negative source rangesThe Verilog standard leaves the sign of (-7) % 8 implementation-defined. Most simulators return -7; some return +1. Either way, assigning the result to an unsigned register either wraps or sign-extends — the histogram is no longer uniform.
The historic Verilog-2001 fix:
r = $random;
if (r < 0) r = -r; // make positive
op = r % 8; // now uniform 0..7But this still has bias — the value r = -2³¹ cannot be negated (overflows back to itself), so the smallest bucket gets one fewer chance.
The modern fix (Verilog-2005 / SystemVerilog):
op = $urandom_range(7, 0); // unsigned, uniform, by constructionAlways prefer $urandom_range for bounded random values.
6. $urandom and $urandom_range
Verilog-2005 added the unsigned random variants.
6.1 $urandom
reg [31:0] u;
u = $urandom; // unsigned 32-bit, [0, 2³²-1]
u = $urandom(seed); // seeded — same contract as $random(seed)Returns a 32-bit unsigned integer. The signed-return trap of $random is gone — every value is in [0, 2³²-1]. The seed contract is identical.
6.2 $urandom_range
reg [7:0] byte;
reg [3:0] nibble;
integer idx;
byte = $urandom_range(255, 0); // [0, 255] uniform
nibble = $urandom_range(15); // [0, 15] uniform — min defaults to 0
idx = $urandom_range(99, 50); // [50, 99] uniform — inclusive both endsReturns a uniform unsigned value in [min, max] (inclusive). Distribution is uniform by construction — no modulo arithmetic, no bias.
The argument order is (max, min) (most-significant first); the min argument defaults to 0 if omitted.
6.3 The upgrade rule
| Old pattern (avoid) | New pattern (prefer) |
|---|---|
$random % N | $urandom_range(N-1, 0) |
($random & 'h7F) (bit-mask to 7 bits) | $urandom_range(127, 0) |
($random % (max - min + 1)) + min | $urandom_range(max, min) |
r = $random; if (r<0) r=-r; | $urandom |
Every legacy $random % pattern has a clean $urandom_range replacement that's both shorter and bias-free.
7. Distribution Functions
IEEE 1364-2005 §17.9.2 defines seven distribution functions for non-uniform draws. All take a seed as the first argument and return an integer.
| Function | Distribution | Extra arguments | Use case |
|---|---|---|---|
$dist_uniform | Uniform | start, end | Same as $urandom_range but Verilog-2001-portable |
$dist_normal | Gaussian | mean, std_dev | Modelling jitter, noise |
$dist_exponential | Exponential | mean | Inter-arrival times for traffic generation |
$dist_poisson | Poisson | mean | Event-count modelling |
$dist_chi_square | Chi-square | degrees_of_freedom | Statistical-test stimulus |
$dist_t | Student's t | degrees_of_freedom | Statistical-test stimulus |
$dist_erlang | Erlang | k_stage, mean | Queueing-theory simulations |
7.1 Example — Gaussian jitter stimulus
module jitter_stimulus;
integer seed = 42;
real jitter_ps;
integer i;
initial begin
for (i = 0; i < 10; i = i + 1) begin
// Mean 0, std dev 50 ps
jitter_ps = $dist_normal(seed, 0, 50);
$display("cycle %0d: jitter = %0d ps", i, jitter_ps);
end
end
endmodule$dist_normal(seed, 0, 50) draws an integer from a Gaussian with mean 0 and standard deviation 50. The vast majority of values land in [-150, +150] (±3σ); rare values exceed.
7.2 Example — Exponential inter-arrival times
module packet_generator;
integer seed = 7;
integer delay_cycles;
integer i;
initial begin
for (i = 0; i < 5; i = i + 1) begin
// Average 20 cycles between packets
delay_cycles = $dist_exponential(seed, 20);
#(delay_cycles) generate_packet();
end
end
endmodule$dist_exponential(seed, 20) draws an integer from an exponential distribution with mean 20 — useful for memoryless traffic models (most packets close together, occasional long gaps).
8. Visual Explanation
Three figures cover the randomization model.
8.1 Visual A — randomization family tree
The functions at a glance.
Randomization family
data flow8.2 Visual B — the negative-modulo bias
Why $random % 8 is biased and $urandom_range(7, 0) is not.
8.3 Visual C — seed contract
Hidden global vs explicit per-context streams.
9. The Reproducibility Discipline
Every working regression suite enforces three rules.
9.1 Always use an explicit seed in production tests
integer seed;
initial begin
if (!$value$plusargs("SEED=%d", seed))
seed = 0; // explicit default — not the hidden global
$display("[tb] seed = %0d", seed);
// ... $random(seed), $urandom(seed), $urandom_range(...) ...
end$value$plusargs("SEED=%d", seed) reads +SEED=12345 from the simulator's command line. If not provided, defaults to 0.
9.2 Log the seed on every failing run
always @(posedge clk) begin
if (test_failed) begin
$display("[FAIL] seed=%0d cycle=%0d ...", seed, $time);
$finish(1);
end
endA failed regression-suite run that doesn't print the seed cannot be replayed. Always log it.
9.3 One seed per process / per context
// Each always block owns its own seed → independent streams.
integer seed_stim, seed_check;
initial seed_stim = 100;
initial seed_check = 200;
always @(posedge clk) begin
stim_value <= $urandom(seed_stim);
end
always @(posedge clk) begin
if ($urandom_range(99, 0) < 5) // 5% probability
inject_glitch();
endSharing one seed across multiple contexts couples their streams — adding a call in one context shifts the others. Per-context seeds keep the streams independent.
10. Industry Use Cases
Five patterns where the randomization family shows up in production.
10.1 Constrained-random stimulus generation
The canonical workload. Every verification suite generates millions of random transactions:
reg [31:0] addr;
reg [31:0] data;
reg [1:0] burst_len;
integer seed = 42;
always @(posedge clk) begin
addr <= $urandom(seed) & 32'hFFFF_FFFC; // word-aligned
data <= $urandom(seed);
burst_len <= $urandom_range(3, 0);
end10.2 Random delays in handshake testbenches
Inserting random delays between handshake phases exposes timing bugs:
initial begin
repeat (100) begin
// Random think time before driving valid
#($urandom_range(20, 1));
valid <= 1;
@(posedge clk iff ready);
valid <= 0;
end
end10.3 Statistical-distribution stimulus
Modelling realistic traffic patterns requires non-uniform distributions:
// Gaussian jitter on a 10 ns clock
real jitter_ps;
integer seed = 7;
always @(posedge nominal_clk) begin
jitter_ps = $dist_normal(seed, 0, 25); // mean=0, sigma=25ps
#(jitter_ps / 1000.0) actual_clk = 1;
end10.4 Random opcode generation for ALU / CPU verification
Cover every opcode + operand combination:
reg [7:0] a, b;
reg [2:0] op;
integer seed = 99;
initial begin
repeat (10000) begin
a = $urandom_range(255, 0);
b = $urandom_range(255, 0);
op = $urandom_range(7, 0);
@(posedge clk);
end
end10.5 Bug-replay via seed logging
When a regression finds a failure, the seed is logged with the failure record. The same test rerun with +SEED=<logged_value> reproduces the exact failure for debugging.
11. Common Mistakes
Six pitfalls that catch every verification engineer at least once.
11.1 $random % N biased distribution
The §5 trap. $random returns signed; % N is biased. Use $urandom_range(N-1, 0) instead.
11.2 Forgotten seed → irreproducible regression
// No seed handling — every run produces different results
always @(posedge clk)
stim <= $random;A failure on Tuesday's regression run cannot be replayed Wednesday. Always read a seed from $value$plusargs and log it.
11.3 Seed shared across contexts couples streams
integer seed = 0;
always @(posedge clk_a) x = $random(seed); // consumes from seed
always @(posedge clk_b) y = $random(seed); // consumes from seed
// Adding any extra $random(seed) in clk_a's block shifts clk_b's stream.One seed per context. Independent streams stay independent.
11.4 Vendor-portable seed assumption
// Seed 42 in VCS produces sequence A
// Seed 42 in Xcelium produces sequence B — different algorithmPseudorandom algorithms are not standardised. Same seed across vendors produces different streams. Reproducibility is vendor-scoped.
11.5 $random in synthesisable RTL
always @(posedge clk)
state <= $random; // wrong tool — $random is simulation-only$random is non-synthesisable. Synthesis tools strip it or warn. For RTL pseudorandom generators, instantiate an LFSR or a Galois polynomial — not a system task.
11.6 $urandom_range(0, 7) vs $urandom_range(7, 0)
The argument order is (max, min). Reversing produces unexpected behaviour — most simulators silently swap, but some return constants.
x = $urandom_range(0, 7); // ambiguous — depends on tool's swap behaviour
x = $urandom_range(7, 0); // canonical — max first, min secondStick to (max, min). Set min = 0 to omit the second argument.
12. Debugging Lab
Three randomization debug post-mortems
13. Coding Guidelines
Six rules teams converge on.
$urandom_range(max, min)for bounded uniform draws. Never$random % N. The unsigned variant is uniform by construction.- Always read the seed from
$value$plusargs. Default to a known value (0 or 1). Log the seed at simulation start and on every failing event. - One seed per independent context. Don't share a seed across
alwaysblocks that should produce independent streams. - Use
$random/$urandomonly in testbench code. Wrap in`ifdef SIMULATIONif the file is shared with RTL. Synthesisable pseudorandom generators use LFSRs, not system tasks. - Pick the right distribution function. Uniform →
$urandom_range; Gaussian →$dist_normal; Exponential (memoryless intervals) →$dist_exponential. Most testbenches use uniform; reach for$dist_*only when the workload demands a specific shape. - Document the seed source. Comment whether a seed is a fixed magic number (reproducible smoke test), a
$plusargsargument (production regression), or a global counter (multi-test independence).
14. Interview Q&A
15. Exercises
Three exercises that turn $random into reflex.
Exercise 1 — Spot the bias
For each snippet, identify whether the distribution is uniform or biased, and explain why.
// Snippet A
reg [3:0] r;
r = $random % 16;
// Snippet B
reg [3:0] r;
r = $urandom_range(15, 0);
// Snippet C
reg [3:0] r;
r = $urandom & 4'hF;
// Snippet D
reg [3:0] r;
r = $random; if (r < 0) r = -r;
r = r % 16;
// Snippet E
reg [6:0] r; // 0..127
r = $random % 100; // intend 0..99Hints. Signed-modulo bias, power-of-two boundaries, the -2³¹ overflow edge case, modulo of non-power-of-two ranges.
Exercise 2 — Add reproducibility
Convert this irreproducible testbench into a regression-grade one (reads +SEED=, defaults to 0, logs the seed at startup and at every failing event).
module sloppy_tb;
reg [7:0] a, b;
reg [2:0] op;
initial begin
repeat (1000) begin
a = $random;
b = $random;
op = $random % 8;
@(posedge clk);
if (golden(a, b, op) !== dut_result)
$display("FAIL at t=%0t", $time);
end
$finish;
end
endmoduleWhat to produce. The rewritten module that (a) reads the seed from $value$plusargs, (b) defaults to 0, (c) uses $urandom(seed) / $urandom_range, (d) logs the seed at startup, (e) logs the seed AND the failing-cycle's randomised inputs at every FAIL event.
Exercise 3 — Pick the right distribution
For each requirement, recommend the best function call.
| # | Requirement |
|---|---|
| 1 | Uniform random 8-bit byte |
| 2 | Uniform random integer in [50, 100] |
| 3 | Random delay 10-50 cycles, uniform |
| 4 | Random delay with mean 20 cycles, exponentially distributed (memoryless) |
| 5 | Clock jitter with mean 0, σ=25 ps, Gaussian |
| 6 | Random 1-out-of-N selection with N=7 |
| 7 | 5% probability event injection |
| 8 | Random opcode from {ADD, SUB, AND, OR, XOR, NOP} (uniform) |
What to produce. Function name and exact arguments for each.
16. Summary
$random is Verilog's pseudorandom number generator — a 32-bit signed pseudorandom integer drawn from an internal stream. The signed-return trap is what motivates the entire upgrade chain to $urandom and $urandom_range.
The four functions:
$random— 32-bit signed; legacy form; uses hidden global seed unless$random(seed)form.$urandom— 32-bit unsigned; modern replacement for$random.$urandom_range(max, min)— uniform unsigned in[min, max]; the working engineer's default.$dist_*family — seven non-uniform distribution functions ($dist_normal,$dist_exponential,$dist_poisson,$dist_chi_square,$dist_t,$dist_erlang,$dist_uniform).
The seed contract:
- Bare
$randomuses a hidden global seed shared across all call sites. Not reproducible by default. $random(seed)uses an explicit seed variable, mutated in place. Reproducible by construction.- Each independent context should own its own seed variable — sharing couples streams.
- Pseudorandom algorithms are not standardised — same seed across vendors produces different streams. Reproducibility is vendor-scoped.
The reproducibility discipline:
- Read seed from
$value$plusargs("SEED=%d", seed)with a known default. - Log the seed at simulation start and on every failing event.
- One seed per context to keep streams independent of unrelated code changes.
The upgrade rule:
| Old pattern (avoid) | New pattern (prefer) |
|---|---|
$random % N | $urandom_range(N-1, 0) |
($random & mask) | $urandom_range(max, 0) for power-of-two only |
r = $random; if (r<0) r=-r; r %= N; | $urandom_range(N-1, 0) |
The day-to-day discipline:
$urandom_range(max, min)for bounded uniform. Never$random % N.- Read the seed from a plusarg, default to a known value, log it. Replayable regressions only.
- One seed per context. Independent streams.
$randomis not synthesisable. Wrap in\ifdef SIMULATION`. Use LFSRs for RTL pseudorandom.- Reach for
$dist_*when the workload demands a non-uniform shape. Uniform is overwhelmingly the common case.
The next three sub-pages close Chapter 8: 8.3 Time Functions covers $time / $realtime / $stime, 8.4 Conditional Simulation covers the assertion-and-severity layer ($error, $warning, $fatal), 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.
- $display vs $monitor vs $strobe vs $write — Chapter 8.1; the display family that prints randomised stimulus.
- Conditional Compilation — Chapter 7.4; the
\ifdef SIMULATION` discipline that gates every random call.