Skip to content

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 typeCan be rand?Size randomisable?Notes
Fixed-sizeYesNo — size fixed at compile timeAll elements randomised
Dynamic arrayYesYes — constrain .size()Solver allocates and fills in one call
Queue ([$])YesYes — constrain .size()Behaves like dynamic array for randomisation
Associative arrayNoN/ACannot 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.

SystemVerilog — fixed-size rand array
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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-zero

The 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.

SystemVerilog — unique{} constraint forms
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
    }
endclass

Dynamic 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.

SystemVerilog — dynamic array with rand size and content
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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–16

Queues as rand Arrays

Queues ([$]) behave identically to dynamic arrays for randomisation purposes. The same size and content constraint patterns apply.

SystemVerilog — rand queue
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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;
    }
endclass

Arrays 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().

SystemVerilog — array of objects, randomised in post_randomize
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 length

Practical Pattern — DMA Scatter-Gather with Non-Overlapping Regions

SystemVerilog — non-overlapping DMA descriptors
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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];
    }
endclass

Common 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

SystemVerilog — rand array cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

✅ Object array — proper pre-construction
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
❌ Object array — null elements
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

1

"Dynamic array size is always 0"

DEBUG
Symptom

A test using a dynamic-array burst transaction reports zero-length bursts every call. Per-element coverage shows 0%.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [31:0] data[];
  // BUG: no size constraint
  constraint c { foreach (data[i]) data[i] inside {[1:'hFFFE]}; }
endclass</code>
Root Cause

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.

Fix

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.

2

"Object-handle array randomises nulls"

DEBUG
Symptom

A class with rand my_xact arr[] runs randomize cleanly, but inspecting arr[0] shows null. Driver crashes when it tries to send.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

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.

3

"rand on associative array silently does nothing"

DEBUG
Symptom

A class declares rand bit [31:0] mem [bit [31:0]] intending a randomised address-indexed memory. After randomize, mem is always empty.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [31:0] mem [bit [31:0]];   // BUG: rand on associative
  constraint c { mem.num() inside {[4:16]}; }
endclass</code>
Root Cause

rand on associative arrays is not supported (or silently no-op). The solver cannot enumerate the unbounded key space.

Fix

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.

4

"Pairwise uniqueness foreach takes 100ms per randomize"

DEBUG
Symptom

A 32-element burst transaction's randomize takes 100ms per call; profiler points at a nested-foreach uniqueness constraint.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

Nested foreach pairwise inequality generates 32×32 = 1024 clauses. The solver handles each clause independently — the cost dominates.

Fix

Use the unique construct: constraint c { beats.size() == 32; unique { beats }; }. Same semantic guarantee, single all-different constraint, typically 50-100× faster.

5

"Size constraint contradicts implicit size from element constraint"

DEBUG
Symptom

A dynamic-array constraint declares both an explicit size and an element-related size constraint; randomize consistently returns 0.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

Two size constraints contradict — one forces 16, the other forces <= 8. Both must hold; no satisfying assignment exists.

Fix

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

  1. Always constrain dynamic-array size explicitly. Without it, the solver picks implementation-defined-ly, often 0.
  2. Constrain size as tightly as legitimacy allows. Tight ranges shrink the solver's search space and accelerate coverage closure.
  3. Use unique { arr } for "no repeats." Don't write nested-foreach pairwise inequality — it's dramatically slower and less readable.
  4. 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.
  5. Don't try to rand associative arrays. Randomise parallel key/value lists with bounded sizes, then build the associative array in post_randomize.
  6. Move per-element dist distributions to post_randomize for arrays larger than ~32 elements. Joint distribution sampling is super-linear; procedural is O(N).
  7. Guard inter-element boundary references with bounds checks. arr[i+1] needs if (i < arr.size() - 1); out-of-bounds references are undefined behaviour.
  8. 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.
  9. For 2D arrays, constrain row count plus per-row column count, then use the two-variable foreach. foreach (data[i, j]) iterates both dimensions.
  10. Document every array-related design choice in source comments. Why this size range? Why unique? Why per-element dist over post_randomize? Future maintainers need the rationale, not just the code.