Testbenches need varied input values to exercise a design well, and writing them all by hand is slow and misses the cases nobody thought of. The system function $random returns a new 32-bit signed random integer every time it is called, which can be positive or negative. With a little arithmetic, those numbers become values in any range a test needs.

10 min read | Beginner Level

What You'll Learn

  • How $random works, with and without a seed
  • Why the same seed always gives the same sequence, and why that is useful
  • How to get values in a full signed range, a positive range, or any range from min to max
  • Common mistakes that give negative, repeating or out-of-range values

Syntax


$random;           // Uses the simulator's internal seed
$random(seed);     // Uses and updates the given seed variable

The seed is optional. It controls which sequence of random numbers is produced: the same starting seed always produces the same sequence. The seed must be a variable of type reg, integer or time, assigned before the call, because $random also writes a new value back into it every time it is called. That new value is the starting point for the next call.

In the example below, the seed is set back to 5 before every call:


module tb;
  reg [3:0]  val;
  reg [31:0] seed;  // prefer 32b value
  integer    i;

  initial begin
    for (i = 0; i < 10; i = i + 1) begin
      seed = 5;

      val = $random(seed) % 3;
      $display ("i=%0d seed=%0d val=0x%0h val=%0d", i, seed, val, val);
    end
  end
endmodule
Output
xcelium> run
i=0 seed=345346 val=0xe val=14
i=1 seed=345346 val=0xe val=14
i=2 seed=345346 val=0xe val=14
i=3 seed=345346 val=0xe val=14
i=4 seed=345346 val=0xe val=14
i=5 seed=345346 val=0xe val=14
i=6 seed=345346 val=0xe val=14
i=7 seed=345346 val=0xe val=14
i=8 seed=345346 val=0xe val=14
i=9 seed=345346 val=0xe val=14
xmsim: *W,RNQUIE: Simulation is complete.

Every call starts from the same seed, 5, so every call returns the same number, and $random leaves the same updated seed, 345346, behind. The value 0xe is -2 in 4 bits: the random number was negative, and its remainder after division by 3 was -2 (see the full range example below).

When the seed is set only once, before the loop, each call continues from the seed left by the previous call, and every call returns a different value:


module tb;
  reg [3:0]  val;
  reg [31:0] seed;  // prefer 32b value
  integer    i;

  initial begin
    seed = 0;
    for (i = 0; i < 10; i = i + 1) begin
      val = $random(seed) % 3;
      $display ("i=%0d seed=%0d val=0x%0h val=%0d", i, seed, val, val);
    end
  end
endmodule
Output
xcelium> run
i=0 seed=2450862598 val=0x2 val=2
i=1 seed=1082744015 val=0xf val=15
i=2 seed=75814084 val=0x0 val=0
i=3 seed=837833973 val=0xe val=14
i=4 seed=2260302130 val=0x0 val=0
i=5 seed=3336541963 val=0x2 val=2
i=6 seed=851608272 val=0xe val=14
i=7 seed=154620049 val=0x0 val=0
i=8 seed=2163466526 val=0x0 val=0
i=9 seed=2262289159 val=0x1 val=1
xmsim: *W,RNQUIE: Simulation is complete.

The seed changes after every call, so the values change too. Run this simulation again and you get exactly the same ten values in the same order: the numbers look random, but they are completely determined by the starting seed. This is called a pseudo-random sequence.

Synthesis result: None. $random is a simulation-only function, and synthesis tools either ignore it or report an error. Hardware that needs random-looking numbers, such as a test pattern generator or a scrambler, uses a linear feedback shift register (LFSR) built from flip-flops and XOR gates.

Quick Check

Why does $random(5), with a constant instead of a variable, not work as a seed?

Show Answer

$random writes the updated seed back into its argument, so the argument must be a variable. A constant cannot be written, and the compiler reports an error.

Reproducing a Failing Random Test

Regressions run the same random testbench hundreds of times, each time with a different seed, usually passed on the command line and read with $value$plusargs. Every test prints its seed at the start. When one fails, the engineer reruns it with that same seed, and the simulation repeats exactly the same stimulus, so the bug can be debugged with waveforms and fixed, and the fix can be checked with the same seed.

Full Range Example

To limit the values to a range, take the remainder after division with the % operator. Because $random returns a signed number, the result of $random % N has the same sign as the random number, and lies between -(N-1) and N-1. For N = 2, the result is -1, 0 or 1.


module tb;
  reg [3:0] val;
  integer   i;

  initial begin
    for (i = 0; i < 10; i = i + 1) begin
      val = $random % 2;
      $display ("val=0x%0h val=%0d", val, val);
    end
  end

endmodule
Output
xcelium> run
val=0x0 val=0
val=0xf val=15
val=0xf val=15
val=0xf val=15
val=0x1 val=1
val=0x1 val=1
val=0xf val=15
val=0x0 val=0
val=0x1 val=1
val=0x1 val=1
xmsim: *W,RNQUIE: Simulation is complete.

The value 15 is -1: val is an unsigned 4-bit reg, and -1 in 4 bits is 4'b1111, which %d prints as 15. Whenever the random number was odd and negative, the remainder was -1.

Positive Range Example

Placing $random inside a concatenation, {$random}, turns the result into an unsigned 32-bit value, because a concatenation is always unsigned. The remainder is then never negative, and {$random} % N gives values from 0 to N-1.


module tb;
  reg [3:0] val;
  integer   i;

  initial begin
    for (i = 0; i < 10; i = i + 1) begin
      val = {$random} % 2;
      $display ("val=0x%0h val=%0d", val, val);
    end
  end

endmodule
Output
xcelium> run
val=0x0 val=0
val=0x1 val=1
val=0x1 val=1
val=0x1 val=1
val=0x1 val=1
val=0x1 val=1
val=0x1 val=1
val=0x0 val=0
val=0x1 val=1
val=0x1 val=1
xmsim: *W,RNQUIE: Simulation is complete.

The random numbers are the same as in the full range example; only the remainder changed. Every value that was 15 (-1) before is now 1.

Random Values in Any Range

Combining the positive range with an offset gives a value between any minimum and maximum:


// A value from MIN to MAX, both included
val = MIN + {$random} % (MAX - MIN + 1);

// Examples
delay = 1 + {$random} % 10;        // 1 to 10
addr  = 'h100 + {$random} % 'h40;  // 'h100 to 'h13F

Verilog also provides $dist_uniform(seed, start, end), which returns a value from start to end directly, and other distribution functions such as $dist_normal and $dist_exponential, for stimulus that should cluster around a typical value instead of being spread evenly.


integer seed, len;

initial begin
  seed = 42;
  len  = $dist_uniform(seed, 64, 1500);   // Packet length from 64 to 1500
end

Quick Check

Which values can val = 4 + {$random} % 4; take?

Show Answer

4, 5, 6 and 7. {$random} % 4 is 0 to 3, and adding 4 shifts the range up.

Random Delays Between Transactions

Real traffic does not arrive at perfectly regular intervals. Testbenches for bus interfaces and FIFOs insert random gaps, such as repeat (1 + {$random} % 8) @(posedge clk);, between transactions. Varying the gap from back-to-back to idle reveals bugs in flow control, such as a FIFO that overflows only when writes arrive faster than expected.

Common Beginner Mistakes

Mistake #1: Using $random % N where only positive values are expected

Wrong: addr = $random % 16;, expecting 0 to 15
Right: addr = {$random} % 16;

Why? $random is signed, so about half the results are negative, from -15 to -1. Assigned to an unsigned variable, they become large values, or, as a delay, very long waits.

Mistake #2: Resetting the seed before every call

Wrong: seed = 5; inside the loop, before each $random(seed)
Right: Set the seed once, before the loop, and let $random update it

Why? The same seed always gives the same number, so the "random" value never changes, as the first example shows.

Mistake #3: Passing the seed but never assigning it

Wrong: Declaring reg [31:0] seed; and calling $random(seed) without ever giving seed a value
Right: Assign the seed, preferably from a plusarg, before the first call

Why? An unassigned reg is x, and the results then depend on how the simulator treats an unknown seed. Always start from a known seed, and print it, so the test can be repeated.

Mistake #4: Off-by-one ranges

Wrong: val = MIN + {$random} % (MAX - MIN);
Right: val = MIN + {$random} % (MAX - MIN + 1);

Why? % K gives 0 to K-1, so without the + 1, MAX itself is never generated. Edge values are where bugs hide, so this mistake removes exactly the case that matters most.

Practice Exercise

Challenge: Random Stimulus with a Repeatable Seed

Write a testbench that reads a seed from the command line with +seed=<value>, using 1 if none is given, prints it, and then prints 5 random 8-bit values between 10 and 20, both included.

Hint

$value$plusargs("seed=%d", seed) returns 1 and fills seed if the plusarg was given. To use the seed variable and still get a positive result, write {$random(seed)}.

Solution

module tb;
  integer   seed, i;
  reg [7:0] val;

  initial begin
    if (!$value$plusargs("seed=%d", seed))
      seed = 1;
    $display ("Using seed %0d", seed);

    for (i = 0; i < 5; i = i + 1) begin
      val = 10 + {$random(seed)} % 11;     // 10 to 20
      $display ("val=%0d", val);
    end
  end
endmodule

Running the simulation twice with +seed=7 prints the same five values both times, while +seed=8 gives different ones. The range uses % 11 because there are 11 values from 10 to 20.

Summary

In this tutorial, you learned:

  • $random returns a 32-bit signed pseudo-random integer, and $random(seed) uses and updates a seed variable
  • The same starting seed always produces the same sequence, which makes failing random tests repeatable
  • $random % N gives -(N-1) to N-1, while {$random} % N gives 0 to N-1
  • MIN + {$random} % (MAX - MIN + 1) gives any range, and $dist_uniform does the same with a seed
  • $random is simulation-only; hardware uses an LFSR for pseudo-random numbers
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5