Skip to content

SystemVerilog · Module 10

The randomize() Method & Return Value

The single entry point between test layer and solver, the four canonical call patterns, the synchronous-on-host-CPU model, and why discarding the return value is the most common CRV bug.

Module 10 · Page 10.3

What randomize() Actually Does

Every class in SystemVerilog that has at least one rand or randc property inherits a built-in method called randomize(). You do not write it yourself — the language provides it automatically.

When you call obj.randomize(), the simulator hands all the randomisable fields over to a constraint solver. The solver finds values that satisfy every active constraint simultaneously, then assigns those values to the properties. If no constraints exist, any value in the legal type range is equally probable.

SystemVerilog — basic randomize() call
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Pkt;
    rand bit [7:0] data;
    rand bit [1:0] prio;
 
    constraint hi_prio { prio >= 2; }
endclass
 
module tb;
    Pkt p;
    initial begin
        p = new();
 
        // The solver picks data freely (0–255)
        // and prio from {2, 3} (satisfying the constraint)
        void'(p.randomize());
 
        $display("data=%0d  prio=%0d", p.data, p.prio);
    end
endmodule

Three things happen inside every randomize() call, in this exact order:

1 — pre_randomize()

Called automatically before the solver runs. Override it to set up state, adjust constraints, or log the call. Covered in depth on page 10.12.

2 — Constraint solver

Finds a legal assignment for all active rand and randc fields. Returns 0 if no solution exists.

3 — post_randomize()

Called automatically after the solver succeeds. Override it to compute derived fields, print the result, or patch values the solver cannot express. Covered on page 10.12.

The Return Value — Never Ignore It

randomize() is a function that returns an int: 1 on success, 0 on failure. It fails when the constraint solver cannot find any assignment that satisfies all active constraints simultaneously — a condition called constraint contradiction.

The single most common bug in CRV code is ignoring this return value. When randomize() fails and you continue, all the rand fields keep their previous values. Your test silently runs with stale data and produces results that look plausible but are meaningless.

Three ways to handle the return value

SystemVerilog — handling randomize() return value
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Pkt p = new();
 
// ── Option 1: assert() — recommended for testbenches ──────────
// Stops simulation immediately if randomize() returns 0.
// The error message points to this exact line.
assert(p.randomize()) else
    $fatal(1, "randomize() failed — check your constraints");
 
// ── Option 2: if-check — use when failure is recoverable ──────
if (!p.randomize()) begin
    $error("randomize() failed at time %0t", $time);
    // apply a safe default, retry, or skip this iteration
end
 
// ── Option 3: void cast — only when you are 100% sure it cannot
//   fail (no constraints, or trivially satisfiable ones).        
//   Using this carelessly is the bug everyone falls into.        
void'(p.randomize());    // silently discards return value

What constraint contradiction looks like

SystemVerilog — contradictory constraints (randomize returns 0)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class BadPkt;
    rand bit [3:0] x;
 
    // These two constraints are mutually exclusive — no x can satisfy both
    constraint c1 { x >  10; }   // x must be 11, 12, 13, 14, or 15
    constraint c2 { x <  5;  }   // x must be 0, 1, 2, 3, or 4
endclass
 
module tb;
    BadPkt b;
    initial begin
        b = new();
        assert(b.randomize()) else   // ← catches the 0 return value
            $fatal(1, "constraints are unsatisfiable");
    end
endmodule
 
// Without the assert, x keeps its previous value and the test runs
// silently with wrong data — the hardest kind of bug to find.

randomize() With Arguments

By default, randomize() solves all rand and randc fields at once. Passing variable names as arguments restricts randomisation to only those fields — all other rand fields behave as fixed, non-random constants for that one call and participate in constraint solving as known values.

SystemVerilog — randomize() with argument list
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Frame;
    rand bit [7:0]  src_addr;
    rand bit [7:0]  dst_addr;
    rand bit [15:0] length;
 
    constraint valid_len { length inside {[64:1518]}; }
endclass
 
Frame f = new();
 
// Randomise only length — src_addr and dst_addr keep current values
assert(f.randomize(length));
 
// Randomise only src_addr and dst_addr — length keeps current value
assert(f.randomize(src_addr, dst_addr));
 
// Randomise everything (normal call — no argument list)
assert(f.randomize());

randomize(null) — disable all randomisation

Passing null as the argument tells the solver to randomise nothing. All rand fields are treated as fixed. The solver still runs and checks constraints against the current values, returning 0 if they are violated. This is useful for validating that a manually set object state is constraint-legal before driving it.

SystemVerilog — randomize(null) to validate a manual assignment
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Frame f = new();
 
// Manually construct a corner-case frame
f.src_addr = 8'hFF;
f.dst_addr = 8'h00;
f.length   = 64;    // minimum valid Ethernet frame
 
// Check: does this state satisfy all active constraints?
if (!f.randomize(null))
    $error("Manually set values violate constraints");
else
    $display("Values are constraint-legal — driving the DUT");

std::randomize() — Randomising Without a Class

Sometimes you need a random value inside a task or function without creating a class for it. std::randomize() lets you randomise ordinary variables — even local ones — with inline constraints, all without declaring a class.

SystemVerilog — std::randomize() usage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb;
    initial begin
        int delay;
        bit [7:0] addr;
 
        // Randomise with an inline constraint
        assert(std::randomize(delay) with { delay inside {[5:20]}; })
            else $fatal(1, "randomize failed");
 
        $display("delay = %0d", delay);   // guaranteed 5–20
 
        // Multiple variables in one call
        assert(std::randomize(addr, delay) with {
            addr  != 8'h00;
            delay <  10;
        });
        $display("addr=%h  delay=%0d", addr, delay);
    end
endmodule
Featureobj.randomize()std::randomize()
Requires a class?Yes — called on a class objectNo — works on any variable
Constraint scopeClass constraint blocks + inline withInline with clause only
pre/post_randomize hooksYes — called automaticallyNo hooks
Typical useTransaction objects, full CRV flowQuick one-off random values in tasks/functions
Return value1 = success, 0 = failure1 = success, 0 = failure

Edge Cases Worth Knowing

No rand properties — randomize() always returns 1

If a class has no rand or randc properties, calling randomize() on it does nothing and returns 1. This is not an error. It is useful in base classes where derived classes add the rand fields — the base class can still be randomised safely.

Calling randomize() on a null handle crashes

Just like any method call on a null handle, calling randomize() before new() produces a null-handle runtime error and terminates simulation. Always allocate the object first.

Inheritance — derived class randomizes all fields

When a derived class calls randomize(), the solver randomises all rand fields from both the parent and the child class, and checks all active constraints from both. You do not need to call super.randomize() explicitly — it is already part of the built-in flow.

SystemVerilog — randomize() across inheritance
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class BasePkt;
    rand bit [7:0] src;
    constraint valid_src { src != 8'hFF; }
endclass
 
class ExtPkt extends BasePkt;
    rand bit [7:0] dst;
    constraint valid_dst { dst != 8'h00; }
endclass
 
ExtPkt e = new();
assert(e.randomize());
// Solver randomises BOTH src and dst,
// and checks BOTH valid_src and valid_dst.
// src will never be 0xFF.  dst will never be 0x00.

Complete Call Flow Diagram

Every randomize() call follows this exact sequence. Understanding it makes debugging constraint failures much faster.

StepWhat happensWhere to intervene
1pre_randomize() is called on the object (and its parent chain)Override to adjust constraints or set non-random fields before solving
2Solver collects all active constraints and all rand/randc fieldsUse constraint_mode(0) before the call to exclude specific constraints
3Solver finds a random satisfying assignment (or fails)Add or tighten constraints to guide the solver; check return value to catch failure
4On success, solver assigns values to the fieldsUse rand_mode(0) before the call to keep a field fixed
5post_randomize() is calledOverride to compute derived fields, pack structs, or log the transaction
6Function returns 1 (success) or 0 (solver failed at step 3)Always check the return value — never discard with void'() blindly

Quick Reference

SystemVerilog — randomize() cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Standard call — randomise all rand/randc fields ───────────
assert(obj.randomize()) else $fatal(1, "randomize failed");
 
// ── Randomise selected fields only ────────────────────────────
assert(obj.randomize(field_a, field_b));
 
// ── Validate current state against constraints ────────────────
assert(obj.randomize(null)) else $error("state violates constraints");
 
// ── Randomise with an inline constraint (with clause) ─────────
// (full detail on page 10.5)                                    
assert(obj.randomize() with { field_a < 10; });
 
// ── Randomise without a class ─────────────────────────────────
assert(std::randomize(x) with { x inside {[1:100]}; });
 
// ── Return value meaning ──────────────────────────────────────
// 1  →  solver succeeded, fields updated
// 0  →  solver failed, fields UNCHANGED (still hold previous values)

Verification Usage — How randomize() Shows Up in Real UVM Code

Across every UVM environment, randomize() calls cluster around four canonical patterns. Recognising them turns reading unfamiliar testbench code from a chore into a routine scan.

The standalone assertion form

The simplest and most defensive use: construct, randomise, abort on failure. UVM's uvm_create` + uvm_rand_send_with` macros do exactly this internally.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>apb_xact tx = apb_xact::type_id::create("tx");
if (!tx.randomize())
  `uvm_fatal("RAND", "tx failed to randomise — constraints unsatisfiable")
drive(tx);</code>

The inline-constraint form (with clause)

Adds per-call constraints on top of the class's built-in constraints — used by sequences to narrow the legal space to the scenario this test wants to exercise. The class stays generic; the test layer steers.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>// Sequence wants only writes to a specific address range
if (!tx.randomize() with { pwrite == 1;
                            paddr inside {[32'h4000_0000 : 32'h4000_00FF]}; })
  `uvm_fatal("RAND", "scenario-specific constraint set is infeasible")
drive(tx);</code>

The named-field form (randomize(args))

Randomise only specific fields, leaving every other rand field at its current value. Common in sequence libraries where the test layer has already chosen the transaction kind and only the payload should vary.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>tx.kind = T_WRITE;                          // test layer fixes the kind
tx.paddr = 32'h4000_0010;                   // test layer fixes the addr
if (!tx.randomize(pwdata, pstrb))           // solver fills only payload
  `uvm_fatal("RAND", "payload solve failed")
drive(tx);</code>

The std::randomize form (module-scope ad-hoc)

For randomising local module-scope variables without wrapping them in a class. Useful for testbench infrastructure (random clock skews, random reset durations) but not for transaction-level stimulus — the class form is always the right tool there.

Simulation Behavior — What the Solver Does on Every Call

Constraint set assembly

At call time, the solver collects every active constraint visible from the object: class-defined constraint blocks (unless disabled by constraint_mode(0)) plus the inline with clause (if any). Constraints from parent classes flow into the set; the satisfying region is the intersection of every constraint's allowed values.

Solve attempt

The solver searches for a satisfying assignment using an implementation-defined algorithm — typically a hybrid of BDD enumeration, interval propagation, and randomised backtracking. The search terminates when (a) a satisfying assignment is found (return 1, fields written, post_randomize() called) or (b) every possible assignment has been ruled out (return 0, fields unchanged, post_randomize() NOT called).

The pre_randomize / post_randomize hooks

Before the solver runs, the simulator invokes pre_randomize() — your last chance to set non-rand fields or call rand_mode/constraint_mode for this specific call. After a successful solve, it invokes post_randomize() — useful for derived-field computation (e.g., setting a CRC field from the just-randomised payload). On a failed solve, post_randomize() is skipped, which is occasionally itself the bug (downstream code assumed it ran).

Synchronous, no time advance

The entire solve runs synchronously on the host CPU. $time does not change, no events fire, no other process is scheduled. A 10 ms solver run consumes 10 ms of wall-clock simulation time but zero simulated time.

✅ Defensive — checks return value
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>if (!tx.randomize() with { paddr inside {map_a}; }) begin
  `uvm_fatal("RAND",
    $sformatf("tx solve failed; constraints: %s",
              tx.get_active_constraints_string()))
end
drive(tx);   // tx fields are guaranteed valid</code>
❌ Discards return — solver failure invisible
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>tx.randomize() with { paddr inside {map_a}; };
drive(tx);
// If the solve failed, drive() sends whatever
// stale fields tx had — usually zeros from new() —
// and the DUT either crashes or silently
// miscompares.</code>

Waveform Analysis — Tying randomize() Calls to Bus Activity

randomize() is a method call — it does not produce waveform signals on its own. Visibility comes from instrumenting the call site and tying the constrained values to the transaction ID that later appears on the bus.

The instrumentation pattern

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class apb_xact extends uvm_sequence_item;
  static int next_id = 0;
  int        id;
 
  rand bit [31:0] paddr;
  rand bit [31:0] pwdata;
 
  function new(string name = "apb_xact");
    super.new(name);
    id = next_id++;
  endfunction
 
  function void post_randomize();
    `uvm_info("RAND",
      $sformatf("#%0d  paddr=0x%08h  pwdata=0x%08h", id, paddr, pwdata),
      UVM_HIGH)
  endfunction
endclass</code>

ASCII view — call site and bus correlated

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>  90ns  RAND  #1  paddr=0x4000_0010  pwdata=0xDEAD_BEEF
 100ns  DRV   #1  bus drive starts10ns later
 140ns  MON   #1  observed on bus, addr=0x4000_0010 OK
 200ns  RAND  #2  paddr=0x4000_0044  pwdata=0x0000_0000
 210ns  DRV   #2  bus drive starts
        ↑                          ↑
   solver finishes,          monitor sees the same
   logs constrained values   addr the solver chose</code>

Diagnosing "DUT sees different values than the solver picked"

If the RAND log shows paddr=0x4000_0010 but the bus waveform shows 0x0000_0000, the bug is between post_randomize and drive — typically a sequence-layer field overwrite, a copy-without-handle bug, or a missed $cast downstream. The transaction ID in both log lines makes correlation unambiguous; without it, you would be guessing.

Industry Insights — Hard-Won Lessons About randomize()

Debugging Academy — Five Real Bugs From randomize() Misuse

1

"Test passes but coverage shows nothing was driven"

DEBUG
Symptom

A test runs cleanly with no errors and a "PASS" verdict; the coverage report shows 0% on every bin. The DUT was apparently never exercised.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>task body();
  for (int i = 0; i < 100; i++) begin
    apb_xact tx = apb_xact::type_id::create("tx");
    tx.randomize() with { paddr inside {[32'hA000:32'hBFFF]}; };  // BUG: return discarded
    drive(tx);
  end
endtask</code>
Root Cause

The class has an existing constraint requiring paddr inside {[32'h4000:32'h7FFF]}. The inline with clause's range never intersects with that — every randomize call returns 0, fields stay at zero (from new()), drive sends 100 identical zero-address transactions which the DUT silently ignores.

Fix

Add the assertion: if (!tx.randomize() with { paddr inside {[32'hA000:32'hBFFF]}; }) uvm_fatal("RAND", "infeasible");`. The fatal aborts on the first failure with a useful diagnostic, instead of silently passing 100 broken iterations.

2

"post_randomize setup never ran — derived field is wrong"

DEBUG
Symptom

A transaction class computes a CRC field in post_randomize(); the DUT rejects every transaction as "CRC mismatch." The CRC field is always 0.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [31:0] payload;
  bit  [31:0] crc;
  constraint c { payload inside {[1:32'hFFFE]}; }   // excludes 0 and 0xFFFF
  function void post_randomize();
    crc = compute_crc(payload);
  endfunction
endclass
 
task body();
  xact tx = new();
  tx.randomize() with { payload == 0; };   // BUG: 0 is excluded by c
  drive(tx);
endtask</code>
Root Cause

The with clause requires payload == 0 but constraint c excludes it; the solve fails, randomize() returns 0, post_randomize() is skipped (not called on a failed solve), crc stays at its post-construction default of 0. The DUT computes the real CRC over the (unchanged) payload, compares against 0, mismatches.

Fix

Two-step. First, assert the return value so the failure surfaces immediately. Second, recognise that post_randomize is a successor hook — if your derived-field computation depends on a successful solve, the assertion gates the rest of the logic correctly. If you want a fallback that runs regardless, compute it explicitly after the call, not in post_randomize.

3

"randomize(field) randomises every field, not just the named one"

DEBUG
Symptom

A sequence sets tx.kind = T_READ and calls tx.randomize(paddr), expecting only paddr to change. Both kind and paddr end up randomised.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand xact_kind_e kind;
  rand bit [31:0]  paddr;
endclass
 
task body();
  xact tx = new();
  tx.kind = T_READ;
  if (!tx.randomize(paddr)) `uvm_fatal("RAND","");
  // tx.kind has been re-randomised — why?
endtask</code>
Root Cause

Actually the call signature randomize(paddr) is what was intended — the solver randomises only the named arguments. If kind changed, the test layer is doing something else (e.g., a pre_randomize() that re-randomises it, or a separate code path before this loop). Verify by logging kind before and after the call; in the canonical form here, kind retains T_READ.

Fix

Confirm the diagnostic before changing code. If you observe kind changing, search for pre_randomize or other writes between the assignment and the call. The named-argument form genuinely does limit the solve scope to the named fields; the problem is elsewhere.

4

"std::randomize on module-scope variable fails to compile"

DEBUG
Symptom

Code calls std::randomize(a, b) on two local integers; the compiler errors with "std::randomize requires standalone scope, not a class context."

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class my_test extends uvm_test;
  function void config_phase();
    int a, b;
    if (!std::randomize(a, b) with { a inside {[0:99]}; b inside {[0:99]}; })
      `uvm_fatal("RAND","");
    // some simulators reject std::randomize inside a class method
  endfunction
endclass</code>
Root Cause

std::randomize is designed for module-scope or program-scope code that wants ad-hoc randomisation without writing a class. Some simulators accept it inside a class context, others reject it (or warn). Inside a class, the canonical form is to declare the variables as class properties with rand and call this.randomize() — that's what classes are for.

Fix

Either (a) move the std::randomize call to a module/program scope outside the class, or (b) refactor: add rand int a, b; as class properties, then if (!this.randomize() with {...}) .... The class form is more portable across simulators and integrates with the existing constraint/coverage infrastructure.

5

"Constraint solver is at 80% CPU — single test takes 30 minutes"

DEBUG
Symptom

Profiler shows randomize() consuming 80% of host CPU; a regression that previously ran in 5 minutes per test now takes 30 minutes.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class burst_xact;
  rand bit [31:0] addr;
  rand bit [3:0]  len;
  rand bit [31:0] data[];
 
  constraint c_size { data.size() == len + 1; }
  constraint c_data { foreach (data[i])
                        data[i] dist { 0          := 5,
                                       [1:32'hFFFE] := 90,
                                       32'hFFFF_FFFF := 5 }; }
endclass</code>
Root Cause

The foreach with a dist on each array element forces the solver to compute the joint distribution across all elements simultaneously. The cost scales super-linearly with array size; a 16-element burst can take 100x longer than a 2-element burst.

Fix

Move the per-element randomisation out of the constraint block and into post_randomize(): solve addr and len declaratively, then size data and assign each element with $urandom_range in post_randomize. The procedural per-element assignment is 100x faster and produces the same statistical distribution. The solver only handles the truly constrained fields.

Interview Q&A — Twelve Questions You Will Be Asked

It is a built-in method on every class. When called, it invokes the constraint solver to find an assignment of all rand and randc fields that satisfies every active constraint, writes those fields on success, and returns 1 (success) or 0 (failure). Without a randomize() call, the object's random fields keep their previous values.

Best Practices — Ten Rules for Disciplined randomize() Usage

  1. Always wrap randomize() in an assertion or if. Discarded return values are the single most common CRV bug. Use assert(tx.randomize()) or UVM's ``uvm_do_with` macro.
  2. Use the named-argument form when the test layer has fixed some fields. tx.randomize(payload) randomises only the payload, leaving prior assignments to kind/addr intact.
  3. Reach for the inline with clause for per-scenario constraints. Don't add scenario-specific constraints to the class — keep the class generic, let the test layer narrow per call.
  4. Treat every solver failure as a bug, not noise. "Intermittent randomize failures" accumulating in regression dashboards mean a real constraint-set issue or a real test-design issue; investigate both.
  5. Compute derived fields in post_randomize(), but understand it's skipped on failure. If your derived field needs to exist regardless of solve outcome, compute it explicitly after the call.
  6. Log the constrained values from post_randomize() during bring-up. The transaction ID + field values is the single most useful debug signal for "what did the solver actually pick?"
  7. Profile randomize() cost when run-time grows. Tangled constraints — especially foreach over arrays with dist on each element — dominate. Move per-element work to post_randomize.
  8. Use UVM's ``uvm_do_with` macro for sequence-level transaction generation. It bundles create + randomize + send with the assertion built in — consistent across the codebase.
  9. Reserve std::randomize for module-scope ad-hoc use. For transaction-level stimulus, classes plus constraint blocks are the right tool.
  10. Pass distinct seeds to every regression run. Without seed variation, a CRV test is a slow directed test. Record the seed per failure for reproducibility.