Inside a constraint block, foreach (arr[i]) expr copies the expression once per element with i replaced by a constant. For a 4-element array, foreach (a[i]) a[i] < 10 hands the solver four constraints, all solved together.
The index is not random. It can appear in arithmetic and conditions, such as i % 2 == 0, and the number of copies follows the size, which is solved first.
foreach (a[i]) a[i] < 10. How many constraints does the solver see?Four: one per index, a[0] < 10 through a[3] < 10.
Neighbour relations such as a[i] > a[i-1] name an index that does not exist at the array edge. Guard the relation so it applies only where both elements exist. The low-side guard if (i > 0) keeps the random size out of the guard.
Guard style | Form | Note |
|---|---|---|
Guard low |
| Preferred: size stays out of the guard |
Guard high |
| Equivalent, mixes the random size into the guard |
Three cookbook patterns use the same guarded form. These short fragments are illustrations, so the course checks skip them.
// snippet
foreach (a[i]) if (i > 0) a[i] >= a[i-1]; // sorted, non-decreasing
foreach (a[i]) if (i > 0) a[i] != a[i-1]; // no two adjacent equal
foreach (a[i]) // alternating bands
if (i % 2 == int'(start_high)) a[i] >= 8'h80;
else a[i] < 8'h80;sorted_arr asks for 5 to 10 strictly ascending values, each below 1000. Can that ever be unsatisfiable? What if the values were bit [2:0]?Not with values below 1000: ten distinct values fit easily. With bit [2:0] there are only 8 values, so a size of 9 or 10 has no strictly ascending answer.
Naming every dimension visits every cell: foreach (m[i, j]) visits all row and column pairs. The standard separates the indexes with commas; some tools also accept m[i][j], but Verilator rejects it. Queues iterate like dynamic arrays.
Associative arrays iterate over existing keys only. randomize() never creates or removes keys, so the testbench adds them before the call.
The bench below runs a printable alternating frame, a strictly ascending array, and a class mixing a 4 by 4 matrix, a queue and an associative array, checking every result.
class frame;
rand bit [7:0] data[];
rand bit start_high;
constraint c_size { data.size() inside {[4:16]}; }
constraint c_elem {
foreach (data[i]) data[i] inside {[8'h20:8'h7E]}; // printable ASCII
foreach (data[i]) // alternating bands
if (i % 2 == int'(start_high)) data[i] >= 8'h50;
else data[i] < 8'h50;
}
endclass
class sorted_arr;
rand int unsigned a[];
constraint c_size { a.size() inside {[5:10]}; foreach (a[i]) a[i] < 1000; }
constraint c_up { foreach (a[i]) if (i > 0) a[i] > a[i-1]; } // guarded on the low side
endclass
class grid;
rand bit [7:0] m[4][4];
rand int q[$];
rand bit [7:0] cfg[string];
constraint c {
foreach (m[i, j]) m[i][j] inside {[0:99]};
foreach (m[i, j]) if (i != j) m[i][i] > m[i][j]; // diagonal biggest in its row
q.size() inside {[2:6]};
foreach (q[k]) q[k] inside {[-100:100]};
foreach (cfg[s]) cfg[s] != 8'hFF; // existing keys only
}
function new();
cfg["mode"] = 8'hFF; // keys exist before randomize()
cfg["speed"] = 8'hFF;
endfunction
endclass
frame: 14 bytes, start_high 0, first bytes 5b 3a
sorted: 6 values, 496 .. 779
grid: row 0 = 95 31 31 61; queue holds 6; keys 2
PASS: printable alternating frames, strictly sorted arrays, and matrix, queue and key rules all holdConstraints cannot add queue entries or associative keys; they only shape what exists.
The foreach index is not random; a separate rand index field picks a position.
Functions called on elements inside constraints see solve-start values and must have no side effects.
randomize() runs. What does foreach constrain?Nothing. There are no keys to visit, and the solver creates none.
One way to write it:
typedef enum bit [1:0] {DATA = 2'd0, CTRL = 2'd1, ERR = 2'd2} kind_e;
class pkt_txn;
rand kind_e kind;
rand bit [1:0] dest;
rand bit [3:0] len;
rand bit [7:0] payload[8];
constraint legal_c { len inside {[1:8]}; dest inside {[0:2]}; }
constraint payload_c {
foreach (payload[i])
if (i < len) payload[i] != 8'h00; // used bytes never zero
else payload[i] == 8'h00; // bytes past len are zero
}
endclass
module tb_pkt_payload;
pkt_txn p = new();
int lens[int];
initial begin
repeat (1000) begin
if (!p.randomize()) $fatal(1, "FAIL: randomize failed");
foreach (p.payload[i])
if ((i < p.len) != (p.payload[i] != 0)) $fatal(1, "FAIL: len %0d, payload[%0d] = %0h", p.len, i, p.payload[i]);
lens[p.len]++;
end
$display("last packet: len %0d, payload %p", p.len, p.payload);
$display("len counts: 1=%0d 2=%0d 3=%0d 4=%0d 5=%0d 6=%0d 7=%0d 8=%0d",
lens[1], lens[2], lens[3], lens[4], lens[5], lens[6], lens[7], lens[8]);
$display("PASS: 1000 packets, bytes 0 to len-1 nonzero and the rest zero");
$finish;
end
endmodulelast packet: len 8, payload '{'hfb, 'hfe, 'hdf, 'h7f, 'h7f, 'haf, 'hff, 'h6f}
len counts: 1=0 2=3 3=5 4=1 5=25 6=56 7=538 8=372
PASS: 1000 packets, bytes 0 to len-1 nonzero and the rest zeroEvery packet is legal, but look at the length counts. With len deciding which bytes must be zero, Verilator 5.048 gave lengths 7 and 8 about 91% of the time and length 1 almost never. The standard asks for no particular distribution, and a solver that favours long packets leaves short ones untested.
The fix is to say what you want: a dist on len, or a coverage point that shows the hole. Functional Coverage builds that check on this exact packet.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator prints a harmless WIDTHTRUNC warning for if (!obj.randomize()), because randomize() returns an int, and an UNSATCONSTR warning when a solve fails on purpose. Icarus 13 does not support constraints.
Verilator rejects foreach (m[i][j]); the bench uses the standard comma form m[i, j]. With len gating the payload rules, lengths 7 and 8 came out 91% of the time over 1000 packets.