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.
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()
endclassThe 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.
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 = 3Repetitions 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.
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
| Property | rand | randc |
|---|---|---|
| Distribution | Uniform random — any value any time | Cyclic — all values once per cycle, random order |
| Repetitions | Allowed — same value can appear back-to-back | Not within a cycle — guaranteed unique until cycle resets |
| Coverage guarantee | None — some values may never be drawn | Every value hit within N calls (N = range size) |
| Max type width | Any integral width | Typically ≤ 8 bits (tool-dependent) |
| Works with constraints? | Yes — fully constrained | Yes — constrained cyclic |
| Typical use | Data payloads, addresses, delays | Opcodes, 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.
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.
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
endmoduleDisabling 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().
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
// ── 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 ✗ — illegalVerification 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.
<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.
<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.
<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><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
<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
<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
"My randc field repeats every other call"
DEBUGA 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.
<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>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.
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.
"Simulator rejects randc bit [15:0] at elaboration"
DEBUGThe class compiles fine in isolation but errors out at elaboration with "randc variable 'addr_low' has domain too large; reduce width or use rand."
<code>class xact;
randc bit [15:0] addr_low; // BUG: 65,536-value cycle is too large
endclass</code>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.
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.
"rand field produces identical values across runs"
DEBUGEvery run of the test produces the same sequence of paddr values: 0x4000_1234, 0x4000_5678, 0x4000_9ABC, .... CRV has degenerated into a directed test.
<code>// Regression runner just invokes:
// simv +UVM_TESTNAME=apb_smoke_test
// — no seed varying
class xact;
rand bit [31:0] paddr;
endclass</code>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.
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.
"rand_mode(0) doesn't seem to disable the field"
DEBUGThe 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.
<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>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.
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().
"rand string s; compiles but never randomises"
DEBUGA class declares rand string name; and the developer expects randomize() to produce random strings. The string stays empty every call.
<code>class agent;
rand string name; // BUG: rand on string is silently ignored
endclass</code>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.
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
- Default to
randfor general randomisation. Userandconly when the test plan requires no-repeat cycling within a bounded set. - Restrict
randcto fields ≤ 8 bits. Wider domains hit simulator limits or degrade silently torandbehaviour. - Pair every
randcfield with a one-bin-per-value coverpoint. The coverage proves the cyclic guarantee actually fires and catches accidental rand↔randc refactors. - Reuse the same transaction object across iterations when you need
randccycling. Constructing a new object per call resets the cycle and silently breaks the no-repeat guarantee. - Decide
randvsrandcat class design time. Changing later breaks seed reproducibility and prior regression replays. - Mark random fields as deliberately as you mark synthesisable signals. Twenty
randfields where only five vary is the OO equivalent of twentyalways_ffblocks where only five are clocked. - Never apply
rand/randctostring,chandle, or other unbounded types. The compiler may accept the syntax, but the field will not randomise. - Use
rand_mode(0)to freeze a field at a specific value. Assign the value first, then callrand_mode(0)— otherwise the field freezes at whatever the default-init left behind. - Always vary seeds across regression runs. A fixed seed turns a CRV test into a directed test; record the seed per failure for reproducibility.
- Log
post_randomize()values during bring-up. Inspecting the actual generated stream is the only way to verifyrandandrandcare doing what you intended.