SystemVerilog Randomization: rand, randc and Constraints

Directed tests only check the cases you already thought of. The bugs that reach silicon are usually the ones you did not. SystemVerilog randomization is how a verification engineer hands that problem to the tool: you describe the space of legal stimulus with a class and a few constraints, and a built-in solver draws fresh, legal values on every call. This is the engine behind constrained-random verification (CRV) and, by extension, every UVM sequence you will ever write. We will build the full picture from first principles using small programs you can compile and run yourself.

Randomization lives inside classes. You tag data members with the rand or randc keyword, optionally pin down their legal values with constraint blocks, then call the built-in randomize() method on an object handle. The solver assigns every active random member a value that satisfies every active constraint simultaneously. It returns 1 on success and 0 when the constraints cannot all be met at once, so you should always check that return value.

rand versus randc

Both keywords make a member solvable, but they differ in how values are distributed over repeated calls. A rand member is drawn independently each time, roughly uniformly across its legal values, and the same value may appear on two calls in a row. A randc member is random-cyclic: the solver builds a random permutation of the member’s entire legal range and walks through it, so no value repeats until every value has been produced once. After the full cycle is exhausted, a new permutation begins.

Use randc when you want a short, guaranteed sweep of every possibility, for example cycling through all opcodes or all small address indices so no case is accidentally starved. The cost is that the solver must enumerate the whole range, so keep randc members narrow (a few bits). In the program below the randc b column produces all eight 3-bit values exactly once across eight iterations, while rand a is free to repeat.

Propertyrandrandc
RepetitionValues may repeat on any callNo repeat until the full range is covered
DistributionIndependent, roughly uniformRandom permutation of all legal values
Typical widthAnyKeep narrow (enumerates whole range)
Use forGeneral data, payloads, addressesExhaustive sweeps of small sets
// dice.sv - rand vs randc
module tb;
  class Dice;
    rand  bit [2:0] a;   // may repeat freely
    randc bit [2:0] b;   // visits all 8 values before repeating
    constraint c_a { a inside {[1:6]}; }
  endclass

  Dice d;
  int  ok;

  initial begin
    d = new();
    $display("iter | rand a | randc b");
    for (int i = 0; i < 8; i++) begin
      ok = d.randomize();
      if (ok == 0) $display("randomize failed at iter %0d", i);
      $display("  %0d  |   %0d    |   %0d", i, d.a, d.b);
    end
    $finish;
  end
endmodule
iter | rand a | randc b
  0  |   1    |   7
  1  |   2    |   5
  2  |   3    |   6
  3  |   2    |   3
  4  |   6    |   0
  5  |   2    |   4
  6  |   1    |   2
  7  |   1    |   1

Notice the constraint c_a restricts a to the range 1 to 6, turning an 8-value member into a legal-die roll. randc b has no constraint, so its cycle covers the full 0 to 7 set.

Calling randomize() and inline constraints

You rarely want to hard-code every scenario inside the class. The randomize() method accepts an inline with { ... } block whose constraints apply only to that one call, layered on top of the class constraints. This lets a sequence steer the object, for example forcing a particular mode, without editing or sub-classing the transaction. The constraints inside with are combined with the class constraints, and all of them must hold together.

Constraints are not procedural statements; they are relations the solver satisfies. One of the most useful is the implication operator ->, which reads “if the left side is true, the right side must hold.” In the example, a wrapping burst is limited to power-of-two lengths. The inline block then demands a wrapping burst with a length greater than 2, so the only values the solver can legally pick are 4 and 8.

// burst.sv - inline constraints and implication
module tb;
  class Burst;
    rand bit       is_wrap;
    rand bit [3:0] len;      // beats in the burst
    // wrapping bursts are restricted to legal power-of-two lengths
    constraint c_wrap { is_wrap -> len inside {1, 2, 4, 8}; }
  endclass

  Burst b;
  int   ok;

  initial begin
    b = new();
    // Force a wrapping burst and demand len > 2 with an inline "with" block.
    for (int i = 0; i < 4; i++) begin
      ok = b.randomize() with { is_wrap == 1; len > 2; };
      if (ok == 0) $display("randomize failed");
      $display("wrap=%0d len=%0d", b.is_wrap, b.len);
    end
    $finish;
  end
endmodule
wrap=1 len=4
wrap=1 len=8
wrap=1 len=4
wrap=1 len=8

Weighted distributions with dist

Uniform randomness wastes cycles on boring stimulus. The dist operator biases the draw toward the values you care about. Each item carries a weight, and two operators divide that weight differently. The := operator gives the stated weight to each value in the item, while :/ spreads the weight across the whole range so every value in it shares a slice. Weights are relative, not percentages, so READ := 6, WRITE := 3, IDLE := 1 simply means a 6:3:1 ratio.

In the class below, operation type is biased roughly 60/30/10, and length is biased so that short bursts of 1 to 4 beats dominate (weight 80 shared across that range) while the wide 5 to 255 band is rare (weight 20 shared across it). Weighted distributions are a cornerstone of realistic traffic shaping. Simulator support for dist varies, so treat this as a reference pattern to drop into your transaction class rather than a universally runnable snippet.

// txn.sv - weighted distribution with dist
module tb;
  typedef enum {READ, WRITE, IDLE} op_e;

  class Txn;
    rand op_e      op;
    rand bit [7:0] len;
    // READ 60%, WRITE 30%, IDLE 10%
    constraint c_op  { op dist {READ := 6, WRITE := 3, IDLE := 1}; }
    // small lengths very common, the wide band rare
    constraint c_len { len dist {[1:4] :/ 80, [5:255] :/ 20}; }
  endclass

  Txn t;
  int ok;

  initial begin
    t = new();
    ok = t.randomize();
    $display("op=%s len=%0d", t.op.name(), t.len);
  end
endmodule

pre_randomize and post_randomize

The solver calls two optional callbacks on every randomize(). pre_randomize() runs just before solving, and post_randomize() runs right after. You override them as plain void functions. Use pre_randomize() to set up guidance, such as copying in a mode flag or counting attempts, and use post_randomize() to compute fields that are derived from the random members rather than solved for, such as a CRC, a parity bit, or a formatted address.

The Frame class keeps parity and attempts outside the rand set. The pre-callback bumps the attempt counter, and the post-callback folds the payload bits with a reduction XOR to produce the parity bit. Because these run automatically, the derived values stay consistent with the randomized payload without any extra call in your test.

// frame.sv - pre_randomize and post_randomize hooks
module tb;
  class Frame;
    rand bit [3:0] payload;
    bit            parity;   // derived, not randomized
    int            attempts; // bookkeeping

    function void pre_randomize();
      attempts++;
      $display("pre : attempt #%0d starting", attempts);
    endfunction

    function void post_randomize();
      parity = ^payload;      // XOR reduction = parity bit
      $display("post: payload=%b parity=%b", payload, parity);
    endfunction
  endclass

  Frame f;
  int   ok;

  initial begin
    f = new();
    for (int i = 0; i < 3; i++) begin
      ok = f.randomize();
      if (ok == 0) $display("randomize failed");
    end
    $display("total attempts = %0d", f.attempts);
    $finish;
  end
endmodule
pre : attempt #1 starting
post: payload=1011 parity=1
pre : attempt #2 starting
post: payload=1100 parity=0
pre : attempt #3 starting
post: payload=0001 parity=1
total attempts = 3

Switching things off: constraint_mode and rand_mode

A single transaction class often serves many scenarios. Two built-in controls let you reshape it at run time without rewriting it. constraint_mode(0) called on a named constraint handle disables that constraint; constraint_mode(1) re-enables it. This is the clean way to inject illegal stimulus: turn off the legality constraint, randomize, and watch the design handle out-of-range input. rand_mode(0) called on a member turns off its randomization, so the member keeps its current value and the solver treats it as a fixed input while still solving the remaining members around it.

Follow the four stages in the program. With everything default, base stays within 0 to 15. After c_low.constraint_mode(0), base can use its full 8-bit range. After offset.rand_mode(0), the offset is frozen at whatever it last held while base is still randomized. Re-enabling both restores the original behaviour. Both methods also have a function form that returns the current state when you call them with no argument.

// addr.sv - constraint_mode and rand_mode control knobs
module tb;
  class Addr;
    rand bit [7:0] base;
    rand bit [7:0] offset;
    constraint c_low { base inside {[0:15]}; }  // keep base small
  endclass

  Addr a;
  int  ok;

  initial begin
    a = new();

    // 1) default: c_low active, both fields random
    ok = a.randomize();
    $display("default      : base=%0d offset=%0d", a.base, a.offset);

    // 2) switch off the c_low constraint -> base may use full range
    a.c_low.constraint_mode(0);
    ok = a.randomize();
    $display("c_low off    : base=%0d offset=%0d", a.base, a.offset);

    // 3) freeze offset with rand_mode(0): keep value, randomize the rest
    a.offset.rand_mode(0);
    a.base = 0;
    ok = a.randomize();
    $display("offset frozen: base=%0d offset=%0d (unchanged)", a.base, a.offset);

    // 4) re-enable both
    a.c_low.constraint_mode(1);
    a.offset.rand_mode(1);
    ok = a.randomize();
    $display("all restored : base=%0d offset=%0d", a.base, a.offset);
    if (ok == 0) $display("randomize failed");
    $finish;
  end
endmodule
default      : base=11 offset=16
c_low off    : base=99 offset=222
offset frozen: base=61 offset=222 (unchanged)
all restored : base=6 offset=4

Habits that keep you out of trouble

  • Always test the return value of randomize(). A silent 0 means an over-constrained class and stale values, which hides real coverage holes.
  • Prefer narrow randc members; a wide one forces the solver to enumerate an enormous range.
  • Put derived values in post_randomize(), never in the rand set, so they cannot contradict the solved members.
  • Reach for inline with and constraint_mode before you copy a class. Reusing one transaction keeps your environment small.

These five constructs, rand and randc, constraints and inline with, dist weights, the pre and post callbacks, and the two mode controls, are the complete vocabulary of stimulus generation in SystemVerilog. Everything a UVM sequence does is built on top of them.

Quick check

What is the key behavioural difference between rand and randc?

A rand member is drawn independently each call and may repeat immediately. A randc member is random-cyclic: it walks a random permutation of its entire legal range and never repeats a value until all values have been produced once.

In dist, how do the := and :/ operators differ?

With :=, the stated weight is given to each individual value in the item. With :/, the weight is divided across the whole range so the values in that range share it. For a single value the two are identical.

Where should a CRC or parity bit be computed, and why?

In post_randomize(). It is derived from the randomized members, not solved for, so computing it after randomization keeps it consistent with the payload. It should not be a rand member.

You want to inject an illegal address that your legality constraint normally forbids. What is the cleanest way?

Call constraint_mode(0) on that constraint’s handle to disable it for the next randomize(), then re-enable it with constraint_mode(1). No sub-classing or rewrite is needed.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD