Beyond for and while, SystemVerilog adds loops shaped for verification work. foreach walks an array's declared index space with no off-by-one risk, and repeat (n) runs a body a fixed count with no loop variable.
int a[3:0] is declared with the range written high to low. In what index order does foreach (a[i]) visit it?3, 2, 1, 0. foreach runs from the left bound to the right bound as declared, not always upward.
foreach order is defined: fixed and dynamic dimensions run from the left bound to the right bound (int a[3:0] gives 3, 2, 1, 0), and associative arrays run in ascending key order. Multidimensional arrays use one form, foreach (t[r, c]). forever loops until its process dies and must block on @ or # every pass, or it starves the scheduler in a zero-time hang.
Loop | Fits |
|---|---|
| Filling and scanning an array with no index bookkeeping |
| Waiting a fixed number of clocks |
| Waiting for a state change with a live condition |
| Monitors and clock generators, always with a timing control |
break leaves the innermost loop and continue jumps to its next iteration. Both are structured and preferred for ordinary early exits.
Naming a block (begin : search) and calling disable search terminates it from inside nested loops or from another process, which is how watchdogs stand down stuck waits. Disabling a task name kills every concurrent activation, so selective kills target a named inner block instead.
A monitor that must survive reset races its collecting loops against a reset watcher and kills the losers with fork ... join_any and disable fork. Those process controls belong to Interprocess Communication in the OOP part.
forever monitor loop contains no @ or #. What happens?The simulation hangs in a zero-time infinite loop. Every pass must block on a timing control.
disable task_name the wrong tool?It kills every activation at once. A named inner block per call gives a selective target.
A one-dimensional and a two-dimensional foreach fill, a search with nested for loops, a bounded forever loop, and a watchdog that stands down via cross-process disable.
module tb_flow;
timeunit 1ns; timeprecision 1ps;
logic clk = 0;
always #5 clk = ~clk;
int flat[12];
int t[3][4];
int target = 9;
int hit_r = -1, hit_c = -1, found = 0;
logic done = 0;
int beats = 0;
int errors = 0;
initial begin
foreach (flat[k]) flat[k] = k * 2;
if (flat[5] !== 10) errors++;
foreach (t[r, c]) t[r][c] = r * 4 + c; // one loop over both dimensions
begin : search
for (int r = 0; r < 3; r++) begin
for (int c = 0; c < 4; c++) begin
if (t[r][c] == target) begin
hit_r = r; hit_c = c; found = 1;
disable search;
end
end
end
end
if (found !== 1) errors++;
if (hit_r !== 2 || hit_c !== 1) errors++;
forever begin
@(posedge clk);
beats = beats + 1;
if (beats == 3) break;
end
if (beats !== 3) errors++;
@(posedge done);
disable guard;
#1100; // past the guard's timeout: it must stay quiet
if (errors != 0) $fatal(1, "FAIL: fill, search or loop counts look wrong, or the watchdog fired");
$display("PASS: foreach fill and [2][1] search hit, forever loop runs 3 beats, watchdog stands down");
$finish;
end
initial begin
repeat (5) @(posedge clk);
done = 1;
end
initial begin : guard
#1000;
errors = 1;
end
endmodulePASS: foreach fill and [2][1] search hit, forever loop runs 3 beats, watchdog stands downRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 (-g2012) runs them too, with the same output.