Loops, Jump Statements, Final Blocks & Labels
The new do-while loop, enhanced for with in-loop declarations and multiple initialisers, the foreach array iterator, C-style break/continue/return jump statements, the final simulation end block, and closing-name block labels.
🔁 Loop Kinds Overview
Verilog-2001 provided four loop types. SystemVerilog adds two new ones and enhances for.
↻ do-while Loop
The do-while loop executes the body first, then tests the condition. This guarantees at least one execution, which is exactly what you need when you want to run a block and then decide whether to repeat.
while: body may never execute
int i = 10; while (i < 5) begin // never executes — condition false from the start $display(i); i++; end
do-while: body runs at least once
int i = 10; do begin $display(i); // executes once even though i >= 5 i++; end while (i < 5); // prints: 10 (loop condition then false — stops)
Practical patterns
// Pattern 1: iterate an associative array forward // (first() returns 0 if empty — checking after is correct) int map[string]; string k; if (map.first(k)) do $display("%s = %0d", k, map[k]); while (map.next(k)); // Pattern 2: poll until ready — always check at least once do begin @(posedge clk); end while (!dut.ready); // waits until ready goes high // Pattern 3: retry a randomisation until constraint is satisfied do begin void'(pkt.randomize()); end while (pkt.len < 8); // keep randomising until len >= 8
→ Enhanced for Loop
SystemVerilog makes three improvements to the Verilog-2001 for loop: the loop control variable can be declared inside the for header, the initialisation can have multiple comma-separated statements, and the step can also be multiple comma-separated assignments.
In-loop variable declaration
// Verilog-2001: variable declared BEFORE the loop // — visible in the whole scope, shared between parallel loops integer i; // shared — risky in concurrent code for (i=0; i<=255; i++) $display(i); // SystemVerilog: variable declared INSIDE the for header // — automatic, local to the loop body only for (int i=0; i<=255; i++) $display(i); // 'i' is invisible outside the for block — like C
for header is equivalent to declaring an automatic variable in an unnamed begin…end block wrapping the loop. Two parallel initial blocks that both use for (int i = 0; ...) each get their own independent i — they cannot accidentally modify each other’s counter.
Multiple initialisers and step expressions
// Single init and step — standard Verilog form (still works) for (int count=0; count<3; count++) value += (a[count]) * (count+1); // Multiple initialisers separated by comma // Multiple types can be declared in one for header for (int count=0, int j=0; j*count<125; j++) $display("j = %0d", j); // Useful pattern: two independent counters in one for for (int lo=0, int hi=15; lo<hi; lo++, hi--) $display("lo=%0d hi=%0d", lo, hi);
Parallel loops with in-loop declarations — safe
module foo; initial begin for (int i=0; i<=255; i++) $display("A: %0d", i); end initial begin // runs concurrently with first initial loop2: for (int i=15; i>=0; i--) $display("B: %0d", i); // this i is separate from the first i end endmodule
initial blocks both used for (i = 0; ...) where i is declared once at module scope, one block could modify i while the other is still using it — a race condition that is very hard to debug. The in-loop declaration in SV completely eliminates this hazard.
→ foreach Loop
The foreach construct iterates over every element of any array type — fixed-size, dynamic, or associative. You never need to write the bounds explicitly. Loop variables are automatic, read-only, and local to the loop body.
// 1-D fixed array — j iterates over all indices string words[2] = {"hello", "world"}; foreach(words[j]) $display("%0d: %s", j, words[j]); // Output: 0: hello / 1: world // 2-D fixed array — k and m each cover one dimension int prod[1:8][1:3]; foreach(prod[k, m]) prod[k][m] = k * m; // Dynamic array — works regardless of current size int dyn[] = new[8]; foreach(dyn[i]) dyn[i] = i * i; // Associative array — key type determines loop variable type int score[string]; score["Alice"] = 95; score["Bob"] = 82; foreach(score[name]) $display("%s: %0d", name, score[name]); // name is type string (from the index type) // Queue int q[$] = {10, 20, 30}; foreach(q[i]) $display(q[i]);
int. For associative arrays indexed by a specific type (e.g. string), the loop variable has that index type. For associative arrays with wildcard index (*), the loop variable is unsigned longint. To use a different type, cast explicitly.
↦ foreach with Multi-Dimensional Arrays
For multi-dimensional arrays, list one loop variable per dimension in the square brackets. The number of variables must equal the number of dimensions, except that trailing empty variables can be omitted to skip those dimensions.
// Three-dimensional: i, j, k each cover one dimension int A[2][3][4]; // dim1=2, dim2=3, dim3=4 foreach(A[i, j, k]) A[i][j][k] = i*12 + j*4 + k; // i: 0→1, j: 0→2, k: 0→3 (C-style [size] ranges start at 0) // Mixed packed+unpacked: skip packed dimension with empty variable bit [3:0][2:1] B [5:1][4]; // unpacked [5:1][4], packed [3:0][2:1] // foreach iterates unpacked dims only: [5:1] and [4] // Skip the 3rd dimension (packed) with an empty entry: foreach(B[q, r, , s]) ... // q: 5→1, r: 0→3, s: 2→1 (dim 3 skipped)
// Dimension cardinality rule:
// Higher cardinality = varies more rapidly = inner loop
// Dimension 1 = outermost/slowest
// Last dimension = innermost/fastest
int A[2][3][4]; // dim1=2 (outermost), dim3=4 (innermost)
foreach(A[i,j,k])
// i runs 0→1 (slowest)
// j runs 0→2
// k runs 0→3 (fastest)
// Reversed-range arrays: foreach respects the declared direction
int Rev[7:0]; // left=7, right=0
foreach(Rev[i])
// i runs 7→0 (down)
| Array declaration | foreach variables | Iteration |
|---|---|---|
| int A[2][3][4] | foreach(A[i,j,k]) | i: 0→1, j: 0→2, k: 0→3 |
| bit B[5:1][4] (packed) | foreach(B[q,r,,s]) | q: 5→1, r: 0→3, s: 2→1 |
| int Rev[7:0] | foreach(Rev[i]) | i: 7→0 (respects direction) |
| string arr[string] | foreach(arr[name]) | name: each key, lexicographic order |
| int dyn[] | foreach(dyn[i]) | i: 0→dyn.size()-1 |
foreach loop variable inside the loop body. It is also an error to name a loop variable with the same identifier as the array itself. Outside the loop, the variable does not exist — it is strictly local and automatic to the loop.
⏩ Jump Statements — break, continue, return
SystemVerilog adds three C-style jump statements. All three were previously missing from Verilog — the only workaround was using disable with named blocks, which required inventing names and was error-prone.
// break: exit loop immediately int arr[] = new[10]; foreach(arr[i]) begin if(arr[i] == 0) break; // stop at first zero element process(arr[i]); end // continue: skip to next iteration for(int i=0; i<16; i++) begin if(i % 2 == 0) continue; // skip even values — only process odd $display("odd: %0d", i); end // return with value (in a function) function automatic int find_first(input int arr[], input int target); foreach(arr[i]) if(arr[i] == target) return i; // found — return index immediately return -1; // not found endfunction // return without value (in a task) task automatic send_packet(input int payload[]); if(payload.size() == 0) return; // early exit — nothing to send // ... send logic endtask
📋 Jump Statement Rules
| Statement | Where legal | What it does | Restriction |
|---|---|---|---|
| break | Inside any loop | Exits the innermost enclosing loop | Cannot cross a fork…join boundary to break an outer loop |
| continue | Inside any loop | Jumps to the loop step/condition; loop continues | Cannot cross a fork…join boundary to continue an outer loop |
| return expr | Inside a function with non-void return type | Exits the function, returns expr to caller | Expression must be of a type compatible with the function’s return type |
| return | Inside a task or void function | Exits the task/function immediately | Cannot have an expression after return in a task |
Verilog-2001: cumbersome disable workaround
// Needed a named block just to break begin : search_loop for(i=0; i<16; i=i+1) begin if(arr[i]==target) disable search_loop; // break workaround end end
SystemVerilog: clean break
// No naming needed — break is direct for(int i=0; i<16; i++) begin if(arr[i]==target) break; // clean, obvious end
fork…join block, a break or continue inside the forked block cannot control the outer loop. This restriction exists because the forked threads run concurrently — by the time break would execute inside a thread, the loop may have already advanced. Use event synchronisation or flags to communicate between forked threads and outer loops.
📍 final Block
The final block is the simulation-end counterpart to the initial block. It executes at the end of simulation, triggered by $finish (explicit or implicit), before any PLI end-of-simulation callbacks. Final blocks are used for printing statistics, flushing log files, or checking final design state.
// Print simulation summary at the end final begin $display("=== Simulation Complete ==="); $display("Cycles executed: %0d", $time / period); $display("Final PC: %0h", PC); $display("Packets sent: %0d", pkt_count); end // Check a final state invariant final assert(fifo.empty()) else $error("FIFO not empty at end of simulation");
final block rules
- Only statements legal inside a
functionare allowed inside afinalblock — this ensures it executes in zero time. - A
finalblock does not run as a separate process; it executes like a function call. - Multiple
finalblocks in a design execute in an arbitrary but deterministic order (tool-defined, consistent across runs). - Calling
$finishfrom inside afinalblock causes simulation to end immediately. - Each
finalblock executes exactly once per simulation. - Final blocks execute before any PLI callbacks that indicate the end of simulation.
initial — runs at start of simulation
initial begin $display("Simulation starting"); // Only runs once at time 0 end
final — runs at END of simulation
final begin $display("Simulation ending at t=%0t", $time); // Only timing-free statements allowed end
final must execute in zero simulation time, it cannot contain any timing control: no #delay, no @event, no wait. Only procedural statements that complete without advancing simulation time are legal. This is the same restriction as function bodies — any statement you could put in a function is legal in a final block.
🏷 Named Blocks & Statement Labels
Verilog-2001 allowed begin : name for block naming. SystemVerilog adds two improvements: a closing name after the block end keyword, and the ability to label any individual statement.
Closing name on begin…end and fork…join
// Verilog-2001: only opening name begin : blockA // ... statements end // which begin does this end? Hard to tell in nested code // SystemVerilog: matching closing name begin : blockB // ... statements end : blockB // explicitly matches opening — improves readability // fork…join variants also support closing names fork : parallel_work thread1(); thread2(); join : parallel_work fork : fire_and_forget background_task(); join_none : fire_and_forget
Statement labels
// A label can be placed before any statement — like C's goto labels // but in SV, labels are used for disable (not goto — SV has no goto) labelA: my_task(); // label on a single statement labelB: if(cond) ...; // label on an if statement // Label on a begin...end block (label goes BEFORE begin) labelC: fork job1(); job2(); join // no colon after join when using a label before fork // ILLEGAL: both a label before begin AND a block name after begin // labelD: begin : blockE ... end : blockE // ERROR: not allowed
Using statement labels with disable
// A labelled statement can be disabled by name — same as a named block // This provides a named target for disable without naming the block itself initial begin run_test: for(int i=0; i<100; i++) begin if(error_detected) disable run_test; // break out of named loop run_one_test(i); end end
Practical use: nested loop readability
// Deep nesting — closing names make structure obvious module top; initial begin : test_main for(int i=0; i<4; i++) begin : outer_loop for(int j=0; j<8; j++) begin : inner_loop drive_and_check(i, j); end : inner_loop // clearly matches its begin end : outer_loop end : test_main endmodule
📋 Quick Reference
Loop kinds cheatsheet
| Loop | Test position | New in SV | Key use case |
|---|---|---|---|
| forever | Never ends | No | Clock generators, always-running monitors |
| repeat(N) | N times fixed | No | Fixed number of transactions |
| while(cond) | Before body | No | General conditional loop |
| for(…) | Before body | Enhanced | Index-driven iteration; in-loop vars; multiple init/step |
| do…while(cond) | After body | Yes | At-least-once loops; assoc array traversal; polling |
| foreach(arr[…]) | Over all elements | Yes | Any array iteration — fixed, dynamic, assoc, queue |
foreach variable types by array kind
| Array kind | Loop variable type |
|---|---|
| Fixed-size, dynamic, queue | int |
Associative — typed index (e.g. string) | Same as the index type |
Associative — wildcard index (*) | unsigned longint |
Jump statement rules
breakandcontinuework in any loop — for, while, do-while, foreach, forever, repeat.breakexits the innermost loop only — cannot skip outer loops.- Neither
breaknorcontinuecan cross afork…joinboundary. return exprin functions — expression type must match return type.returnin tasks and void functions — no expression.- There is no
gotoin SystemVerilog.
final block rules
- Executes once at end of simulation (triggered by
$finish). - Only function-legal statements — no timing controls.
- Executes in zero simulation time (like a function call, not a separate process).
- Multiple
finalblocks: order is tool-defined but deterministic.
Named block / label rules
- Closing name is optional but must match opening name exactly if given.
- A statement label goes before the statement; a block name goes after the
begin/forkkeyword. - Cannot have both a label before
beginand a block name afterbeginon the same block. - Cannot have a closing name on a statement label — only on block end/join keywords.
- Labels and block names can be used with
disableto exit named constructs.
