SYSTEMVERILOG SERIES · SV-08B

SystemVerilog Series — SV-08b: Loops, Jumps, Final & Labels — VLSI Trainers
SystemVerilog Series · SV-08b

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.

forever
Verilog-2001
Loops indefinitely. Must contain a timing control or will run infinitely in zero time.
repeat(N)
Verilog-2001
Execute the body exactly N times. N evaluated once before the loop starts.
while(cond)
Verilog-2001
Test before executing. If condition false initially, body never executes.
for(…)
Verilog-2001 + SV enhanced
Init, condition, step. SV adds in-loop variable declaration and multiple inits/steps.
do-while(cond)
New in SV
Test after executing. Body always runs at least once. Use when one execution is guaranteed.
foreach(arr[…])
New in SV
Iterates every element of any array — fixed, dynamic, or associative. Loop vars auto-typed and automatic.

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
In-loop variables are automatic. A variable declared inside a 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
Verilog-2001 shared loop variable hazard: In Verilog-2001, if two 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]);
Loop variable type: For fixed-size and dynamic arrays, loop variables are automatically typed as 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 numbering and iteration order
// 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 declarationforeach variablesIteration
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
Loop variable is read-only. You cannot assign to a 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
Exits the innermost enclosing loop immediately. Equivalent to jumping to the statement after the loop.
continue
Skips the rest of the current loop body and jumps to the loop’s step/condition. Loop continues normally.
return
Exits a task or function. In functions returning a value, must include the return expression. In void functions and tasks, no expression needed.
// 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

StatementWhere legalWhat it doesRestriction
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
break/continue cannot cross fork…join boundaries. If you have a loop that contains a 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 function are allowed inside a final block — this ensures it executes in zero time.
  • A final block does not run as a separate process; it executes like a function call.
  • Multiple final blocks in a design execute in an arbitrary but deterministic order (tool-defined, consistent across runs).
  • Calling $finish from inside a final block causes simulation to end immediately.
  • Each final block 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
No timing controls in final. Because 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
Closing name must match opening name. If a closing name is provided, it must exactly match the block’s opening name. A mismatch is a compile error. The closing name is entirely optional — it only appears for documentation and readability. You cannot have only a closing name; the opening name must exist too.

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

LoopTest positionNew in SVKey use case
foreverNever endsNoClock generators, always-running monitors
repeat(N)N times fixedNoFixed number of transactions
while(cond)Before bodyNoGeneral conditional loop
for(…)Before bodyEnhancedIndex-driven iteration; in-loop vars; multiple init/step
do…while(cond)After bodyYesAt-least-once loops; assoc array traversal; polling
foreach(arr[…])Over all elementsYesAny array iteration — fixed, dynamic, assoc, queue

foreach variable types by array kind

Array kindLoop variable type
Fixed-size, dynamic, queueint
Associative — typed index (e.g. string)Same as the index type
Associative — wildcard index (*)unsigned longint

Jump statement rules

  • break and continue work in any loop — for, while, do-while, foreach, forever, repeat.
  • break exits the innermost loop only — cannot skip outer loops.
  • Neither 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.
  • There is no goto in 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 final blocks: 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/fork keyword.
  • Cannot have both a label before begin and a block name after begin on 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 disable to exit named constructs.
Coming next: SV-09 covers Processes — always_comb, always_ff, always_latch, fork-join variants (join_any / join_none), wait fork, fine-grained event control, and the process class (Section 9).

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top