Control Flow
Verilog's Loops page covered for, while, repeat, and forever. SystemVerilog adds a small set of control-flow constructs on top — none of them enable anything impossible before, but each removes real boilerplate from common patterns, especially over the collection types from Arrays and Dynamic and Associative Arrays.
foreach: iterate without managing an index variable
int data [0:7] = '{3, 1, 4, 1, 5, 9, 2, 6};
foreach (data[i])
$display("data[%0d] = %0d", i, data[i]);
A plain for loop over an array needs the bound written out separately (for (int i = 0; i < 8; i++)) and is a common off-by-one source when the array is resized later. foreach (data[i]) derives its bound from the array itself — resizing data never requires touching the loop. It works identically over multi-dimensional arrays (foreach (matrix[i, j])) and the associative/queue collections from the previous page, where there's no numeric index to manage by hand at all. A dimension can also be left blank to skip iterating over it — foreach (matrix[i, ]) iterates only the first dimension (indexed by i), leaving the second dimension's index unnamed and unvaried inside the loop body.
do-while: guaranteed at least one iteration
int attempts = 0;
do begin
attempts++;
end while (attempts < 3 && !success);
Plain Verilog's while checks its condition before the first iteration; do-while checks after, guaranteeing the body runs at least once — useful for retry logic where the first attempt always has to happen regardless of any prior state.
unique/priority on if and case
unique case (opcode)
2'b00: result = a + b;
2'b01: result = a - b;
2'b10: result = a & b;
2'b11: result = a | b;
endcase
unique asserts that exactly one branch condition is true for any given input — the simulator (and, where supported, the synthesis tool) flags a runtime warning if zero branches match or more than one does. priority instead asserts that branches are checked in order and at least one matches, without requiring mutual exclusivity. Both are optional annotations layered on top of plain if/case — they don't change what hardware synthesizes, but they turn an implicit assumption ("these opcodes are mutually exclusive, I promise") into something the tools actively check, catching a decode-table bug that a plain case would just silently misbehave on.
unique0/priority0: relaxing the "at least one match" requirement
unique0 case (mode)
2'b00: result = idle_val;
2'b01: result = run_val;
2'b10: result = pause_val;
// 2'b11 deliberately unhandled — not every mode combination is legal
endcase
unique0 and priority0 are the same two checks as unique/priority, minus the "at least one branch must match" half — they still flag a runtime warning if more than one branch matches (the mutual-exclusivity check unique0 shares with unique), but silently allow the case where no branch matches at all, with no warning. This is the right choice exactly when some input combinations are legitimately unhandled by design (like 2'b11 above) — using plain unique/priority there would generate a spurious warning every time that input occurs, even though nothing is actually wrong.
break and continue
foreach (data[i]) begin
if (data[i] == 0)
continue; // skip this iteration
if (data[i] < 0)
break; // exit the loop entirely
$display("%0d", data[i]);
end
Plain Verilog has no equivalent of either — exiting a loop early, or skipping to the next iteration, had to be simulated with extra flag variables and nested conditionals. break exits the innermost loop immediately; continue skips straight to the next iteration's condition check. Both work inside any of the loop forms — for, while, do-while, foreach, repeat, forever.
What's next
Control flow rounds out the procedural vocabulary. The next page covers small but genuinely useful upgrades to function/task declarations — default argument values and pass-by-reference arguments — before moving to the two constructs that most change how a design is organized: interface and package.