Skip to content

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
op = $urandom_range(7, 0);   // uniform unsigned 0..7

This 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

random-syntax.v — two call forms
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
integer r;
r = $random;          // bare call — uses an internal global seed
r = $random(seed);    // seeded call — uses the explicit seed, updates it

The 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 $random call can return negative values; this is the source of the % N distribution-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 $random calls 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    integer i;
    for (i = 0; i < 5; i = i + 1)
        $display("%0d", $random);   // uses hidden global seed
end

The 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))

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

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

random-modulo-bug.v — biased range restriction
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ranges

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
r = $random;
if (r < 0) r = -r;       // make positive
op = r % 8;              // now uniform 0..7

But 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):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
op = $urandom_range(7, 0);   // unsigned, uniform, by construction

Always prefer $urandom_range for bounded random values.

6. $urandom and $urandom_range

Verilog-2005 added the unsigned random variants.

6.1 $urandom

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ends

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

FunctionDistributionExtra argumentsUse case
$dist_uniformUniformstart, endSame as $urandom_range but Verilog-2001-portable
$dist_normalGaussianmean, std_devModelling jitter, noise
$dist_exponentialExponentialmeanInter-arrival times for traffic generation
$dist_poissonPoissonmeanEvent-count modelling
$dist_chi_squareChi-squaredegrees_of_freedomStatistical-test stimulus
$dist_tStudent's tdegrees_of_freedomStatistical-test stimulus
$dist_erlangErlangk_stage, meanQueueing-theory simulations

7.1 Example — Gaussian jitter stimulus

dist-normal-jitter.v — model clock jitter
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

dist-exponential-traffic.v — packet arrivals
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 flow
Randomization familyrandomization$-prefixed random funcs$randomsigned 32-bit, the legacy choice$urandomunsigned 32-bit$urandom_range(max,min)uniform bounded — preferred$dist_normal / _exponential / _poisson / …$dist_normal /_exponential /…7 non-uniform distributions
$urandom_range is the working engineer's default for bounded uniform draws. $random survives in legacy code; $urandom replaces it when you want full 32-bit unsigned. The $dist_* family covers non-uniform distributions.

8.2 Visual B — the negative-modulo bias

Why $random % 8 is biased and $urandom_range(7, 0) is not.

Distribution bias from signed modulo$randomsigned 32-bit% 8biased —implementation-defined$urandom_range(7,0)uniform — unbiasedskewed histogrambucket 0 over-representeduniform histogramall buckets equal12
Negative-modulo bias. $random returns signed values across the full 32-bit range. % 8 maps each value to an 8-wide bucket — but negative inputs map to negative outputs in Verilog, which then wrap when assigned to unsigned. Result: bucket 0 receives twice the mass (from -8, 0, +8 mod, ...) compared to buckets 1..7 which only see positive-input contributions. $urandom_range picks a uniform bucket directly with no modulo step.

8.3 Visual C — seed contract

Hidden global vs explicit per-context streams.

Hidden global seed vs explicit per-context seedalways @clk$randomalways @rst$randominitial$randomhidden global seedshared, interleavedinitial seed = 42$random(seedA)initial seed = 99$random(seedB)independent streamsdeterministic each12
Two streams. Bare $random shares a hidden global seed across every call — interleaving the consumption with no per-context guarantees. $random(seed) advances an explicit seed per call, giving each owning context its own deterministic stream. Production regressions use explicit seeds for reproducibility; debug snapshots can use bare $random for convenience.

9. The Reproducibility Discipline

Every working regression suite enforces three rules.

9.1 Always use an explicit seed in production tests

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    if (test_failed) begin
        $display("[FAIL] seed=%0d cycle=%0d ...", seed, $time);
        $finish(1);
    end
end

A failed regression-suite run that doesn't print the seed cannot be replayed. Always log it.

9.3 One seed per process / per context

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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();
end

Sharing 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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);
end

10.2 Random delays in handshake testbenches

Inserting random delays between handshake phases exposes timing bugs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    repeat (100) begin
        // Random think time before driving valid
        #($urandom_range(20, 1));
        valid <= 1;
        @(posedge clk iff ready);
        valid <= 0;
    end
end

10.3 Statistical-distribution stimulus

Modelling realistic traffic patterns requires non-uniform distributions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
end

10.4 Random opcode generation for ALU / CPU verification

Cover every opcode + operand combination:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
end

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Seed 42 in VCS produces sequence A
// Seed 42 in Xcelium produces sequence B — different algorithm

Pseudorandom algorithms are not standardised. Same seed across vendors produces different streams. Reproducibility is vendor-scoped.

11.5 $random in synthesisable RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x = $urandom_range(0, 7);    // ambiguous — depends on tool's swap behaviour
x = $urandom_range(7, 0);    // canonical — max first, min second

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

  1. $urandom_range(max, min) for bounded uniform draws. Never $random % N. The unsigned variant is uniform by construction.
  2. 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.
  3. One seed per independent context. Don't share a seed across always blocks that should produce independent streams.
  4. Use $random / $urandom only in testbench code. Wrap in `ifdef SIMULATION if the file is shared with RTL. Synthesisable pseudorandom generators use LFSRs, not system tasks.
  5. 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.
  6. Document the seed source. Comment whether a seed is a fixed magic number (reproducible smoke test), a $plusargs argument (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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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..99

Hints. 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).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

What 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
1Uniform random 8-bit byte
2Uniform random integer in [50, 100]
3Random delay 10-50 cycles, uniform
4Random delay with mean 20 cycles, exponentially distributed (memoryless)
5Clock jitter with mean 0, σ=25 ps, Gaussian
6Random 1-out-of-N selection with N=7
75% probability event injection
8Random 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 $random uses 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.
  • $random is 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.