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.
Verilog-2001 provided four loop types. SystemVerilog adds two new ones and enhances for.
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.
int i = 10; while (i < 5) begin // never executes — condition false from the start $display(i); i++; end
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)
// 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
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.
// 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.// 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);
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.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.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.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
| 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 |
// 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
// 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.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");
function are allowed inside a final block — this ensures it executes in zero time.final block does not run as a separate process; it executes like a function call.final blocks in a design execute in an arbitrary but deterministic order (tool-defined, consistent across runs).$finish from inside a final block causes simulation to end immediately.final block executes exactly once per simulation.initial begin $display("Simulation starting"); // Only runs once at time 0 end
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.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.
// 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
// 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
// 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
// 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
| 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 |
| 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 |
break and continue work in any loop — for, while, do-while, foreach, forever, repeat.break exits the innermost loop only — cannot skip outer loops.break nor continue can cross a fork…join boundary.return expr in functions — expression type must match return type.return in tasks and void functions — no expression.goto in SystemVerilog.$finish).final blocks: order is tool-defined but deterministic.begin/fork keyword.begin and a block name after begin on the same block.disable to exit named constructs.