VHDL · Chapter 5.6 · Sequential Statements & Processes
Loops — for, while, and loop
A loop in a process looks like software iteration, but for hardware it is something different. A for loop with static bounds unrolls into replicated logic, the process-side counterpart of the generate statement. Each iteration becomes a copy of the body, parameterised by the loop index, and all the copies are built at once and run in parallel. That single idea, that loops unroll rather than iterate over time, is what keeps you from writing logic that explodes in size or refuses to synthesise. This lesson covers the synthesizable for loop and how it builds reductions, bit-sliced logic, and array operations from one body, the while and plain loop forms with exit and next, and why static bounds matter for anything you intend to turn into hardware.
Foundation14 min readVHDLfor loopUnrollingReplicationReductionsSequential
1. Engineering intuition — a loop is replication, not iteration
In software a loop runs its body again and again over time. In synthesizable VHDL a for loop does
not run over time at all — the tool unrolls it, stamping out one copy of the body per index value
and wiring them together. A loop over 0 to 7 is eight parallel (or chained) copies of the body, not
one block executed eight times. This is the same replication you met with generate (lesson 4.8),
now available inside a process for sequential-style description. Keep "unroll, do not iterate" in mind
and loops stop being mysterious.
2. Formal explanation — the three loop forms
-- FOR loop: static range, loop variable is a per-iteration constant. Synthesizable when bounds are static.
for i in 0 to N-1 loop
-- body, parameterised by i (i is read-only, declared implicitly)
end loop;
-- WHILE loop: repeats while a condition holds. Synthesizable only if statically bounded.
while count < LIMIT loop
-- body
end loop;
-- Plain LOOP with exit/next: general loop; 'exit' leaves it, 'next' skips to the next iteration.
loop
-- body
exit when done; -- leave the loop
end loop;The for loop's range must be static (constants/generics) to synthesise — the tool needs to know
how many copies to build. The loop variable is an implicitly-declared, read-only per-iteration
constant. while and plain loop are general but synthesise only when statically bounded; they are
common in testbenches. exit leaves a loop; next skips to the next iteration.
3. Production RTL — reductions and bit-sliced logic
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- (A) Population count (number of '1' bits) — a for loop unrolled into an adder tree/chain.
popcount : process (all)
variable cnt : unsigned(3 downto 0);
begin
cnt := (others => '0');
for i in data'range loop
if data(i) = '1' then cnt := cnt + 1; end if; -- one comparator+add per bit, unrolled
end loop;
ones <= std_logic_vector(cnt);
end process;
-- (B) Initialise / transform an array with a for loop (unrolled wiring).
rev : process (all)
begin
for i in din'range loop
dout(din'high - i) <= din(i); -- bit-reverse: pure routing, one assignment per bit
end loop;
end process;What hardware does this become? (A) the loop unrolls into eight conditional increments — an adder
chain/tree counting set bits, combinational logic with no clock; (B) the loop unrolls into wiring that
reverses the bus — no logic at all, just routing. Neither "runs" eight times; each is one piece of
combinational hardware built from the unrolled body. (cnt is a variable so each iteration sees the
running total immediately — the anchor's immediate-update rule.)
4. Hardware interpretation — unrolled copies
5. Simulation interpretation — unrolled, then run in zero time
By the execution model, when the process wakes the (unrolled) loop body runs in zero simulated time —
the iterations are sequential within that single wake-up, so a variable accumulated across iterations
(like cnt) carries its running value from one iteration to the next immediately. Real time does not
pass between iterations; the whole loop is part of one process run.
Combinational popcount: the for loop output tracks the input bit count
8 cycles6. Debugging example — the non-static loop bound
Expected: a for loop synthesises. Observed: the synthesiser errors or refuses, complaining
the loop bound is not static (or it builds enormous logic). Root cause: the loop range depended on
a signal (a runtime value) instead of a constant/generic, so the tool cannot know how many copies
to build; or the static bound was very large, unrolling into huge hardware. Fix: make the range a
static constant/generic, and keep the count modest (large reductions may need a clocked, multi-cycle
implementation instead of a single unrolled cone). Engineering takeaway: synthesizable loops have
static bounds and unroll fully — if a loop count must vary at runtime, that is sequential
(multi-cycle) behaviour, not a single unrolled loop.
-- WRONG: loop bound from a runtime signal → not statically unrollable → synthesis error.
-- for i in 0 to to_integer(unsigned(len)) loop ... end loop;
-- RIGHT: static range (constant/generic); process only the bits you need with a guard.
for i in 0 to N-1 loop
if i < to_integer(unsigned(len)) then acc := acc + data(i); end if; -- static range, runtime guard
end loop;7. Common mistakes & what to watch for
- Non-static
for/whilebounds in synthesizable code. Synthesis needs static counts; use a constant/generic range with a runtime guard inside. - Expecting time to pass per iteration. A loop unrolls into one wake-up's worth of logic; it does not consume clock cycles. For multi-cycle behaviour use a clocked process + counter.
- Huge unroll. A large static loop builds large combinational logic (and long critical paths); consider a registered, iterative design instead.
- Accumulating in a signal across iterations. Use a variable for a running value within a loop (immediate update); a signal would read its old value each iteration.
- Forgetting
exit/nextsemantics.exitleaves the loop entirely;nextskips to the next iteration — mixing them up changes the unrolled logic.
8. Engineering insight
Loops are the sequential face of replication. A for loop lets you describe per-element computation —
reductions, bit-slicing, encoders, array transforms — once, and the tool unrolls it into the parallel
or chained hardware it implies. The discipline is to keep bounds static and counts sane, and to
remember that "iteration" here is spatial (copies of logic) for combinational loops, not temporal
(clock cycles). When you genuinely need iteration over time, that is a clocked process with a counter,
not a single unrolled loop — a distinction the execution model makes precise.
9. Summary
A for loop with static bounds unrolls at elaboration into replicated hardware — the process-side
analog of generate — so it describes spatial replication, not runtime iteration. while and plain
loop (with exit/next) are general but synthesise only when statically bounded. Use variables to
carry running values across iterations (immediate update), keep bounds static, and reach for a clocked
counter when you truly need multi-cycle iteration.
10. Learning continuity
Loops, if, and case all leaned on variables to carry intermediate and running values immediately.
The next lesson, Variables Inside Processes, examines them in full: their process-local scope and
immediate-update semantics, when a variable is just combinational wiring versus when read-before-write
infers a register, and the disciplined patterns (accumulators, intermediates) that make variables a
precise, predictable tool rather than a source of surprise storage.