Skip to content

SystemVerilog · Module 10

rand & randc Keywords

Property modifiers that mark class fields randomisable, the difference between independent draws and cyclic without-repeat, per-instance state, and the 8-bit ceiling on randc.

Module 10 · Page 10.2

Where rand and randc Live

Both rand and randc are property modifiers. They can only be applied to variables declared inside a class. You cannot use them on module-level variables, local task variables, or anywhere outside a class body.

SystemVerilog — Declaring rand and randc properties
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Packet;
 
    // ── rand: each randomize() call picks any legal value ─────────
    rand bit [7:0]  data;          // can be 0–255, any value, any time
    rand bit [1:0]  prio;          // can be 0, 1, 2, or 3 — repeated freely
 
    // ── randc: cycles through every value before repeating ────────
    randc bit [2:0] channel;       // visits 0,1,2,3,4,5,6,7 in random order
                                   // then starts a fresh cycle
 
    // ── non-random: not touched by randomize() ────────────────────
    bit valid;                     // stays whatever you last assigned
    int packet_id;                 // same — untouched by randomize()
 
endclass

The modifier goes before the data type. Any integral type works: bit, logic, int, byte, enum, and packed arrays. Real-valued types and strings cannot be randomised.

How rand Works

A rand variable behaves like a fair dice. Each call to randomize() picks a new value from the full legal range, independently of every previous value. The same value can appear back-to-back. There is no memory between calls.

SystemVerilog — rand behaviour demo
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class PrioGen;
    rand bit [1:0] prio;   // legal values: 0, 1, 2, 3
endclass
 
module tb;
    PrioGen g;
    initial begin
        g = new();
        repeat (8) begin
            void'(g.randomize());
            $display("prio = %0d", g.prio);
        end
    end
endmodule
 
// Possible output (order is random, repetitions are normal):
// prio = 3
// prio = 0
// prio = 3   ← same as call 1 — perfectly legal for rand
// prio = 1
// prio = 2
// prio = 0
// prio = 0   ← repeated again — still fine
// prio = 3

Repetitions are expected and valid with rand. If you need all values to appear before any repeats, that is exactly what randc is for.

How randc Works

A randc variable is a cyclic randomiser. Think of it as a bag containing every legal value. On each randomize() call it draws one value from the bag at random without replacement. Once the bag is empty it refills — starting a new cycle — and draws again. The guarantee is that every value appears exactly once per cycle.

SystemVerilog — randc behaviour demo
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class ChanGen;
    randc bit [1:0] ch;   // 4 legal values: 0, 1, 2, 3
endclass
 
module tb;
    ChanGen g;
    initial begin
        g = new();
        repeat (8) begin
            void'(g.randomize());
            $display("ch = %0d", g.ch);
        end
    end
endmodule
 
// Guaranteed output structure (specific order random):
// ── Cycle 1 ──────────────
// ch = 2     ┐
// ch = 0     │ all 4 values appear,
// ch = 3     │ no repeats within the cycle
// ch = 1     ┘
// ── Cycle 2 ──────────────
// ch = 3     ┐ a NEW random permutation
// ch = 1     │ starts automatically
// ch = 0     │
// ch = 2     ┘

Cycle boundary — the critical detail

When a new object is created with new(), the cycle always starts fresh. The solver internally tracks which values have already been used in the current cycle. If you create a second object of the same class, that object has its own independent cycle tracker.

rand vs randc — Side-by-Side

Propertyrandrandc
DistributionUniform random — any value any timeCyclic — all values once per cycle, random order
RepetitionsAllowed — same value can appear back-to-backNot within a cycle — guaranteed unique until cycle resets
Coverage guaranteeNone — some values may never be drawnEvery value hit within N calls (N = range size)
Max type widthAny integral widthTypically ≤ 8 bits (tool-dependent)
Works with constraints?Yes — fully constrainedYes — constrained cyclic
Typical useData payloads, addresses, delaysOpcodes, channels, short enums needing full coverage

Using rand and randc With Constraints

Both keywords work transparently with constraint blocks (covered in depth in page 10.4). The solver respects the constraint while still applying the rand or randc behaviour. For randc, the cycle is taken over the values that satisfy the active constraints, not the full type range.

SystemVerilog — randc with a constraint
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class OpcodeGen;
    randc bit [2:0] opcode;  // 8 legal values without constraint
 
    // Restrict to READ(0), WRITE(1), FETCH(4), FLUSH(5) only
    constraint valid_ops {
        opcode inside {3'h0, 3'h1, 3'h4, 3'h5};
    }
endclass
 
// Result: randc now cycles through {0, 1, 4, 5} in random order.
// Each cycle visits all four values exactly once — no more, no less.
// Values 2, 3, 6, 7 are never generated because the constraint excludes them.

Mixing rand, randc, and Non-Random Fields

A single class can have any combination of rand, randc, and plain (non-random) variables. The solver randomises all rand and randc fields together in one call. Non-random fields are untouched.

SystemVerilog — mixed class example
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class BusTransaction;
    // Randomised fields
    rand  bit [31:0]  addr;      // plain random address each call
    rand  bit [7:0]   data;      // plain random data each call
    randc bit [1:0]   burst_len; // cycles through 0,1,2,3 before repeating
 
    // Non-randomised fields — you set these manually
    int  trans_id;               // unaffected by randomize()
    bit  error_inject;          // unaffected by randomize()
 
    function void display();
        $display("[%0d] addr=%h data=%h burst=%0d err=%0b",
                 trans_id, addr, data, burst_len, error_inject);
    endfunction
endclass
 
module tb;
    BusTransaction t;
    initial begin
        t = new();
        t.trans_id     = 1;    // set manually before randomize()
        t.error_inject = 0;
        repeat (6) begin
            void'(t.randomize());   // addr, data, burst_len randomised
            t.display();            // trans_id and error_inject unchanged
        end
    end
endmodule

Disabling Randomisation — rand_mode()

You can temporarily switch a rand or randc property back to behaving like a plain variable — without removing the keyword from the class — using rand_mode().

SystemVerilog — rand_mode() usage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
BusTransaction t = new();
 
// Fix the address — stop randomize() from touching it
t.addr.rand_mode(0);          // 0 = OFF
t.addr = 32'hC000_0000;       // set it manually
 
void'(t.randomize());          // only data and burst_len randomised
                               // addr stays 0xC000_0000
 
// Re-enable when you want the solver to own it again
t.addr.rand_mode(1);          // 1 = ON
 
// You can also check the current mode
if (t.addr.rand_mode() == 0)
    $display("addr is not being randomised");

Common Pitfalls

Ignoring the return value of randomize()

randomize() returns 1 on success, 0 on failure (unsolvable constraints). Always capture it: assert(t.randomize()) or if (!t.randomize()) $fatal(...). Silently continuing with stale values is a hard-to-find bug.

Using randc on wide types

randc bit [15:0] x means 65 536 values per cycle. Most tools refuse this or silently degrade to rand. Keep randc to 8 bits or fewer.

Expecting randc to correlate across objects

Each object has its own cycle state. Two handles pointing to different objects will progress through their cycles independently. There is no shared state between objects of the same class.

Declaring rand on real or string

rand real x is illegal. Only integral and packed types are supported. For floating-point randomisation, use $urandom() and scale the result manually in post_randomize().

Quick Reference

SystemVerilog — rand & randc cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Declare ────────────────────────────────────────────────────
rand  <type> prop;          // uniform random, any width
randc <type> prop;          // cyclic random, max 8 bits recommended
 
// ── Randomise ──────────────────────────────────────────────────
assert(obj.randomize());   // randomise all rand/randc fields
 
// ── Disable / re-enable a single field ────────────────────────
obj.prop.rand_mode(0);     // OFF  — field behaves as plain variable
obj.prop.rand_mode(1);     // ON   — field randomised again
int m = obj.prop.rand_mode(); // query current mode (returns 0 or 1)
 
// ── Types that work ────────────────────────────────────────────
rand bit [N:0] p;   // packed array  ✓
rand int       p;   // signed int    ✓
rand byte      p;   // byte          ✓
// rand real   p;   // real          ✗ — illegal
// rand string s;   // string        ✗ — illegal

Verification Usage — Where rand and randc Pay Off in Real Testbenches

Every production transaction class makes deliberate choices about which fields get rand, which get randc, and which stay plain (driven from the test layer). Three recurring patterns dominate.

rand on every protocol-shaped scalar

Addresses, data words, byte enables, lengths, IDs — the bulk of a transaction's mutable state — all get rand. The solver picks each value independently per call, the constraint block shapes the legal space, and the test ends up with a uniformly distributed sample stream that exercises every legal combination over enough iterations.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class apb_xact extends uvm_sequence_item;
  `uvm_object_utils(apb_xact)
 
  rand bit [31:0] paddr;
  rand bit [31:0] pwdata;
  rand bit [3:0]  pstrb;
  rand bit        pwrite;
 
  constraint c_align { paddr[1:0] == 2'b00; }
  constraint c_strb  { pwrite -> pstrb == 4'b1111; }
endclass</code>

randc on small enums that need exhaustive cycling

Transaction kind enums (READ / WRITE / ATOMIC_RMW), error-code enums, agent IDs, and any small bounded field where the test plan says "every value must appear at least once" — these are the textbook randc use cases. The cyclic guarantee removes the "did we cover every value?" question from the test layer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>typedef enum bit [1:0] {
  T_READ, T_WRITE, T_ATOMIC_RMW, T_BARRIER
} xact_kind_e;
 
class apb_xact extends uvm_sequence_item;
  randc xact_kind_e kind;   // every kind appears before any repeat
  rand  bit [31:0]  paddr;
endclass</code>

Mixed rand + randc + plain fields

A realistic transaction class typically has a small number of randc fields (the structural choices), many rand fields (the payload), and a few non-rand fields driven directly from the test layer (sequence-specific overrides, replay metadata). The mixture is intentional: the test layer steers the small high-level choices; the solver fills in the payload.

Simulation Behavior — How the Solver Treats rand vs randc

rand — independent draws from the satisfying set

On every randomize() call, the solver computes the satisfying set (the cross-product of all active constraints projected onto each rand field) and draws one value from each field's slice independently. Successive calls have no memory of previous draws — duplicate values are possible at any cadence.

randc — stateful cycling without repeats

Each randc field carries a hidden bitmap of "values already drawn this cycle." On each call, the solver draws from the not-yet-drawn subset; when the bitmap is full, it resets and a new cycle begins. The state lives with the object — different instances of the same class have independent cycles.

State persists across randomize() calls but not new()

A randc field's bitmap is part of the object's state — constructed fresh on new() and updated on every successful randomize(). Two newly-constructed objects of the same class start their cycles from scratch. This is why "cycling through every transaction kind" requires re-using the same transaction handle across calls; constructing a new transaction per call resets the cycle and breaks the no-repeat guarantee.

Domain-size constraints on randc

The simulator must allocate state proportional to the satisfying-set size. Most implementations cap randc at 8-bit domains (256 values) and slow down dramatically above that. randc bit [31:0] is either silently downgraded to rand or rejected at elaboration — check your simulator's manual.

✅ Right tool for the field
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand  bit [31:0] paddr;   // wide payload — rand
  rand  bit [31:0] pwdata;
  randc bit [1:0]  kind;    // 4 values — randc
  randc bit [3:0]  err_id;  // 16 codes — randc
endclass</code>
❌ randc on a wide field — solver bloat
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  // 4 billion-entry state bitmap —
  // simulator either rejects or
  // silently degrades.
  randc bit [31:0] paddr;
endclass</code>

Waveform Analysis — Verifying rand vs randc Behaviour in Logs

rand and randc have no direct waveform footprint — they are class fields, not module signals. The way to verify they are behaving correctly is to log each randomised value at randomize() time and inspect the resulting stream.

Logging pattern that distinguishes rand from randc

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact extends uvm_sequence_item;
  rand  bit [31:0] paddr;
  randc bit [1:0]  kind;
 
  function void post_randomize();
    `uvm_info("RAND",
      $sformatf("paddr=0x%08h (rand)  kind=%0d (randc)", paddr, kind),
      UVM_HIGH)
  endfunction
endclass</code>

ASCII view — what to look for in the stream

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code> 100ns  RAND  paddr=0xDEAD_BEEF (rand)  kind=2 (randc)
 200ns  RAND  paddr=0x4321_CAFE (rand)  kind=0 (randc)
 300ns  RAND  paddr=0xDEAD_BEEF (rand)  kind=3 (randc)   ← rand repeats early
 400ns  RAND  paddr=0x7777_8888 (rand)  kind=1 (randc)
 500ns  RAND  paddr=0xAAAA_AAAA (rand)  kind=2 (randc)   ← randc starts new cycle
       ↑                                ↑
   freely repeats                  every value 0..3
   any time                        appears once before
                                   any repeat begins</code>

Diagnosing "randc not cycling" symptoms

If a randc field appears to repeat early, three likely causes: (a) a fresh object is being constructed per randomize() call, resetting the cycle; (b) the constraint set has narrowed the satisfying set to a single value (cycle length = 1); (c) the field is actually rand and the misperception comes from a misread declaration. Log $typename of the field's owning object plus a per-object call counter to disambiguate quickly.

Industry Insights — Hard-Won Lessons About rand and randc

Debugging Academy — Five Real Bugs From rand/randc Misuse

1

"My randc field repeats every other call"

DEBUG
Symptom

A randc bit [3:0] field is expected to cycle through 16 values before repeating. The log shows values 3, 7, 3, 11, 3, 5, 3, ... — the value 3 appears repeatedly within what should be one cycle.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  randc bit [3:0] kind;
endclass
 
task body();
  xact tx;
  for (int i = 0; i < 32; i++) begin
    tx = new();              // BUG: new object every call
    assert(tx.randomize());
    $display("kind=%0d", tx.kind);
  end
endtask</code>
Root Cause

Each tx = new() constructs a fresh object with its own cycle state. The randc bitmap resets every iteration, so the field's first draw is whatever the new object happens to land on — independently of previous objects.

Fix

Construct the transaction once outside the loop, then call randomize() repeatedly on the same object: xact tx = new(); for (int i = 0; i < 32; i++) begin assert(tx.randomize()); ... end. The single object's randc bitmap accumulates correctly across the loop, producing a true cycle.

2

"Simulator rejects randc bit [15:0] at elaboration"

DEBUG
Symptom

The class compiles fine in isolation but errors out at elaboration with "randc variable 'addr_low' has domain too large; reduce width or use rand."

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  randc bit [15:0] addr_low;   // BUG: 65,536-value cycle is too large
endclass</code>
Root Cause

The simulator must allocate a state vector proportional to the satisfying-set size — 65,536 entries for a 16-bit field with no constraint. Most simulators cap randc at 8 bits / 256 values to keep the state-tracking cost bounded.

Fix

Decide whether you actually need cycling. If the test plan needs "every 16-bit value once," partition into a randc bit [7:0] hi + rand bit [7:0] lo, then concatenate at use time. If you just want a uniform distribution, change to rand bit [15:0] — over enough iterations, every value will appear without the state-vector cost.

3

"rand field produces identical values across runs"

DEBUG
Symptom

Every run of the test produces the same sequence of paddr values: 0x4000_1234, 0x4000_5678, 0x4000_9ABC, .... CRV has degenerated into a directed test.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>// Regression runner just invokes:
//   simv +UVM_TESTNAME=apb_smoke_test
// — no seed varying
class xact;
  rand bit [31:0] paddr;
endclass</code>
Root Cause

Without an explicit seed plusarg, the simulator's PRNG starts from a default (often seed 1). Same seed → identical rand draws. The rand keyword is doing its job correctly; the test infrastructure isn't.

Fix

Add seed variation to the regression dispatcher: simv +UVM_TESTNAME=apb_smoke_test +ntb_random_seed=$RANDOM (or simulator-equivalent). Record the chosen seed in the test log header so failing runs are reproducible. UVM's standard report header prints the seed when invoked correctly.

4

"rand_mode(0) doesn't seem to disable the field"

DEBUG
Symptom

The test disables randomisation on a specific field with tx.field.rand_mode(0);, but the field continues to take on different values on each randomize() call.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [3:0] kind;
endclass
 
task body();
  xact tx = new();
  tx.kind.rand_mode(0);    // intended: freeze kind at its current value
  assert(tx.randomize());
  $display("kind=%0d", tx.kind);   // kind still varying — why?
endtask</code>
Root Cause

rand_mode(0) tells the solver to not randomise the field on subsequent randomize() calls — but the field keeps whatever value it had at the time rand_mode(0) was called. The first call here is on a freshly-constructed object, so kind is 0 (default-initialised), and stays 0 across every subsequent randomize() — which the developer interpreted as "still varying" but is in fact the same value every time.

Fix

The mechanic is working correctly; the developer's mental model was off. Verify by logging kind across multiple iterations — it will be 0 on every call. To freeze the field at a specific non-default value, assign first (tx.kind = 4'h7;) then call rand_mode(0). The field is now pinned at 7 across every subsequent randomize().

5

"rand string s; compiles but never randomises"

DEBUG
Symptom

A class declares rand string name; and the developer expects randomize() to produce random strings. The string stays empty every call.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class agent;
  rand string name;   // BUG: rand on string is silently ignored
endclass</code>
Root Cause

rand can only be applied to scalar types with a bounded domain — integral types, enums, and packed structures of those. The string type has unbounded length and is not legal for rand. Most simulators silently accept the declaration but never randomise the field; some emit a warning at elaboration that's easy to miss.

Fix

Either choose a fixed string from a constrained pool (rand bit [1:0] name_idx; constraint c { name_idx inside {[0:3]}; } string names[4] = '{"a","b","c","d"}; ... name = names[name_idx]; in post_randomize), or generate the string outside randomize() entirely from $urandom_range calls in a helper. rand on unbounded types is not a supported mechanism.

Interview Q&A — Twelve Questions You Will Be Asked

It marks a class property as randomisable. When the object's randomize() method is called, the constraint solver picks a value for that property — subject to any active constraints — drawn independently from the satisfying set. Without rand, the property is invisible to the solver and keeps its current value across randomize() calls.

Best Practices — Ten Rules for rand and randc Usage

  1. Default to rand for general randomisation. Use randc only when the test plan requires no-repeat cycling within a bounded set.
  2. Restrict randc to fields ≤ 8 bits. Wider domains hit simulator limits or degrade silently to rand behaviour.
  3. Pair every randc field with a one-bin-per-value coverpoint. The coverage proves the cyclic guarantee actually fires and catches accidental rand↔randc refactors.
  4. Reuse the same transaction object across iterations when you need randc cycling. Constructing a new object per call resets the cycle and silently breaks the no-repeat guarantee.
  5. Decide rand vs randc at class design time. Changing later breaks seed reproducibility and prior regression replays.
  6. Mark random fields as deliberately as you mark synthesisable signals. Twenty rand fields where only five vary is the OO equivalent of twenty always_ff blocks where only five are clocked.
  7. Never apply rand/randc to string, chandle, or other unbounded types. The compiler may accept the syntax, but the field will not randomise.
  8. Use rand_mode(0) to freeze a field at a specific value. Assign the value first, then call rand_mode(0) — otherwise the field freezes at whatever the default-init left behind.
  9. Always vary seeds across regression runs. A fixed seed turns a CRV test into a directed test; record the seed per failure for reproducibility.
  10. Log post_randomize() values during bring-up. Inspecting the actual generated stream is the only way to verify rand and randc are doing what you intended.