Module 6 - Looping Construct

loop

Introduction to Looping Constructs

Introduction to Looping Constructs

Looping constructs are VHDL instructions that allow a program to repeat the same block of code, a process known as iteration (similar to C).

Loops in VHDL are divided into two main categories based on how they function and what they create.


1. Sequential Loops (Inside a process)

These loops are similar to those in traditional programming languages. They describe an algorithm or a sequence of operations that execute step-by-step.

There are two main types of sequential loops:


2. Concurrent Loops (Outside a process)

This type of loop is unique to hardware description languages. It doesn't describe an algorithm but instead describes the replication of hardware structures. It acts like copy and pasting ICs on a breadboard.

image

(well, that's the gist of it)

For Loop

For Loop

The for loop is the most common type of sequential loop in VHDL. It is used to repeat a block of code a specific, pre-determined number of times. This makes it ideal for tasks where you know exactly how many iterations you need, such as processing the bits of a vector.


Syntax

The basic structure of a for loop is:

loop_label: for <index_variable> in <range> loop
    -- code code code...
end loop loop_label;

Example:

testloop: for i in 0 to 2 loop
    -- code cedo deco ... .. . .
end loop

Notes

When using a for loop, there are a few important rules to remember:


Example: Init Memory

The code below shows the for loop iterating through every address of the memory to write a '0' value to it, like setting a RAM's content to 0.

type t_memory is array(0 to 63) of std_logic_vector(7 downto 0);

-- Inside your architecture...
p_Memory_Reset: process (i_Clock, i_Reset)
    variable v_ram : t_memory;
begin
    if (i_Reset = '1') then
        -- Initialize all 64 locations of the RAM to zero.
        -- We use a label "RAM_INIT_LOOP" for clarity.
        RAM_INIT_LOOP: for i in v_ram'range loop
        -- RAM_INIT_LOOP: for i in 0 to 63 loop <- would work too. variable`range is more flexible
            v_ram(i) := (others => '0');
        end loop RAM_INIT_LOOP;

    elsif rising_edge(i_Clock) then
        -- ... normal memory operation code ...
    end if;
end process p_Memory_Reset;

This loop is much cleaner than writing 64 individual lines of code. It uses v_ram'range (variable'range) to automatically loop through the entire size of the array.

While Loop

While Loop

The while loop is used where the number of repetitions is not known from the start. A while loop continues to execute as long as a specified condition is true.


Syntax

The basic structure of a while loop is:

loop_label: while <condition> loop
    -- Going through...
    -- IMPORTANT: Must include logic to eventually make the condition false!
end loop loop_label;

Warning: Infinite Loops

A while loop can create an infinite loop. This occurs if the loop's condition never becomes false. Unlike in C, Java, Python etc. where infinite loops are harmless, VHDL shouldn't have these.

To avoid this, ensure that the logic inside the loop will eventually cause the condition to become false.


Example: Finding the First '1'

In this example, we'll search a vector from left to right (from the most significant bit) to find the position of the first bit that is a '1'.

Notice that unlike a for loop, we must manually declare and update our index variable (i).

-- Inside a process...
p_Find_First_One: process(a_vector)
    -- We must declare our own index variable for a while loop
    variable i : integer := a_vector'left; -- Start at the leftmost bit
    variable i_position : integer := -1; -- -1 if no 1 is found
begin
    -- Reset variables for each run of the process
    i_position := -1;
    i := a_vector'left;

    FIRST_ONE: while (i >= a_vector'right) loop

        if a_vector(i) = '1' then
            i_position := i;
            exit FIRST_ONE; -- Quit if found
        end if;

        -- Manually decrement the index to check the next bit
        i := i - 1;

    end loop SEARCH_LOOP;

    -- Assign the result to a signal
    found_position_signal <= i_position;

end process p_Find_First_One;

In this code, the loop continues as long as i is within the vector's bounds. If a '1' is found, the exit statement terminates the loop early. If no '1' is found, the loop completes naturally when i goes out of bounds.

Loop Control - Next & Exit

Loop Control - Next & Exit

The two control statements, next and exit, allow you to skip an iteration or terminate the loop entirely, giving you more precise control over your sequential code.

These statements can be used in both for and while loops.


One, The next Statement

The next statement immediately stops the current loop iteration and jumps to the beginning of the next one. Any code that comes after the next statement within the loop body is skipped for that specific iteration (same thing as in C!).

The syntax can be written in two ways:

-- Form 1: Using an if-statement
if <condition> then
    next;
end if;

-- Form 2: Using the 'when' keyword
next when <condition>;

Example: Summing Only Odd Numbers

To skip even numbers, the next statement is perfect!

-- Inside a process...
variable sum : integer := 0;
...
SUM_ODD_LOOP: for i in 1 to 10 loop
    -- If the number is even, skip to the next iteration
    next when (i mod 2 = 0);

    -- This line is only reached for odd numbers
    sum := sum + i;
end loop SUM_ODD_LOOP;
-- At the end, sum will be 1+3+5+7+9 = 25

Two, The exit Statement

The exit statement terminates the loop entirely. As soon as exit is executed, the program moves to the line after end loop.

The syntax is similar to next:

-- Form 1: Using an if-statement
if <condition> then
    exit;
end if;

-- Form 2: Using the 'when' keyword
exit when <condition>;
Example: Finding a Value in Memory

Say, we want to search through a memory to find an address that holds a specific value. Once we find it, there is no reason to continue searching.

-- Inside a process...
constant SEARCH_VALUE : std_logic_vector(7 downto 0) := x"A5";
variable found_addr : integer := -1; -- Default value
...
SEARCH_MEM_LOOP: for i in ram'range loop
    -- If the value at the current address matches,
    -- store the address and exit the loop immediately.
    if ram(i) = SEARCH_VALUE then
        found_addr := i;
        exit SEARCH_MEM_LOOP;
    end if;
end loop SEARCH_MEM_LOOP;

-- The code goes on...

Using exit makes the search efficient. It prevents the loop from doing unnecessary work after the item has been found.

For-Generate Loop

The Concurrent 'for-generate' Loop

We now switch from sequential loops to a concurrent one.

The for-generate statement is not a loop that executes over time inside a process. Instead, it's a command that tells the synthesizer to create multiple copies of hardware structures.

It's good for repetitive structures like registers and chains of components. A key rule is that for-generate is used outside of a process, in the main architectural body.


Main Differentiators


Syntax

The basic structure of a for-generate statement is:

generate_label: for <identifier> in <range> generate
    -- Concurrent statements to be replicated go here...
    -- (e.g., component instantiations)
end generate generate_label;

Example: 4-Bit Adder

We start with a 1-bit full adder and use for-generate to create and connect four of them in a chain to make a 4-bit adder.

1. Replication Target First, we need the definition of the 1-bit full adder that we want to copy.

component full_adder is
    port (
        A, B, Cin  : in  std_logic;
        S, Cout    : out std_logic
    );
end component;

2. 4-Bit Adder Architecture Next, we use for-generate to create four instances of the full_adder and wire them together.

entity four_bit_adder is
    port (
        A, B    : in  std_logic_vector(3 downto 0);
        Cin     : in  std_logic;
        S       : out std_logic_vector(3 downto 0);
        Cout    : out std_logic
    );
end entity;

architecture structural of four_bit_adder is
    -- Internal signal to wire the carry chain between the adders.
    -- It needs 5 bits to include the first Cin and final Cout.
    signal C : std_logic_vector(4 downto 0);
begin

    -- The first carry wire is connected to the adder's carry-in pin
    C(0) <= Cin;

    -- Generate the chain of 4 full adders
    ADDER_CHAIN: for i in 0 to 3 generate
        -- Create one instance of the full_adder in each "iteration"
        FA_INSTANCE: full_adder
            port map (
                A    => A(i),       -- Connect to the i-th bit of input A
                B    => B(i),       -- Connect to the i-th bit of input B
                Cin  => C(i),       -- The carry-in for this bit
                S    => S(i),       -- The sum output for this bit
                Cout => C(i+1)      -- The carry-out for this bit
            );
    end generate ADDER_CHAIN;

    -- The final carry-out of the chain is the adder's carry-out pin
    Cout <= C(4);

end architecture structural;

Physically (RTL wise), this is the exact same as copy-pasting the 4 adders manually. So, this approach is much cleaner and more organized.

When & Which

When & Which?

Comparison

Feature for Loop while Loop for-generate Statement
Execution Sequential Sequential Concurrent
Usage Location Inside a process Inside a process Outside a process
Iteration Fixed number of times Repeats while a condition is true Creates N physical copies
Purpose Algorithmic tasks Searching or Polling Hardware Replication

Quick Guide

Remember, all loops have the same syntax rules, so you just need to remember the structure. Good luck! :+1: