A for loop in SystemVerilog repeats a set of statements as long as a condition stays true. The starting value, the exit condition and the step are all written in the loop header. That makes it the most common loop in both RTL design and testbenches. When the body has more than one statement, wrap them in begin and end, just like any other procedural block.
7 min read | Intermediate Level
What You'll Learn
- The syntax of a
forloop and the order in which its parts run - How to declare loop variables in the header and use several of them at once
- How a
forloop inalways_combsynthesizes into hardware - The loop variable mistakes that cause infinite loops and compile errors
Syntax
A for loop runs in three steps:
- Initialization runs once, before the first pass. It usually declares and sets the loop variable.
- Condition is checked before every pass, including the first. If it is false, the loop ends.
- Modifier (the step) runs at the end of every pass, and then the condition is checked again.
for ( [initialization]; <condition>; [modifier])
// Single statement
for ( [initialization]; <condition>; [modifier]) begin
// Multiple statements
end
- initialization (optional): for example
int i = 0. Variables declared here exist only inside the loop. - condition: for example
i < 8. Leaving it out, as infor (;;), creates an infinite loop. - modifier (optional): for example
i++,i += 2ori--.
SystemVerilog adds C-style increment and decrement operators, which are the usual way to write the step. They can be used as a pre-increment or a post-increment:
| Statement | Description |
|---|---|
j = i++ | Post-increment: assign i to j, then increment i by 1 |
j = ++i | Pre-increment: increment i by 1, then assign i to j |
j = i-- | Post-decrement: assign i to j, then decrement i by 1 |
j = --i | Pre-decrement: decrement i by 1, then assign i to j |
Used on its own as a loop step, i++ and ++i do the same thing.
Using the for Loop in Digital Design
A for loop is synthesizable when its bounds are constants (literals or parameters) and its body has no timing controls such as # or @.
The synthesis tool unrolls the loop. It makes one copy of the body for each iteration and turns all the copies into logic. A loop that runs eight times in simulation becomes eight slices of hardware that all work in parallel.
A priority encoder is a good example. It outputs the position of the highest-priority active input. Unlike a plain encoder, which assumes only one input is active, it handles several active inputs at once and picks the one with the highest priority, here the highest index.
module priority_encoder #(parameter WIDTH = 8) (
input logic [WIDTH-1:0] req,
output logic [$clog2(WIDTH)-1:0] grant,
output logic valid
);
always_comb begin
grant = '0;
valid = 1'b0;
// Process from LOW to HIGH priority, so higher index overwrites lower
for (int i = 0; i < WIDTH; i++) begin
if (req[i]) begin
grant = i;
valid = 1'b1;
end
end
end
endmodule
The loop scans req from bit 0 upwards. Every set bit overwrites grant, so after the last iteration grant holds the index of the highest set bit. For example, req = 8'b0010_0110 gives grant = 5. Because WIDTH is a parameter, the loop unrolls into a chain of 2-to-1 muxes, one per request bit. Changing WIDTH resizes the hardware with no other code changes.
The order of the iterations matters. In unrolled hardware, a later iteration's assignment sits closer to the output, so it wins. That is how this loop gives the highest index priority.
Where is the for loop used in RTL?
Arbiters, leading-zero counters, parity and CRC generators, and population counters are all commonly written as parameterized for loops in always_comb. A single module can then serve a 4-requester arbiter in one block and a 32-requester arbiter in another.
Using the for Loop in Verification
In a testbench there are no synthesis limits. Bounds can come from variables or $size(), and the body can wait on clocks. The example below prints every element of a string array of fruit names.
The initialization declares a local variable i that holds the current index. The condition keeps i below the size of the array, and the step moves i to the next index on every pass.
module tb;
string array [5] = '{"apple", "orange", "pear", "blueberry", "lemon"};
initial begin
for (int i = 0; i < $size(array); i++)
$display ("array[%0d] = %s", i, array[i]);
end
endmodule
ncsim> run array[0] = apple array[1] = orange array[2] = pear array[3] = blueberry array[4] = lemon ncsim: *W,RNQUIE: Simulation is complete.
Use $size(array) instead of a hard-coded 5. The loop then stays correct if the array size changes. To visit every element of an array, foreach is even shorter.
Quick Check
Q: How many times does the body of for (int i = 10; i > 0; i -= 3) run?
Show Answer
Four times, with i = 10, 7, 4 and 1. After the fourth pass i becomes -2, the condition i > 0 fails, and the loop ends.
Multiple Initializations
The initialization part can set up more than one variable, separated by commas. Below, i and j are both declared as int when the loop starts. Each pass replaces the character at index j = 2 of the current string with "0".
module tb;
string array [5] = '{"apple", "orange", "pear", "blueberry", "lemon"};
initial begin
for (int i = 0, j = 2; i < $size(array); i++) begin
array[i][j] = "0";
$display ("array[%0d] = %s, %0dth index replaced by 0", i, array[i], j);
end
end
endmodule
ncsim> run array[0] = ap0le, 2th index replaced by 0 array[1] = or0nge, 2th index replaced by 0 array[2] = pe0r, 2th index replaced by 0 array[3] = bl0eberry, 2th index replaced by 0 array[4] = le0on, 2th index replaced by 0 ncsim: *W,RNQUIE: Simulation is complete.
String characters are indexed from 0, so index 2 is the third character: the first "p" in "apple" and the "a" in "orange".
Multiple Modifiers
The modifier part can also hold several comma-separated expressions. Here i counts up while j counts down. j starts at the last index of the first string ("apple", so 4) and decreases by one on every pass.
module tb;
string array [5] = '{"apple", "orange", "pear", "blueberry", "lemon"};
initial begin
for (int i = 0, j = array[i].len() - 1; i < $size(array); i++, j--) begin
array[i][j] = "0";
$display ("array[%0d] = %s, %0dth index replaced by 0", i, array[i], j);
end
end
endmodule
ncsim> run array[0] = appl0, 4th index replaced by 0 array[1] = ora0ge, 3th index replaced by 0 array[2] = pe0r, 2th index replaced by 0 array[3] = b0ueberry, 1th index replaced by 0 array[4] = 0emon, 0th index replaced by 0 ncsim: *W,RNQUIE: Simulation is complete.
j is calculated only once, from the first string. After that it just counts down 4, 3, 2, 1, 0. It is not the last index of each string, which is why "orange" has its fourth character replaced rather than its last.
Common Beginner Mistakes
Mistake #1: A loop variable too narrow for the condition
Wrong: for (bit [2:0] i = 0; i < 8; i++)
Right: for (int i = 0; i < 8; i++)
Why? A 3-bit variable can only hold 0 to 7. After 7 it wraps back to 0, so i < 8 is always true and the loop never ends.
Mistake #2: Using the loop variable after the loop
A variable declared in the header, as in for (int i = 0; ...), exists only inside the loop. Reading i after end is a compile error. Why? The header creates a new local scope. If you need the final value, declare the variable before the loop and write for (i = 0; ...).
Mistake #3: A non-constant bound in RTL
Wrong (RTL): for (int i = 0; i < num_req; i++), where num_req is a signal
Right (RTL): for (int i = 0; i < MAX_REQ; i++) if (i < num_req) ...
Why? Synthesis must know how many copies of the body to build. Loop to a constant maximum and use an if inside for the run-time limit.
Mistake #4: Forgetting begin and end
Without begin and end, only the first statement after the header belongs to the loop. Why? Indentation means nothing to the compiler. The remaining statements run once, after the loop finishes.
Practice Exercise
Challenge: Count the ones
Write a synthesizable module that takes an 8-bit input data and outputs ones, the number of bits in data that are 1. Use a for loop inside always_comb.
Hint
Start ones at zero before the loop, then add each bit of data to it. The largest possible count is 8, so how many bits does ones need?
Solution
module popcount (
input logic [7:0] data,
output logic [3:0] ones // 0 to 8 needs 4 bits
);
always_comb begin
ones = '0; // default before the loop
for (int i = 0; i < 8; i++)
ones += data[i]; // add 1 for every bit that is set
end
endmodule
The loop unrolls into eight adder stages, which synthesis typically optimizes into an adder tree. For data = 8'b1011_0010, ones = 4.
Summary
In this tutorial, you learned:
- A
forloop runs the initialization once, checks the condition before every pass, and runs the modifier after every pass - Variables declared in the header are local to the loop and cannot be used after it
- The header can initialize and update several variables, separated by commas
++and--come in pre and post forms; as a loop step they behave the same- With constant bounds and no timing control, a
forloop unrolls into parallel hardware, and iteration order sets priority - A loop variable that is too narrow wraps around and makes the loop infinite