Part 3 · Constraint Randomization · Intermediate
foreach Constraints
Per-element constraints, index-dependent relations like ascending arrays, multi-dimensional foreach, and iteration over queues and associative arrays.
foreach is constraint replication, not a loop
Inside a constraint block, foreach (arr[i]) expr; does not "execute" anything. It replicates the constraint expression once per element , with i substituted, and hands the whole expanded set to the solver. For a 4-element array, foreach (a[i]) a[i] < 10; is exactly the constraint set { a[0]<10, a[1]<10, a[2]<10, a[3]<10 }. Because the array size may itself be random, the replication count is tied to the size variable — another reason size and elements solve jointly.
class frame;
rand bit [7:0] data[];
constraint c_size { data.size() inside {[4:16]}; }
constraint c_elem {
// replicated once per element:
foreach (data[i]) data[i] inside {[8'h20:8'h7E]}; // printable ASCII
// index-dependent: even indexes low, odd indexes high
foreach (data[i])
if (i % 2 == 0) data[i] < 8'h40;
else data[i] >= 8'h40;
}
endclassThe loop index i is a compile-time iteration variable, not a rand variable. You can use it in arithmetic (data[i] == i * 4), comparisons, and conditionals — the solver sees only the fully expanded per-element constraints.
Index-dependent relations: the ascending-array pattern
Relating neighboring elements is the most-asked foreach pattern. The trap is the array boundary: data[i+1] does not exist when i is the last index. Guard the relation so it only applies where both elements exist.
class sorted_arr;
rand int unsigned a[];
constraint c_size { a.size() inside {[5:10]}; }
// Strictly ascending — two equivalent guard styles:
constraint c_up_guard_high {
foreach (a[i])
if (i < a.size() - 1)
a[i] < a[i+1];
}
// Alternative: guard on the LOW side, reference a[i-1]
constraint c_up_guard_low {
foreach (a[i])
if (i > 0)
a[i] > a[i-1];
}
endclassBoth styles are correct; pick one and stay consistent (here they coexist redundantly for illustration — in real code keep one). The i > 0 guard with a[i-1] is often preferred because a.size() - 1 mixes a random size into the guard expression, which some older tools handled poorly. Note that strict ascent over N elements also forces the value range to contain at least N distinct values — with bit [2:0] elements (8 values) and size 10, the constraint set is unsatisfiable and randomize() returns 0.
FOREACH EXPANSION FOR AN ASCENDING ARRAY (size=4)
source: foreach (a[i]) if (i > 0) a[i] > a[i-1];
expands to the solver as:
(i=0) no constraint (guard false)
(i=1) a[1] > a[0]
(i=2) a[2] > a[1]
(i=3) a[3] > a[2]
chained relations: a[0] < a[1] < a[2] < a[3]
+-----------------------------------------------+
| Each pairwise constraint is independent text, |
| but the solver sees the conjunction — values |
| are forced into a strictly increasing chain. |
+-----------------------------------------------+Multi-dimensional, queues, and associative arrays
foreach iterates every dimension you name. For a 2-D array, foreach (m[i][j]) visits all (row, column) pairs; foreach (m[i]) visits only rows, letting you constrain whole-row properties. Queues iterate like dynamic arrays. Associative arrays iterate over existing keys — randomize() never creates or removes keys, so foreach over an associative array constrains the values at keys that already exist.
class matrix;
rand bit [7:0] m[4][4]; // fixed 4x4
rand int q[$]; // queue
rand bit [7:0] cfg[string]; // associative — keys fixed by testbench
constraint c {
// all 16 cells, both indexes available:
foreach (m[i][j]) m[i][j] inside {[0:99]};
// diagonal dominance: diagonal cell biggest in its row
foreach (m[i][j])
if (i != j) m[i][i] > m[i][j];
q.size() inside {[2:6]};
foreach (q[k]) q[k] inside {[-100:100]};
// associative: iterates existing string keys only
foreach (cfg[s]) cfg[s] != 8'hFF;
}
endclass
// Testbench seeds the associative keys BEFORE randomize():
// c.cfg["mode"] = 0; c.cfg["speed"] = 0;
// void'(c.randomize()); // values at "mode"/"speed" randomizedWhat foreach cannot do
It cannot change the iteration set — you cannot add queue entries or associative keys from inside a constraint.
The index variable is not rand — you cannot ask the solver to "pick an index"; instead make a separate rand index variable and constrain arr[idx] via element comparison idioms.
Function calls on elements inside foreach are restricted like any constraint expression — no side effects, treated as state at solve start.
Interview angle
What interviewers probe
"Write a constraint for a strictly increasing array" — they watch whether you guard the boundary index. Unguarded a[i] < a[i+1] is the #1 instant red flag.
"Is foreach a loop?" — expected: no, it is constraint replication; the solver receives the expanded conjunction, all solved simultaneously.
"Ascending 3-bit array of size 10 — what happens?" — expected: unsatisfiable, randomize() returns 0; strict ordering needs at least size-many distinct values.
"Constrain element at a random position" — expected: the index cannot be the foreach variable; use a separate rand idx plus foreach (a[i]) if (i == idx) ... or direct a[idx] reference.
Bonus points in interviews for mentioning that the replication count is bound to the random size() — it shows you understand the joint solving model rather than a procedural picture.
Key takeaways
foreach replicates a constraint per element — the solver sees the expanded conjunction, solved jointly with size.
Guard neighbor relations at array boundaries: if (i > 0) a[i] > a[i-1] is the canonical ascending pattern.
Strict ordering requires enough distinct values in the element type — otherwise unsatisfiable.
foreach on associative arrays visits existing keys only; randomize() never alters the key set.
Common pitfalls
Unguarded a[i+1] or a[i-1] references at array boundaries — out-of-range element in the expanded constraint.
Treating the foreach index as random — it is an iteration variable; use a separate rand index for "pick a position".
Strictly ascending constraint on a narrow element type with a larger size — silent randomize() failure.
Expecting foreach on an empty associative array to create keys — it iterates nothing.
Writing if/else element conditions that conflict with a separate blanket foreach range — over-constrained set, randomize() returns 0.