SystemVerilog · Module 10
Iterative Constraints — foreach in Constraints
Per-element burst payload rules, guarded inter-element references, unique vs O(N²) pairwise, and when to push per-element dist into post_randomize.
Module 10 · Page 10.10
What foreach Does in a Constraint Block
The foreach construct inside a constraint block is a declarative expansion, not a procedural loop. When the solver runs, it unrolls the foreach into one constraint expression per element and solves all of them simultaneously as a single constraint system.
This is the primary tool for constraining rand arrays — both fixed-size and dynamic arrays — where you need a rule to apply to every element individually, or a relationship between adjacent elements.
class BurstTxn;
rand bit [7:0] data[8]; // fixed-size array of 8 bytes
// Constrain every element: no element may be 0x00
constraint no_zero {
foreach (data[i])
data[i] != 8'h00;
}
// Constrain every element: each byte must be in a valid range
constraint valid_data {
foreach (data[i])
data[i] inside {[8'h20:8'hFE]};
}
endclassInter-Element Relationships
The most powerful use of foreach in constraints is expressing relationships between adjacent or relative elements. The index variable can be used in arithmetic to reference neighbouring elements.
class SortedBuf;
rand bit [7:0] addr[4];
// Strictly ascending — each element greater than the previous
constraint ascending {
foreach (addr[i])
if (i > 0) addr[i] > addr[i-1];
}
// Solver finds 4 distinct values in ascending order.
// Example: [12, 45, 89, 203]
// Minimum gap between adjacent addresses
constraint min_gap {
foreach (addr[i])
if (i > 0) addr[i] >= addr[i-1] + 8;
}
// Each address at least 8 apart from the previous
endclassUnique Elements
To guarantee all elements are distinct, use a nested foreach that compares every pair of elements. The solver treats this as a set of pairwise inequality constraints.
class UniqueKeys;
rand bit [7:0] key[4];
// All keys must be distinct
constraint all_unique {
foreach (key[i])
foreach (key[j])
if (i != j) key[i] != key[j];
}
endclass
// Note: for small arrays this is fine.
// For large arrays, consider using unique{} (page 10.11) instead —
// it is more solver-efficient than O(N^2) pairwise constraints.foreach With Dynamic Arrays
foreach in constraints works on dynamic arrays too. The solver uses the array's current size at the time randomize() is called. If the size itself is a rand variable, it must be solved before the foreach expands — the solver handles this ordering automatically.
class VarBurst;
rand bit [7:0] payload[]; // dynamic array — size TBD
// Constrain the size first
constraint size_c {
payload.size() inside {[1:16]};
}
// Then constrain every element within that (randomised) size
constraint payload_c {
foreach (payload[i])
payload[i] != 8'h00;
}
// Monotonically non-decreasing payload bytes
constraint non_decreasing {
foreach (payload[i])
if (i > 0) payload[i] >= payload[i-1];
}
endclassNested foreach for 2D Arrays
Two-dimensional fixed-size arrays use nested foreach with two index variables — one per dimension — declared inside the same foreach(arr[i, j]) expression.
class Matrix;
// 4 rows × 8 columns of random bytes
rand bit [7:0] grid[4][8];
// Every cell must be in a valid range
constraint range_c {
foreach (grid[i, j])
grid[i][j] inside {[8'h01:8'hFE]};
}
// Each row: values non-decreasing left to right
constraint row_order {
foreach (grid[i, j])
if (j > 0) grid[i][j] >= grid[i][j-1];
}
endclassforeach Combined With Other Constraint Forms
foreach works naturally inside a constraint block alongside implication, dist, and inside. Each element gets the full expressive power of the constraint language applied to it.
class PacketStream;
rand bit valid[8]; // valid flag per slot
rand bit [7:0] data[8]; // data per slot
rand bit [1:0] prio[8]; // priority per slot
// Implication per element:
// if a slot is valid, its data must be non-zero
constraint valid_data {
foreach (valid[i])
valid[i] == 1 -> data[i] != 8'h00;
}
// Weighted distribution per element:
// most slots low-priority, some high
constraint prio_dist {
foreach (prio[i])
prio[i] dist { 0 := 5, 1 := 3, 2 := 1, 3 := 1 };
}
endclassPractical Patterns
Pattern 1 — Randomised address burst with alignment
class AddrBurst;
rand bit [31:0] addr[4];
// All addresses in peripheral space
constraint in_region {
foreach (addr[i])
addr[i] inside {[32'h4000_0000:32'h4FFF_FFFF]};
}
// All addresses word-aligned
constraint aligned {
foreach (addr[i])
addr[i][1:0] == 2'b00;
}
// Addresses strictly ascending with a minimum 4-byte gap
constraint ordered {
foreach (addr[i])
if (i > 0) addr[i] >= addr[i-1] + 4;
}
endclassPattern 2 — Stress test with all-zero and all-one rows
class MemFrame;
rand bit [7:0] row[4][8];
rand bit [1:0] row_type[4]; // 0=normal, 1=all-zero, 2=all-ones
constraint corner_rows {
foreach (row[i, j]) {
row_type[i] == 1 -> row[i][j] == 8'h00;
row_type[i] == 2 -> row[i][j] == 8'hFF;
}
}
endclassConstraint foreach vs Procedural foreach
| Aspect | Constraint foreach | Procedural foreach |
|---|---|---|
| When it runs | Inside the solver during randomize() | At simulation time, sequentially |
| What it does | Declares one constraint per element — all solved simultaneously | Executes one iteration at a time, may assign values |
| Can assign values? | No — declarative only | Yes |
| Location | Inside a constraint block | Inside a task, function, initial, or always block |
| Index type | Implicit integer — cannot be rand | Explicit loop variable |
Common Pitfalls
Out-of-bounds index reference
data[i+1] inside a foreach that iterates the full array will access past the last element on the final iteration. Guard with if (i < data.size()-1).
Forgetting to size dynamic arrays
A dynamic array with no size constraint defaults to size 0. The foreach expands to nothing and randomize() succeeds silently with an empty array. Always constrain the size to a non-zero range.
O(N²) pairwise unique on large arrays
Nested foreach for uniqueness creates N×(N-1)/2 constraints. For arrays larger than ~16 elements this strains the solver. Use unique {} (page 10.11) instead — it is a dedicated, solver-efficient form.
Assigning inside a constraint foreach
data[i] = 5; inside a constraint block is a compile error. Use data[i] == 5; as a constraint expression, or assign in post_randomize().
Quick Reference
// ── Element-wise bound ────────────────────────────────────────
foreach (arr[i]) arr[i] inside {[lo:hi]};
// ── Adjacent-element relationship ─────────────────────────────
foreach (arr[i]) if (i > 0) arr[i] > arr[i-1]; // ascending
foreach (arr[i]) if (i > 0) arr[i] >= arr[i-1]; // non-decreasing
foreach (arr[i]) if (i > 0) arr[i] >= arr[i-1] + gap; // min gap
// ── Pairwise uniqueness (small arrays) ───────────────────────
foreach (arr[i]) foreach (arr[j]) if (i != j) arr[i] != arr[j];
// ── 2D array ─────────────────────────────────────────────────
foreach (grid[i, j]) grid[i][j] inside {[lo:hi]};
// ── foreach + implication per element ────────────────────────
foreach (valid[i]) valid[i] -> data[i] != 0;
// ── foreach + dist per element ────────────────────────────────
foreach (prio[i]) prio[i] dist { 0 := 5, 1 := 3, 2 := 1, 3 := 1 };
// ── Guard adjacent-element access ────────────────────────────
// if (i > 0) → safe to access arr[i-1]
// if (i < size - 1) → safe to access arr[i+1]Verification Usage — Where foreach Constraints Live in Real Testbenches
foreach shows up in three reliable places across production UVM environments. Outside these three, the construct is usually a performance trap waiting to happen.
Array-element bounds across burst payloads
The canonical use: a burst transaction has an array of beat-level data and metadata; each beat must satisfy per-beat protocol rules. foreach applies the per-beat rule uniformly without enumerating each beat by hand.
<code>class axi_burst_xact;
rand bit [7:0] len; // 0..255 beats
rand bit [31:0] data[];
rand bit [3:0] wstrb[];
constraint c_size { data.size() == len + 1;
wstrb.size() == len + 1; }
constraint c_per_beat {
foreach (data[i]) {
data[i] != 32'hDEAD_BEEF; // exclude reserved pattern
wstrb[i] != 4'h0; // each beat strobes at least one byte
}
}
endclass</code>Monotonic or distinct address generation
Stress tests for caches, address translation, and arbitration often need sequences of addresses with structural properties — monotonically increasing, all distinct, or evenly spaced. foreach with inter-element references and bounds guards expresses these cleanly.
<code>class addr_sweep_xact;
rand bit [31:0] addrs[];
constraint c_sweep {
addrs.size() inside {[8:32]};
foreach (addrs[i]) {
if (i < addrs.size() - 1)
addrs[i] < addrs[i+1]; // monotonic
addrs[i] inside {[32'h4000_0000:32'h4000_FFFF]}; // bounded
addrs[i][1:0] == 2'b00; // word-aligned
}
}
endclass</code>Sum/aggregate constraints across an array
Tests targeting bandwidth or burst-total bounds need constraints on aggregates of array elements. foreach doesn't directly express "sum" or "min/max," but pairing it with the SV array methods (.sum(), .min()) handles the common cases.
<code>class throughput_xact;
rand int unsigned beats[];
constraint c_total {
beats.size() == 16;
foreach (beats[i]) beats[i] inside {[1:64]};
beats.sum() inside {[256:512]}; // total in [256:512]
}
endclass</code>Simulation Behavior — How the Solver Expands foreach
Constraint set expansion at solve time
At every randomize() call, the solver expands foreach (arr[i]) expr(arr[i]) based on the array's current size. For a 16-element array, the foreach body becomes 16 separate constraint clauses, one per element. This expansion happens before the solve begins; the solver sees the expanded set as if it had been hand-written.
Dynamic array size handling
When the array's size is itself a rand field (typical in burst transactions), the solver must first decide the size, then expand the foreach based on that size, then solve the resulting clauses. Modern solvers handle this iteratively — try a candidate size, expand, attempt the joint solve; if infeasible, try another size. The cost grows with the number of size candidates explored.
Inter-element coupling
Constraints like arr[i] < arr[i+1] create coupling between adjacent elements: the solver cannot decide arr[i+1]'s value independently of arr[i]. For an N-element array with chain coupling, the joint search space grows multiplicatively. Without coupling (each element independent), the cost grows linearly with N.
Solver performance characteristics
Simple uncoupled foreach: ~linear in N. Inter-element coupling: ~quadratic in N for nearest-neighbour chains, exponential for long-range dependencies. Plus a dist distribution per element: super-exponential — typically infeasible above N=16. Profile every foreach beyond N=32 if it includes coupling or distributions.
<code>class burst;
rand bit [31:0] data[];
constraint c_size { data.size() == 64; }
constraint c_per_elem {
// Each element constrained independently
// Solver: 64 independent solves, parallel-friendly
foreach (data[i])
data[i] inside {[1:32'hFFFE]};
}
endclass
// Per call: ~milliseconds</code><code>class burst;
rand bit [31:0] data[];
constraint c_size { data.size() == 64; }
constraint c_coupled {
// Each element depends on the previous —
// 64-element joint solve
foreach (data[i]) {
if (i > 0) data[i] > data[i-1];
data[i] inside {[1:32'hFFFE]};
}
}
endclass
// Per call: ~hundreds of milliseconds</code>Waveform Analysis — Tracing Per-Element Generation Across a Burst
A foreach-generated burst produces a stream of N beat values per randomize. Verifying the per-beat behaviour matches the constraint requires either inspecting all N values (impractical for large N) or computing aggregate properties and checking them.
The instrumentation pattern
<code>class burst_xact extends uvm_sequence_item;
rand bit [31:0] data[];
constraint c { data.size() == 16;
foreach (data[i]) data[i] inside {[1:'hFFFE]}; }
function void post_randomize();
`uvm_info("BURST",
$sformatf("size=%0d min=%0h max=%0h sum=%0d unique_count=%0d",
data.size(), data.min(), data.max(),
data.sum(), data.unique().size()),
UVM_HIGH)
endfunction
endclass</code>ASCII view — aggregate properties tell the constraint story
<code> 100ns BURST size=16 min=0x000A max=0xFFEE sum=2456789 unique_count=16
200ns BURST size=16 min=0x0001 max=0xFFFE sum=3123456 unique_count=16
300ns BURST size=16 min=0x0042 max=0xFFFF sum=2987654 unique_count=15 ← duplicate!
↑
uniqueness slipped:
one beat appears twice
(constraint allowed it)</code>Coverage validation per-element
For coverage, sample the array's elements individually into a coverpoint with the iteration index. SV's coverpoint with iff (sampling_event) can target specific indices to verify that each beat hits the intended distribution.
Industry Insights — Hard-Won Lessons About Iterative Constraints
Debugging Academy — Five Real Bugs From foreach Misuse
"Boundary reference arr[i+1] without guard — silent failure"
DEBUGA monotonic-array constraint produces non-monotonic output on some runs; on others randomize silently returns 0; behaviour differs across simulators.
<code>class xact;
rand int arr[];
constraint c_size { arr.size() == 8; }
constraint c_mono {
foreach (arr[i])
arr[i] < arr[i+1]; // BUG: at i=7, arr[8] is out of bounds
}
endclass</code>The reference arr[i+1] is valid for i=0..6 but undefined at i=7. Different simulators handle this differently — some treat the out-of-bounds value as 0 (silently changing constraint semantics), some emit warnings, some fail to solve.
Guard the boundary: foreach (arr[i]) if (i < arr.size() - 1) arr[i] < arr[i+1];. The guard makes the constraint portable across simulators and obvious to next readers. Treat this as a style rule for every inter-element reference.
"O(N²) pairwise uniqueness instead of unique"
DEBUGA burst transaction class compiles cleanly and produces unique-value arrays, but randomize takes 50ms per call on a 16-element array.
<code>class burst;
rand int unsigned arr[];
constraint c_size { arr.size() == 16; }
constraint c_unique_slow {
foreach (arr[i])
foreach (arr[j])
if (i != j) arr[i] != arr[j]; // 256 pairwise clauses
}
endclass</code>The nested foreach generates N² pairwise inequality clauses. For 16 elements, 256 clauses; for 64 elements, 4096 — the solver's clause database becomes hot.
Use the unique construct: constraint c_unique_fast { unique { arr }; }. Same semantic guarantee (all elements distinct), single all-different clause, dramatically faster solver. The pattern is widely supported by modern simulators.
"foreach on dynamic array returns 0 elements when size=0"
DEBUGA test using a dynamic array burst occasionally generates zero-length bursts; the per-beat coverage shows 0% hits even though many transactions ran.
<code>class xact;
rand int len;
rand bit [3:0] data[];
constraint c_size { data.size() == len;
len inside {[0:7]}; } // BUG: allows len=0
constraint c_data {
foreach (data[i])
data[i] inside {[1:14]}; // never fires when size=0
}
endclass</code>When len randomises to 0, the array has zero elements, and the foreach iterates zero times — the per-element constraint never applies. The test silently misses every zero-length case.
Either narrow the size constraint to exclude 0 (len inside {[1:7]}), or add explicit handling for the zero-length case if it's legitimate stimulus. The fix depends on whether zero-length bursts are valid for your protocol; reads typically allow zero-length, writes typically don't.
"foreach with dist on 64-element array hangs the solver"
DEBUGA test runs the first transaction successfully then appears to hang. randomize on subsequent transactions takes 30+ seconds each. Profiler shows solver at 99% CPU.
<code>class burst;
rand bit [31:0] data[];
constraint c_size { data.size() == 64; }
constraint c_data_dist {
foreach (data[i])
data[i] dist { 0 := 10,
[1:'hFFFE] := 80,
'hFFFF_FFFF :=10 };
}
endclass</code>The foreach with dist per element creates a 64-element joint distribution sampling problem. The solver must compute the joint distribution shape across all 64 elements simultaneously; cost scales super-linearly with array size.
Move per-element distribution to post_randomize: function void post_randomize(); foreach (data[i]) begin int r = $urandom_range(0, 99); data[i] = (r < 10) ? 0 : (r < 90) ? $urandom_range(1, 'hFFFE) : 'hFFFF_FFFF; end endfunction. Same statistical distribution; each element drawn in O(1), total time linear in N. Two orders of magnitude faster.
"foreach on 2D array forgets to iterate the inner index"
DEBUGA 2D array (data[][]) is partially constrained — outer rows behave as expected, but elements within each row are unconstrained.
<code>class xact;
rand bit [7:0] data[][];
constraint c_outer { data.size() == 4; }
constraint c_inner {
foreach (data[i]) { // BUG: iterates only outer
data[i].size() == 8;
// forgot to add inner foreach
data[i] inside {[0:127]}; // applies to whole row (not legal)
}
}
endclass</code>foreach (data[i]) iterates only the outer index; data[i] refers to an entire row. The inside applied to a whole array is undefined behaviour in most simulators. The inner index needs its own foreach.
Add the inner iteration: foreach (data[i, j]) data[i][j] inside {[0:127]};. The two-variable foreach iterates both dimensions in lockstep. Alternatively, nest: foreach (data[i]) foreach (data[i][j]) data[i][j] inside {[0:127]}; — same effect, more verbose.
Interview Q&A — Twelve Questions You Will Be Asked
It expands the constraint body to apply per-element. foreach (arr[i]) arr[i] inside {[0:9]}; on an N-element array becomes N independent arr[k] inside {[0:9]} clauses — one for each element. The expansion happens at solve time using the array's current size.
Best Practices — Ten Rules for foreach Discipline
- Always guard inter-element boundary references.
arr[i+1]needsif (i < arr.size() - 1);arr[i-1]needsif (i > 0). Make this a style rule for every inter-element reference. - Prefer
unique { arr }over nested-foreach pairwise uniqueness. Same guarantee, dramatically faster, more readable. - Use the two-index form for 2D arrays.
foreach (data[i, j])iterates both dimensions; single-indexforeach (data[i])iterates only the outer dimension. - Move per-element
distdistributions topost_randomizefor arrays larger than ~32 elements. Solver-driven joint distribution sampling is super-linear; procedural is O(N). - Handle empty arrays explicitly. Either constrain size away from 0 or add a separate constraint that handles size=0 if zero-length is legal stimulus.
- Profile
foreachconstraints with coupling. Inter-element references multiply cost; identify the threshold where procedural generation becomes faster than declarative constraints. - Mirror the protocol spec's per-beat rules in the foreach body. Each spec rule about an array beat should be one expression inside the foreach for direct traceability.
- Log aggregate properties (sum, min, max, unique-count) in
post_randomize. They describe the constraint's effect compactly without flooding logs with element-by-element values. - Combine
foreachwith array methods for aggregate constraints.foreach (arr[i]) arr[i] inside {[1:10]}; arr.sum() inside {[50:80]};uses both forms appropriately. - Distinguish constraint-block
foreachfrom procedural-blockforeach. Same syntax, different semantics; the class context makes the distinction obvious, but reviewers may confuse them without it.