Part 3 · Constraint Randomization · Intermediate
Q&A: Arrays & Nested Objects
Constraining dynamic array size and elements, object allocation before randomize, the sum() overflow trap, uniqueness without unique, and 2D arrays.
Q: How do you constrain a dynamic array's size AND its elements?
Direct answer: constrain arr.size() like any expression, and use foreach for the elements; the solver picks a size and populates elements consistently in one solve — there is no separate “resize phase”, and foreach automatically ranges over whatever size was chosen. Element constraints may even reference the size or the index.
class pkt;
rand bit [7:0] data[];
constraint c_size { data.size() inside {[4:16]}; }
constraint c_elem { foreach (data[i]) data[i] inside {[1:200]}; }
// Index- and size-aware element constraints:
constraint c_shape {
foreach (data[i]) {
if (i == 0) data[i] == 8'hA5; // header marker
if (i == data.size() - 1) data[i] == 8'h5A; // trailer marker
}
}
endclassFollow-up you should expect
“What if you don’t constrain size at all?” — The size itself is then solver-chosen with tool-specific defaults (often biased small, possibly zero), so portable code always bounds it. And “can one element constrain another?” — yes, foreach bodies can reference data[i-1] with an index guard; that is how ordering and run-length structures are built.
Junior vs senior answer
Junior: “constrain arr.size() and use foreach.” — names both pieces.
Senior: adds single-solve semantics (no resize phase), index/size-aware element constraints, the unbounded-size portability hazard, and cross-element references with guards.
Q: Why must nested objects exist before randomize() is called?
Direct answer: because randomize() solves — it never allocates. A null handle has no fields to include in the constraint problem: the LRM-conformant behavior is to skip null rand handles, so the nested object’s constraints silently vanish from the solve (some tools warn or error instead). The rule: construct in new() (or build_phase), randomize afterwards — allocation is the test writer’s job, solving is the solver’s.
class config_c;
rand bit [3:0] mode;
constraint c_mode { mode inside {[1:8]}; }
endclass
class env_txn;
rand config_c cfg;
function new();
cfg = new(); // without this: cfg stays null, c_mode never solved,
endfunction // and cfg.mode is a null deref when the driver reads it
endclassFollow-up you should expect
“Does randomize() re-randomize the nested object’s fields every call?” — Yes, if the handle is rand and non-null: parent and child fields form one joint problem, so cross-boundary constraints (parent referencing cfg.mode) are solved simultaneously, not in parent-then-child order. If you want the child held constant, cfg.rand_mode(0) on the handle excludes the whole object from the solve while its values stay readable as constants.
Junior vs senior answer
Junior: “you’d get a null pointer error.” — plausible, but the dangerous case is the silent skip.
Senior: explains skip-not-allocate semantics, the silently-vanishing-constraints consequence, joint parent/child solving, and rand_mode(0) on the handle as the freeze knob.
Q: What is the sum() overflow trap?
Direct answer: array reduction sum() accumulates in the element type’s width . For bit [7:0] arr[10], the addition is modulo 256 — so arr.sum() == 100 is satisfied by true sums of 100, 356, 612… The solver will happily pick wrapped solutions, and your “sums to 100” array arrives summing to 356. Fix: widen each item inside the reduction with a with clause — arr.sum() with (int'(item)) == 100.
class budget;
rand bit [7:0] arr[10];
// TRAP: 8-bit accumulator — wrapped totals "pass"
// constraint c_bad { arr.sum() == 100; }
// FIX: 32-bit accumulation via per-item cast
constraint c_sum { arr.sum() with (int'(item)) == 100; }
constraint c_elem { foreach (arr[i]) arr[i] inside {[1:50]}; }
endclassFollow-up you should expect
“Where else does the same width trap bite?” — Everywhere constraint arithmetic exceeds operand width: a + b <= 200 on bytes, size * count <= 4096 on 32-bit factors, address-end checks start + len at the space boundary. One habit fixes all of them: widen at the operands , because casting the result happens after the wrap already occurred.
Junior vs senior answer
Junior: knows sum() “has an overflow issue”, fuzzy on mechanism or fix.
Senior: states accumulation-in-element-width precisely, writes the with (int'(item)) fix from memory, and generalizes to the whole family of width-wrap constraint bugs.
Q: Make all array elements unique WITHOUT the unique keyword
Direct answer: pairwise inequality over index pairs — nested foreach with an i < j guard so each pair is constrained once. This is both the portability answer (pre-2012 tools) and the comprehension check behind the unique keyword: an interviewer who suspects you only know the keyword asks for this form.
class uniq;
rand bit [7:0] arr[8];
constraint c_pairwise {
foreach (arr[i])
foreach (arr[j])
if (i < j) arr[i] != arr[j];
}
// 8 elements → 28 pairwise inequalities; solver satisfies all at once.
// Feasibility: need ≥ 8 distinct candidates — 8-bit type has 256. Fine.
endclassFollow-up you should expect
“How does this scale?” — Quadratically: N(N-1)/2 constraints; at hundreds of elements solve time grows noticeably, where unique {arr} lets the solver use a dedicated all-different algorithm. And the recurring trap follow-up: “would randc work instead?” — no; randc cycles one variable across successive calls and says nothing about distinct elements within one call.
Junior vs senior answer
Junior: writes adjacent-only arr[i] != arr[i+1], or proposes randc.
Senior: pairwise with the i<j guard, counts the constraints, notes the feasibility precondition and the quadratic-vs-native-unique scaling.
Q: How do you randomize a 2D array with constraints?
Direct answer: declare it rand and use multi-index foreach (m[i][j]) — wait, the correct foreach spelling for multiple dimensions is foreach (m[i, j]), comma-separated in ONE bracket; that spelling itself is interview bait. Row/column aggregate constraints (sums, uniqueness per row) are expressed by fixing one index and iterating the other.
class matrix;
rand bit [7:0] m[4][4];
// Per-cell bounds — note the comma form for 2 dims
constraint c_cell { foreach (m[i, j]) m[i, j] inside {[0:9]}; }
// Each row sums to 20 (widened accumulation, manual inner sum)
constraint c_row {
foreach (m[i, j])
if (j == 0)
(int'(m[i][0]) + int'(m[i][1]) + int'(m[i][2]) + int'(m[i][3])) == 20;
}
// Diagonal dominance example: diagonal cell strictly largest in its row
constraint c_diag {
foreach (m[i, j])
if (i != j) m[i][j] < m[i][i];
}
endclassFollow-up you should expect
“Why the manual four-term sum instead of m[i].sum()?” — Reduction methods on a slice of a multidimensional rand array inside constraints have uneven tool support; the explicit sum is the portable whiteboard answer for small fixed dimensions, and for dynamic dimensions you restructure as an array of row objects, each owning a 1D array with its own sum constraint — which also re-uses everything you know about nested rand objects.
Junior vs senior answer
Junior: writes foreach (m[i][j]) — the bracket form that doesn’t parse — and is surprised.
Senior: knows the comma spelling cold, the guarded-inner-index idiom for row aggregates, and the rows-as-objects restructuring for anything dynamic.
Key takeaways
size() and foreach solve together in one pass — bound the size, guard cross-element references.
randomize() never allocates — null rand handles are silently skipped, constraints and all.
sum() accumulates in element width — with (int'(item)) every time; widen operands, not results.
Pairwise i<j inequality is the no-keyword uniqueness answer; know its quadratic cost.
Multi-dim foreach is comma-form: foreach (m[i, j]) — the bracket form is bait.
Common pitfalls
Unbounded dynamic array size — tool-dependent defaults, occasionally zero-length surprises.
Constructing nested objects after randomize instead of before — constraints silently skipped.
Casting the sum result instead of the items — the accumulator already wrapped.
Reduction methods on multi-dim slices in constraints — portability minefield; restructure instead.