Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Industrial programming >> VHDL

Leveraging In‑Process Procedures for Cleaner VHDL FSM Design

In VHDL, a procedure can drive external signals as long as the signal remains in its scope. While a procedure declared in an architecture has no access to external signals—because those signals are not visible at compile time—a procedure defined inside a process can freely read and write any signal the process can see.

Using in‑process procedures is an effective way to declutter repetitive logic in state machines or complex protocols. By encapsulating common actions—such as a timed state transition—within a procedure, you reduce boilerplate code, improve readability, and make the intent of each step crystal clear.

For example, in a traffic‑light controller, renaming operations to ChangeState(ToState, Minutes, Seconds) tells the reader exactly what the code is doing without diving into the underlying variable assignments.

Practical Exercise

In a previous tutorial, we used an impure function to drive a Counter signal and determine state changes. However, we couldn’t move the State assignment into that function because VHDL requires a return value for every function call. Instead, a procedure—devoid of a return value—fits perfectly for this use case.

Below is the complete testbench and module that demonstrate a traffic‑light FSM driven by an in‑process procedure.

Testbench

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity T23_ProcedureInProcessTb is
end entity;

architecture sim of T23_ProcedureInProcessTb is
    constant ClockFrequencyHz : integer := 100; -- 100 Hz
    constant ClockPeriod : time := 1000 ms / ClockFrequencyHz;
    signal Clk         : std_logic := '1';
    signal nRst        : std_logic := '0';
    signal NorthRed    : std_logic;
    signal NorthYellow : std_logic;
    signal NorthGreen  : std_logic;
    signal WestRed     : std_logic;
    signal WestYellow  : std_logic;
    signal WestGreen   : std_logic;
begin
    i_TrafficLights : entity work.T23_TrafficLights(rtl)
        generic map(ClockFrequencyHz => ClockFrequencyHz)
        port map (
            Clk         => Clk,
            nRst        => nRst,
            NorthRed    => NorthRed,
            NorthYellow => NorthYellow,
            NorthGreen  => NorthGreen,
            WestRed     => WestRed,
            WestYellow  => WestYellow,
            WestGreen   => WestGreen);

    Clk <= not Clk after ClockPeriod / 2;

    process is
    begin
        wait until rising_edge(Clk);
        wait until rising_edge(Clk);
        nRst <= '1';
        wait;
    end process;
end architecture;

Traffic‑Light Module

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity T23_TrafficLights is
    generic(ClockFrequencyHz : integer);
    port(
        Clk         : in std_logic;
        nRst        : in std_logic;
        NorthRed    : out std_logic;
        NorthYellow : out std_logic;
        NorthGreen  : out std_logic;
        WestRed     : out std_logic;
        WestYellow  : out std_logic;
        WestGreen   : out std_logic);
end entity;

architecture rtl of T23_TrafficLights is
    type t_State is (NorthNext, StartNorth, North, StopNorth,
                     WestNext, StartWest, West, StopWest);
    signal State   : t_State;
    signal Counter : integer range 0 to ClockFrequencyHz * 60;
begin
    process(Clk) is
        procedure ChangeState(ToState : t_State;
                              Minutes : integer := 0;
                              Seconds : integer := 0) is
            variable TotalSeconds : integer;
            variable ClockCycles  : integer;
        begin
            TotalSeconds := Seconds + Minutes * 60;
            ClockCycles  := TotalSeconds * ClockFrequencyHz - 1;
            if Counter = ClockCycles then
                Counter <= 0;
                State   <= ToState;
            end if;
        end procedure;
    begin
        if rising_edge(Clk) then
            if nRst = '0' then
                State   <= NorthNext;
                Counter <= 0;
                NorthRed    <= '1';
                NorthYellow <= '0';
                NorthGreen  <= '0';
                WestRed     <= '1';
                WestYellow  <= '0';
                WestGreen   <= '0';
            else
                NorthRed    <= '0';
                NorthYellow <= '0';
                NorthGreen  <= '0';
                WestRed     <= '0';
                WestYellow  <= '0';
                WestGreen   <= '0';
                Counter <= Counter + 1;
                case State is
                    when NorthNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        ChangeState(StartNorth, Seconds => 5);
                    when StartNorth =>
                        NorthRed    <= '1';
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        ChangeState(North, Seconds => 5);
                    when North =>
                        NorthGreen <= '1';
                        WestRed    <= '1';
                        ChangeState(StopNorth, Minutes => 1);
                    when StopNorth =>
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        ChangeState(WestNext, Seconds => 5);
                    when WestNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        ChangeState(StartWest, Seconds => 5);
                    when StartWest =>
                        NorthRed   <= '1';
                        WestRed    <= '1';
                        WestYellow <= '1';
                        ChangeState(West, Seconds => 5);
                    when West =>
                        NorthRed  <= '1';
                        WestGreen <= '1';
                        ChangeState(StopWest, Minutes => 1);
                    when StopWest =>
                        NorthRed   <= '1';
                        WestYellow <= '1';
                        ChangeState(NorthNext, Seconds => 5);
                end case;
            end if;
        end if;
    end process;
end architecture;

Waveform

The simulation remains identical to the original FSM—only the code has become more maintainable and easier to understand.

Analysis

By moving the timing and state‑change logic into a single procedure, we preserved the module’s behavior while dramatically improving readability. The procedure ensures consistent implementation wherever it’s called.

Key Takeaways

VHDL

  1. Using Impure Functions in VHDL: Enhancing FSM Readability and Maintainability
  2. Mastering VHDL Functions: A Practical Guide to Efficient Design
  3. Using Procedures in VHDL: Simplify Your Design with Reusable Code
  4. Building a Clock‑Triggered Process in VHDL: A Practical Guide
  5. Mastering the Case-When Statement in VHDL: Efficient Multiplexer Design
  6. Mastering Concurrent Statements in VHDL: A Practical Guide
  7. Mastering Signed and Unsigned Types in VHDL: A Practical Guide
  8. Mastering VHDL’s std_logic: States, Resolution, and Waveform Analysis
  9. Mastering While Loops in VHDL: Dynamic Iteration Control
  10. Mastering For‑Loops in VHDL: A Practical Guide