A Verilog for loop repeats a group of statements while a condition holds, with a loop variable that steps from a start value to an end value. In software, a loop runs its iterations one after another in time. In synthesizable Verilog, a for loop is a shorthand for replicating hardware: the synthesis tool unrolls every iteration into its own copy of the logic.

12 min read | Beginner Level

What You'll Learn

  • The syntax of the for loop and how it differs from a software loop
  • How synthesis unrolls a loop into parallel hardware
  • Write compact, parameterized designs with for loops in clocked and combinational blocks
  • Avoid loop bugs: non-constant bounds, narrow loop variables and wrong assignment types

Syntax

A for loop has three parts in its header: an initial assignment, a condition checked before each iteration, and a step assignment that runs after each iteration. Like other procedural statements, it must be inside an always or initial block.


for (<initial_condition>; <condition>; <step_assignment>) begin
	// Statements
end
  • <initial_condition> - Sets the loop variable's starting value, such as i = 0 (required)
  • <condition> - The loop runs while this is true, such as i < 10 (required)
  • <step_assignment> - Updates the loop variable after each iteration, such as i = i + 1 (required)
  • begin ... end - Groups several statements in the loop body; optional for a single statement

The loop variable is usually declared as an integer in the module. A while loop is more general, since it only needs a condition. A for loop is the better fit when there is a clear start, end and step, which is almost always the case in RTL.


module my_design;
	integer i;

	initial begin
		// Note that ++ operator does not exist in Verilog !
		for (i = 0; i < 10; i = i + 1) begin
			$display ("Current loop#%0d ", i);
		end
	end
endmodule
Output
ncsim> run
Current loop#0
Current loop#1
Current loop#2
Current loop#3
Current loop#4
Current loop#5
Current loop#6
Current loop#7
Current loop#8
Current loop#9
ncsim: *W,RNQUIE: Simulation is complete.

The loop starts at i = 0 and stops when i reaches 10, so the body runs 10 times. Verilog has no ++ operator, so the step is written as i = i + 1. The ++ operator was added in SystemVerilog.

How Synthesis Handles a for Loop

Hardware has no notion of "running a loop". A synthesis tool unrolls the loop: it calculates every value the loop variable takes, then writes out the loop body once for each value, as if you had typed every iteration by hand. All the copies exist side by side in the hardware and work at the same time.

This has two consequences:

  • The loop bounds must be constants at compile time, such as numbers or parameters. The tool must know how many copies to build.
  • The loop takes no time. All iterations happen within one evaluation of the always block, so a loop inside a clocked block finishes in a single clock cycle, not one iteration per clock.

For loops are also used inside generate blocks to replicate module instances and assign statements, using a genvar loop variable. That is a different construct; see Verilog generate block.

Design Example

This 8-bit register clears on reset, loads load_val when load_en is 1, and otherwise moves every bit one position to the left at each clock edge. Bit 7 wraps around into bit 0, so the bits rotate rather than shift out. First, the design without a loop:


module lshift_reg (input 						clk,				// Clock input
                   input 						rstn,				// Active low reset input
                   input [7:0] 			load_val, 	// Load value
                   input 						load_en, 		// Load enable
                   output reg [7:0] op); 				// Output register value

	 // At posedge of clock, if reset is low set output to 0
	 // If reset is high, load new value to op if load_en=1
	 // If reset is high, and load_en=0 shift register to left
	 always @ (posedge clk) begin
	    if (!rstn) begin
	      op <= 0;
	    end else begin
	    	if (load_en) begin
	      	op <= load_val;
	      end else begin
	        op[0] <= op[7];
	        op[1] <= op[0];
	        op[2] <= op[1];
	        op[3] <= op[2];
	        op[4] <= op[3];
	        op[5] <= op[4];
	        op[6] <= op[5];
	        op[7] <= op[6];
	      end
	    end
	  end
endmodule

The eight assignments follow one pattern: op[i+1] <= op[i]. A for loop writes that pattern once:


module lshift_reg (input 						clk,				// Clock input
                   input    				rstn,				// Active low reset input
                   input [7:0] 			load_val, 	// Load value
                   input 						load_en, 		// Load enable
                   output reg [7:0] op); 				// Output register value

	 integer i;

	 // At posedge of clock, if reset is low set output to 0
	 // If reset is high, load new value to op if load_en=1
	 // If reset is high, and load_en=0 shift register to left
	 always @ (posedge clk) begin
	    if (!rstn) begin
	      op <= 0;
	    end else begin

	    	// If load_en is 1, load the value to op
	    	// else keep shifting for every clock
	    	if (load_en) begin
	      	op <= load_val;
	      end else begin
            for (i = 0; i < 7; i = i + 1) begin   // i = 0..6 covers op[1] to op[7]
              op[i+1] <= op[i];
            end
            op[0] <= op[7];                       // Wrap bit 7 around to bit 0
	      end
	    end
	  end
endmodule

Synthesis result: The loop unrolls into exactly the eight assignments of the first version, so both designs build identical hardware: eight flip-flops with synchronous reset, and in front of each, a 2 to 1 mux that selects load_val or the neighboring bit. The rotate itself is only wiring between flip-flops and costs no logic. Non-blocking assignments (<=) make every bit capture its neighbor's old value, which is what a shift needs.

hardware schematic for shift register

Parameterized Version

The main benefit of the loop is that the width can become a parameter. Without a loop, a 64-bit version would need 64 hand-written assignments. With a loop, the same code works for any width:


module lshift_reg_n #(parameter N = 8) (   // N sets the register width
  input              clk,
  input              rstn,
  input      [N-1:0] load_val,
  input              load_en,
  output reg [N-1:0] op
);

  integer i;

  always @(posedge clk) begin
    if (!rstn)
      op <= 0;
    else if (load_en)
      op <= load_val;
    else begin
      for (i = 0; i < N-1; i = i + 1)   // Bound uses the parameter: still a constant
        op[i+1] <= op[i];
      op[0] <= op[N-1];                 // Wrap the MSB around to bit 0
    end
  end

endmodule

Instantiating lshift_reg_n #(.N(32)) gives a 32-bit rotating register with no code changes. The loop bound N-1 is still a constant, because parameters are fixed at compile time.

Where is a for Loop Used?

Parameterized IP blocks, such as FIFOs, bus width converters and CRC generators, are delivered as one RTL file that each project configures through parameters. Inside them, for loops handle the per-bit and per-byte logic, for example reversing byte order or masking bytes with a byte-enable signal, so the same code works for 32, 64 or 128-bit buses.

Quick Check

Q: In lshift_reg, how many clock cycles does the for loop take to move all eight bits?

Show Answer

One. The loop is unrolled into eight parallel assignments, and all eight flip-flops update at the same clock edge. The loop describes structure, not a sequence in time.

Testbench

The testbench holds reset for two clocks, runs for five more clocks with op at 0, loads 8'h01 for one clock, then lets the register rotate for 20 clocks. See Verilog Testbench for testbench structure.


module tb;
  reg clk;
  reg rstn;
  reg [7:0] load_val;
  reg load_en;
  wire [7:0] op;

  // Setup DUT clock
  always #10 clk = ~clk;

  // Instantiate the design
  lshift_reg u0 ( .clk(clk),
                 .rstn (rstn),
                 .load_val (load_val),
                 .load_en (load_en),
                 .op (op));

  initial begin
  	// 1. Initialize testbench variables
    clk <= 0;
    rstn <= 0;
    load_val <= 8'h01;
    load_en <= 0;

    // 2. Apply reset to the design
    repeat (2) @ (posedge clk);
    rstn <= 1;
    repeat (5) @ (posedge clk);

    // 3. Set load_en for 1 clk so that load_val is loaded
    load_en <= 1;
    repeat(1) @ (posedge clk);
    load_en <= 0;

    // 4. Let design run for 20 clocks and then finish
    repeat (20) @ (posedge clk);
    $finish;
  end
endmodule
Waveform of the 8-bit rotating register: op loads 0x01, then moves the 1 one bit to the left each clock and wraps from bit 7 back to bit 0

In the waveform, op stays 0 through reset and the following five clocks, since rotating 0 still gives 0. After the load, op is 8'h01, then 02, 04, 08 and so on up to 80. At the next clock, bit 7 wraps around and op returns to 01, so the pattern repeats every eight clocks.

for Loop in Combinational Logic

Loops are also useful in combinational always @(*) blocks, where each iteration builds on the result of the previous one. This module counts how many bits of its input are 1, often called a population count:


module popcount #(parameter N = 8) (
  input      [N-1:0]           in,
  output reg [$clog2(N+1)-1:0] count     // Wide enough to hold the value N
);

  integer i;

  always @(*) begin
    count = 0;                           // Start from 0 on every evaluation
    for (i = 0; i < N; i = i + 1)
      count = count + in[i];             // Blocking (=): each iteration sees the
                                         // value written by the previous one
  end

endmodule

Synthesis result: Unrolled, the loop is a chain of N additions: ((0 + in[0]) + in[1]) + .... The synthesis tool usually rebalances such a chain into an adder tree, so the delay grows with log2N instead of N. The design uses blocking assignments, because each iteration must read the count value written by the iteration before it.

PPA Impact: Because every iteration becomes real hardware, the area of a loop grows with its iteration count. A loop over 1024 bits in a combinational block builds 1024 copies of its body. Check the loop bounds of wide parameterized designs, since a small edit to a bound can change the gate count by orders of magnitude.

Population Count in Hardware

Counting the 1 bits in a word appears in several places: error-correcting code (ECC) logic in memory controllers measures how many bits differ between two codes, network switches count active ports, and many processor instruction sets include a popcount instruction. Each is described with a loop like the one above and synthesized into an adder tree.

Loops in Testbenches

In verification, for loops run in time, because testbench code is simulated rather than synthesized and can include delays and clock waits. A memory test, for example, loops over every address, writes a pattern, waits for the write to finish, then loops again to read each location back and compare it.

Quick Check

Q: Can the loop bound in popcount be an input port, such as for (i = 0; i < num_bits; i = i + 1)?

Show Answer

Not for synthesis. The tool must know how many copies of the loop body to build, so the bound must be a constant or a parameter. To count only the lowest num_bits bits, loop over all N bits and add in[i] only when i < num_bits.

Common Beginner Mistakes

Mistake #1: Using ++ to step the loop

Wrong: for (i = 0; i < 8; i++)
Right: for (i = 0; i < 8; i = i + 1)

Why? Verilog has no ++ operator, so the compiler reports a syntax error. ++ is valid in SystemVerilog, which is why it appears in some code you may see elsewhere.

Mistake #2: A loop variable too narrow for the bound

Wrong: reg [2:0] i; with for (i = 0; i < 8; i = i + 1)
Right: integer i;

Why? A 3-bit i can only hold 0 to 7. After 7, i + 1 wraps to 0, so i < 8 is never false and the loop never ends. Simulation hangs, and synthesis fails or runs out of memory while unrolling. Declaring the loop variable as an integer (32 bits, signed) avoids this.

Mistake #3: Non-constant loop bounds in synthesizable code

Wrong: for (i = 0; i < len; i = i + 1), where len is a signal
Right: Loop up to a constant or parameter, and check the signal inside: if (i < len) ...

Why? The loop is unrolled at compile time, so its iteration count cannot depend on a value that only exists while the hardware runs. Synthesis tools report an error. Simulation accepts it, so the problem appears only when you synthesize.

Mistake #4: Non-blocking assignments in an accumulating loop

Wrong: always @(*) begin count = 0; for (...) count <= count + in[i]; end
Right: Use blocking assignments: count = count + in[i];

Why? A non-blocking assignment updates count only after the whole block finishes, so every iteration reads the same old value. Only the last iteration's result is kept, so count ends up equal to in[N-1]. When each iteration depends on the previous one, use blocking assignments. When each iteration writes a different signal, as in the shift register, non-blocking assignments are correct. See Verilog Blocking & Non-Blocking.

Practice Exercise

Challenge: Parameterized Bit Reverse

Write a combinational module bit_reverse with a parameter N, an N-bit input in and an N-bit output out, where out[i] = in[N-1-i] for every bit. Use a for loop. What hardware does it synthesize into?

Hint

Declare out as reg and write the loop in an always @(*) block. Each iteration assigns a different output bit, so no initial assignment is needed as long as the loop covers every bit.

Solution

module bit_reverse #(parameter N = 8) (
  input      [N-1:0] in,
  output reg [N-1:0] out
);

  integer i;

  always @(*) begin
    for (i = 0; i < N; i = i + 1)
      out[i] = in[N-1-i];        // Bit 0 gets bit N-1, bit 1 gets bit N-2, ...
  end

endmodule

The loop unrolls into N assignments that connect each output bit to a different input bit. Synthesis builds no gates at all, only wires. This shows that a for loop costs only as much hardware as its unrolled body: a loop of pure assignments is free, while a loop of additions builds an adder per iteration.

Summary

In this tutorial, you learned:

  • A for loop has an initial assignment, a condition and a step, and must be inside a procedural block
  • Synthesis unrolls the loop into one copy of the body per iteration, all working in parallel
  • Loop bounds must be constants or parameters, and the loop takes no clock cycles in hardware
  • Loops make designs shorter and let a parameter set the width, as in the rotating shift register
  • The hardware cost of a loop is its unrolled body: wiring is free, arithmetic grows with the iteration count
  • Use integer loop variables and i = i + 1, since Verilog has no ++
  • Use blocking assignments when each iteration reads the previous result, and non-blocking for independent register bits
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5