SystemVerilog · Module 10
Randomising Arrays
Fixed and dynamic array rand, the mandatory size constraint, object-handle array pre_randomize construction, and the associative-array workaround via parallel key/value lists.
Module 10 · Page 10.11
Which Array Types Can Be rand
| Array type | Can be rand? | Size randomisable? | Notes |
|---|---|---|---|
| Fixed-size | Yes | No — size fixed at compile time | All elements randomised |
| Dynamic array | Yes | Yes — constrain .size() | Solver allocates and fills in one call |
Queue ([$]) | Yes | Yes — constrain .size() | Behaves like dynamic array for randomisation |
| Associative array | No | N/A | Cannot be declared rand |
Fixed-Size Arrays
A fixed-size rand array is the simplest case. All elements are randomised in a single randomize() call. Use foreach (page 10.10) or the unique{} constraint to shape element values.
class RegWrite;
rand bit [31:0] data[4]; // 4 random 32-bit words
rand bit [3:0] strobe[4]; // 4 random byte-enable strobes
// Each strobe must be non-zero
constraint valid_strobe {
foreach (strobe[i]) strobe[i] != 4'h0;
}
// All data words must be distinct
constraint unique_data {
unique { data }; // unique{} — solver-efficient all-distinct
}
endclass
RegWrite rw = new();
assert(rw.randomize());
// data[0..3] are four distinct 32-bit values
// strobe[0..3] each non-zeroThe unique{} Constraint
unique { arr } is a built-in constraint that guarantees all elements of the array are distinct in a single, solver-efficient declaration. It is far more efficient than the O(N²) nested foreach approach from page 10.10 and should be preferred for any array where all elements must be different.
class UniqueDemo;
rand bit [7:0] ids[6];
rand bit [7:0] x, y, z;
// ── All elements of an array distinct ─────────────────────────
constraint arr_unique {
unique { ids };
}
// ── Mix array elements and scalar variables ───────────────────
// ids[0] through ids[5] AND x AND y AND z all distinct
constraint all_unique {
unique { ids[0], ids[1], ids[2], x, y, z };
}
// ── Unique within a subset of elements ───────────────────────
constraint first_four {
unique { ids[0], ids[1], ids[2], ids[3] };
// ids[4] and ids[5] may duplicate others
}
endclassDynamic Arrays — Size + Content Together
For dynamic arrays, the solver randomises both the size and all element values in a single randomize() call. The .size() method call inside a constraint is treated as a special rand variable representing the array's length.
class DmaBuf;
rand bit [31:0] payload[]; // dynamic — size and content both random
// Constrain the size
constraint size_c {
payload.size() inside {[4:16]}; // 4 to 16 words
}
// Constrain the content
constraint content_c {
unique { payload }; // all distinct words
foreach (payload[i])
payload[i] != 32'h0000_0000; // no null entries
}
endclass
DmaBuf buf = new();
assert(buf.randomize());
$display("size = %0d", buf.payload.size()); // 4–16Queues as rand Arrays
Queues ([$]) behave identically to dynamic arrays for randomisation purposes. The same size and content constraint patterns apply.
class TxQueue;
rand bit [7:0] frames[$]; // queue — size and content both random
constraint size_c {
frames.size() inside {[1:8]};
}
constraint valid_frames {
foreach (frames[i]) frames[i] != 8'h00;
}
endclassArrays of Class Objects
An array of class handles where each element points to a rand-capable object is a powerful pattern. Calling randomize() on the outer class does not automatically randomise the nested objects — you must call randomize() on each element explicitly, typically in post_randomize().
class Descriptor;
rand bit [31:0] addr;
rand bit [15:0] len;
constraint valid {
addr[1:0] == 2'b00;
len inside {[64:4096]};
}
endclass
class ScatterGather;
rand int num_descs; // how many descriptors
Descriptor descs[]; // NOT rand — objects randomised manually
constraint count_c {
num_descs inside {[1:8]};
}
// After the scalar fields are solved, allocate and randomise each descriptor
function void post_randomize();
descs = new[num_descs];
foreach (descs[i]) begin
descs[i] = new();
assert(descs[i].randomize());
end
endfunction
endclass
ScatterGather sg = new();
assert(sg.randomize());
// sg.num_descs: 1–8 (random)
// sg.descs[0..N-1]: each independently randomised, aligned, valid lengthPractical Pattern — DMA Scatter-Gather with Non-Overlapping Regions
class DmaDesc;
rand bit [31:0] start_addr[4];
rand bit [15:0] length[4];
// All start addresses in DMA-capable region
constraint in_region {
foreach (start_addr[i])
start_addr[i] inside {[32'h2000_0000:32'h2FFF_FFFF]};
}
// All start addresses word-aligned
constraint aligned {
foreach (start_addr[i]) start_addr[i][1:0] == 2'b00;
}
// Lengths reasonable
constraint len_c {
foreach (length[i]) length[i] inside {[64:4096]};
}
// Non-overlapping: each region ends before the next one starts
constraint no_overlap {
foreach (start_addr[i])
if (i > 0)
start_addr[i] >= start_addr[i-1] + length[i-1];
}
endclassCommon Pitfalls
Unsized dynamic array with unique{}
If the array size is unconstrained, the solver may pick size 0 (unique is trivially satisfied on an empty array) or a size that exceeds the element type's cardinality. Always bound the size.
Randomising object handles, not objects
An array of class handles declared without rand is just an array of pointers. The solver randomises the outer class's scalar fields but not the pointed-to objects. Use post_randomize() to randomise each element.
unique{} on arrays with too-small element type
unique { arr } with a 4-bit element type and an array of size 17 is unsatisfiable (only 16 distinct 4-bit values exist). The solver returns 0 silently if you don't check the return value.
Associative arrays cannot be rand
rand bit [7:0] map[string] is illegal. Randomise a dynamic array instead and build the associative array from it in post_randomize().
Quick Reference
// ── Fixed-size rand array ─────────────────────────────────────
rand bit [7:0] arr[N];
// ── Dynamic rand array ────────────────────────────────────────
rand bit [7:0] arr[];
constraint sz { arr.size() inside {[lo:hi]}; }
// ── Queue ─────────────────────────────────────────────────────
rand bit [7:0] q[$];
constraint sz { q.size() inside {[lo:hi]}; }
// ── unique{} — all elements distinct (preferred over nested foreach)
constraint c { unique { arr }; }
constraint c { unique { arr[0], arr[1], x, y }; } // mix array+scalars
// ── Element-wise constraint ───────────────────────────────────
constraint c { foreach (arr[i]) arr[i] inside {[lo:hi]}; }
// ── Array of objects — randomise in post_randomize() ─────────
function void post_randomize();
foreach (objs[i]) begin
objs[i] = new();
assert(objs[i].randomize());
end
endfunction
// ── Associative arrays: NOT rand — build in post_randomize() ─Verification Usage — Where Array Randomisation Lives in Real Testbenches
Array randomisation shows up in three architectural roles across every modern bus VIP and every protocol-stack testbench. Each role uses different array kinds and idioms.
Burst payload arrays — fixed or dynamic size with per-beat rules
The canonical use: a burst transaction holds an array of beat-level data, byte strobes, and metadata; size is constrained to legal burst lengths; each beat must satisfy per-beat protocol rules.
<code>class axi_xact extends uvm_sequence_item;
rand bit [7:0] len; // 0..255 → 1..256 beats
rand bit [31:0] data[];
rand bit [3:0] strb[];
constraint c_size { data.size() == len + 1;
strb.size() == len + 1;
len inside {[0:15]}; } // limit bursts to 16 beats
constraint c_per_beat {
foreach (data[i]) {
data[i] inside {[1:'hFFFE]}; // avoid reserved patterns
strb[i] != 4'h0; // each beat strobes
}
}
endclass</code>Address-list arrays — distinct or monotonic sequences
Cache-stress and arbitration tests need lists of addresses with structural properties — all distinct, monotonically increasing, evenly spaced. unique and inter-element foreach express these.
<code>class addr_list_xact;
rand bit [31:0] addrs[];
constraint c_size { addrs.size() inside {[8:64]}; }
constraint c_props {
unique { addrs }; // no two equal
foreach (addrs[i]) {
addrs[i] inside {[32'h4000_0000:32'h4000_FFFF]};
addrs[i][1:0] == 2'b00; // aligned
}
}
endclass</code>Object-handle arrays — sub-transactions held by a parent
Complex transactions like AXI multi-stream or NVMe SQE/CQE batches hold arrays of child transaction objects. The parent's pre_randomize constructs the children; the parent's randomize randomises both its own fields and the children's fields jointly.
<code>class burst_xact;
rand int unsigned beat_count;
rand beat_xact beats[]; // sub-objects
function void pre_randomize();
if (beats == null) beats = new[beat_count];
foreach (beats[i])
if (beats[i] == null)
beats[i] = beat_xact::type_id::create($sformatf("beat_%0d", i));
endfunction
constraint c_count { beat_count inside {[1:16]};
beats.size() == beat_count; }
endclass</code>Simulation Behavior — How the Solver Handles Each Array Kind
Fixed-size arrays
The simplest case: size is fixed at declaration; the solver only deals with element values. Each element acts like an independent scalar rand field unless coupled by inter-element constraints. Cost scales linearly with size for independent constraints.
Dynamic arrays
The solver picks both size and element values jointly. If size is itself a rand field constrained against the array's .size(), the solver iteratively explores candidate sizes — picks one, expands the foreach, attempts the joint solve; if infeasible, picks a different size. The cost grows with the number of viable sizes the solver explores.
Queues
Same as dynamic arrays for the solver — both are size-variable element containers. The implementation distinction (linked list vs contiguous buffer) is invisible to the constraint solver.
Associative arrays — not directly randomisable
rand on an associative array is illegal. The keys aren't a bounded set the solver can enumerate. The workaround is to randomise a fixed/dynamic key-list and a value-list, then build the associative array procedurally in post_randomize.
Object-handle arrays
The solver randomises each element's fields independently (subject to constraints), but it does not construct the objects. The contract is: pre_randomize ensures every element handle points to a real object; the solver then operates on the existing objects' fields. Forgetting to construct produces "randomise null pointer" behaviour — typically a silent no-op, sometimes a runtime error.
<code>class burst;
rand int beat_count;
rand beat_xact beats[];
function void pre_randomize();
if (beats == null) beats = new[beat_count];
foreach (beats[i])
if (beats[i] == null)
beats[i] = beat_xact::type_id::create(
$sformatf("beat_%0d", i));
endfunction
endclass</code><code>class burst;
rand int beat_count;
rand beat_xact beats[];
// No pre_randomize → beats[i] are all null
// randomize() silently does nothing useful
endclass
// Caller has to remember:
b = new();
b.beats = new[8]; // size
foreach (b.beats[i])
b.beats[i] = beat_xact::new(); // construct
b.randomize();</code>Waveform Analysis — Tracing Generated Array Content
Randomised arrays produce a stream of N values per randomize() call. Verifying the generation matches the constraint means logging both the array's size and a compact summary of its contents — full per-element dumps drown the log.
The instrumentation pattern
<code>class burst_xact extends uvm_sequence_item;
rand bit [31:0] data[];
rand bit [3:0] strb[];
function void post_randomize();
`uvm_info("BURST",
$sformatf("size=%0d data[min=0x%h max=0x%h sum=%0d uniq=%0d] strb[min=%0h max=%0h]",
data.size(),
data.min(), data.max(), data.sum(), data.unique().size(),
strb.min(), strb.max()),
UVM_HIGH)
endfunction
endclass</code>ASCII view — array aggregates per transaction
<code> 100ns BURST size=8 data[min=0x0042 max=0xFE10 sum=4912 uniq=8] strb[min=0xF max=0xF]
200ns BURST size=4 data[min=0x1234 max=0xABCD sum=2876 uniq=4] strb[min=0x3 max=0xF]
300ns BURST size=16 data[min=0x0000 max=0xFFFF sum=8123 uniq=15] strb[min=0xF max=0xF]
↑
one duplicate value
(uniqueness was soft, not hard)</code>Diagnosing per-element coverage misses
When per-element coverage shows holes (e.g., the zero-value bin never fires on the data array), the question is: did the per-element constraint exclude it, or is the array always empty? Aggregate logging answers both — the size column shows whether arrays are being produced; the min/max/uniq columns show the value distribution. Drill into specific transactions only when aggregates flag something suspicious.
Industry Insights — Hard-Won Lessons About Array Randomisation
Debugging Academy — Five Real Bugs From Array Randomisation Misuse
"Dynamic array size is always 0"
DEBUGA test using a dynamic-array burst transaction reports zero-length bursts every call. Per-element coverage shows 0%.
<code>class xact;
rand bit [31:0] data[];
// BUG: no size constraint
constraint c { foreach (data[i]) data[i] inside {[1:'hFFFE]}; }
endclass</code>Without a size constraint, the solver picks the array's size implementation-defined-ly — often 0. The foreach then iterates zero times; the per-element constraint never applies. The test silently runs with empty stimulus.
Always constrain dynamic-array size: constraint c_size { data.size() inside {[1:16]}; }. Make this a style rule for every rand dynamic array — the size constraint is just as load-bearing as the element constraint.
"Object-handle array randomises nulls"
DEBUGA class with rand my_xact arr[] runs randomize cleanly, but inspecting arr[0] shows null. Driver crashes when it tries to send.
<code>class burst;
rand int beat_count;
rand beat_xact beats[];
constraint c { beat_count inside {[1:8]};
beats.size() == beat_count; }
// BUG: no pre_randomize to construct beats[i]
endclass
burst b = new();
b.randomize(); // beats[i] still null
drive(b.beats[0].data); // NULL POINTER DEREFERENCE</code>The solver doesn't construct class objects — it only randomises fields of objects that already exist. The array gets sized to beat_count, but every element is null.
Add a pre_randomize that allocates: function void pre_randomize(); if (beats == null) beats = new[beat_count]; foreach (beats[i]) if (beats[i] == null) beats[i] = beat_xact::type_id::create($sformatf("b_%0d", i)); endfunction. The contract: pre_randomize ensures the handles exist; the solver then fills in their fields.
"rand on associative array silently does nothing"
DEBUGA class declares rand bit [31:0] mem [bit [31:0]] intending a randomised address-indexed memory. After randomize, mem is always empty.
<code>class xact;
rand bit [31:0] mem [bit [31:0]]; // BUG: rand on associative
constraint c { mem.num() inside {[4:16]}; }
endclass</code>rand on associative arrays is not supported (or silently no-op). The solver cannot enumerate the unbounded key space.
Randomise parallel key/value lists and build the associative array procedurally: rand bit [31:0] keys[]; rand bit [31:0] vals[]; constraint c { keys.size() == vals.size(); keys.size() inside {[4:16]}; unique { keys }; } function void post_randomize(); foreach (keys[i]) mem[keys[i]] = vals[i]; endfunction. Now the solver handles the bounded list representation; the associative array is built deterministically afterward.
"Pairwise uniqueness foreach takes 100ms per randomize"
DEBUGA 32-element burst transaction's randomize takes 100ms per call; profiler points at a nested-foreach uniqueness constraint.
<code>class burst;
rand int unsigned beats[];
constraint c { beats.size() == 32;
foreach (beats[i])
foreach (beats[j])
if (i != j) beats[i] != beats[j]; }
endclass</code>Nested foreach pairwise inequality generates 32×32 = 1024 clauses. The solver handles each clause independently — the cost dominates.
Use the unique construct: constraint c { beats.size() == 32; unique { beats }; }. Same semantic guarantee, single all-different constraint, typically 50-100× faster.
"Size constraint contradicts implicit size from element constraint"
DEBUGA dynamic-array constraint declares both an explicit size and an element-related size constraint; randomize consistently returns 0.
<code>class xact;
rand bit [31:0] len;
rand bit [31:0] data[];
constraint c_size_a { data.size() == 16; } // 16 always
constraint c_size_b { data.size() == len; } // depends on len
constraint c_len { len inside {[1:8]}; } // 1..8
// Joint: data.size() == 16 AND data.size() == len AND len ≤ 8
// → 16 == len AND len ≤ 8 → infeasible
endclass</code>Two size constraints contradict — one forces 16, the other forces <= 8. Both must hold; no satisfying assignment exists.
Pick one source of truth for the size. If len is the test's controlling field, drop c_size_a; if 16 is the fixed expected size, drop the len-based constraint. The two constraints encode different design decisions; the test infrastructure needs to pick one and document why.
Interview Q&A — Twelve Questions You Will Be Asked
Yes — to fixed-size arrays, dynamic arrays, and queues. Each element becomes randomisable; the solver picks values that jointly satisfy all active constraints. rand on associative arrays is not supported (the key space is unbounded).
Best Practices — Ten Rules for Array Randomisation
- Always constrain dynamic-array size explicitly. Without it, the solver picks implementation-defined-ly, often 0.
- Constrain size as tightly as legitimacy allows. Tight ranges shrink the solver's search space and accelerate coverage closure.
- Use
unique { arr }for "no repeats." Don't write nested-foreach pairwise inequality — it's dramatically slower and less readable. - For object-handle arrays, construct elements in
pre_randomize. The solver randomises fields, not objects; pre_randomize ensures every handle points to a real instance. - Don't try to
randassociative arrays. Randomise parallel key/value lists with bounded sizes, then build the associative array inpost_randomize. - Move per-element
distdistributions topost_randomizefor arrays larger than ~32 elements. Joint distribution sampling is super-linear; procedural is O(N). - Guard inter-element boundary references with bounds checks.
arr[i+1]needsif (i < arr.size() - 1); out-of-bounds references are undefined behaviour. - Log array aggregates (size, min, max, sum, uniq-count) in
post_randomize. Aggregates fit in one log line and describe the constraint's effect compactly; per-element dumps drown the log. - For 2D arrays, constrain row count plus per-row column count, then use the two-variable foreach.
foreach (data[i, j])iterates both dimensions. - Document every array-related design choice in source comments. Why this size range? Why
unique? Why per-element dist overpost_randomize? Future maintainers need the rationale, not just the code.