Skip to content

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.

SystemVerilog — foreach constraint syntax and first example
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]};
    }
endclass

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

SystemVerilog — sorted array and adjacent element constraints
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endclass

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

SystemVerilog — all-distinct elements via nested foreach
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

SystemVerilog — foreach on a dynamic array with rand size
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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];
    }
endclass

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

SystemVerilog — foreach on a 2D array
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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];
    }
endclass

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

SystemVerilog — foreach with implication and dist per element
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 };
    }
endclass

Practical Patterns

Pattern 1 — Randomised address burst with alignment

SystemVerilog — address burst: ascending, aligned, in-region
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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;
    }
endclass

Pattern 2 — Stress test with all-zero and all-one rows

SystemVerilog — corner rows forced via implication + foreach
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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;
        }
    }
endclass

Constraint foreach vs Procedural foreach

AspectConstraint foreachProcedural foreach
When it runsInside the solver during randomize()At simulation time, sequentially
What it doesDeclares one constraint per element — all solved simultaneouslyExecutes one iteration at a time, may assign values
Can assign values?No — declarative onlyYes
LocationInside a constraint blockInside a task, function, initial, or always block
Index typeImplicit integer — cannot be randExplicit 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

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

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

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

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

✅ Linear-cost foreach (independent elements)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
❌ Quadratic-cost foreach (coupled elements)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

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

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

                                                  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

1

"Boundary reference arr[i+1] without guard — silent failure"

DEBUG
Symptom

A monotonic-array constraint produces non-monotonic output on some runs; on others randomize silently returns 0; behaviour differs across simulators.

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

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.

Fix

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.

2

"O(N²) pairwise uniqueness instead of unique"

DEBUG
Symptom

A burst transaction class compiles cleanly and produces unique-value arrays, but randomize takes 50ms per call on a 16-element array.

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

The nested foreach generates N² pairwise inequality clauses. For 16 elements, 256 clauses; for 64 elements, 4096 — the solver's clause database becomes hot.

Fix

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.

3

"foreach on dynamic array returns 0 elements when size=0"

DEBUG
Symptom

A test using a dynamic array burst occasionally generates zero-length bursts; the per-beat coverage shows 0% hits even though many transactions ran.

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

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.

Fix

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.

4

"foreach with dist on 64-element array hangs the solver"

DEBUG
Symptom

A test runs the first transaction successfully then appears to hang. randomize on subsequent transactions takes 30+ seconds each. Profiler shows solver at 99% CPU.

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

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.

Fix

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.

5

"foreach on 2D array forgets to iterate the inner index"

DEBUG
Symptom

A 2D array (data[][]) is partially constrained — outer rows behave as expected, but elements within each row are unconstrained.

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

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.

Fix

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

  1. Always guard inter-element boundary references. arr[i+1] needs if (i < arr.size() - 1); arr[i-1] needs if (i > 0). Make this a style rule for every inter-element reference.
  2. Prefer unique { arr } over nested-foreach pairwise uniqueness. Same guarantee, dramatically faster, more readable.
  3. Use the two-index form for 2D arrays. foreach (data[i, j]) iterates both dimensions; single-index foreach (data[i]) iterates only the outer dimension.
  4. Move per-element dist distributions to post_randomize for arrays larger than ~32 elements. Solver-driven joint distribution sampling is super-linear; procedural is O(N).
  5. 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.
  6. Profile foreach constraints with coupling. Inter-element references multiply cost; identify the threshold where procedural generation becomes faster than declarative constraints.
  7. 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.
  8. 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.
  9. Combine foreach with array methods for aggregate constraints. foreach (arr[i]) arr[i] inside {[1:10]}; arr.sum() inside {[50:80]}; uses both forms appropriately.
  10. Distinguish constraint-block foreach from procedural-block foreach. Same syntax, different semantics; the class context makes the distinction obvious, but reviewers may confuse them without it.