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

Using Impure Functions in VHDL: Enhancing FSM Readability and Maintainability

In VHDL, an impure function can access or modify any signal within its lexical scope, including signals that are not listed as parameters. This capability gives the function side effects—its return value may vary even when the same arguments are supplied, and it may alter external signals that are not directly returned.

While pure functions are confined to deterministic behaviour, impure functions are invaluable for cleaning up complex state‑machine logic by moving repetitive or side‑effecting tasks into a single, well‑named subprogram.

Note: Impure functions are best declared inside a process. Declaring them at the architecture or package level offers no advantage, as the function’s scope would be limited to the signals that are already in scope at compile time.

When and Why to Use Impure Functions

Typical scenarios include:

Using an impure function keeps the core state‑machine logic readable while still preserving the side‑effecting behaviour required by the design.

Syntax

To declare an impure function, simply prepend the keyword impure before function:

impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean;

Refer to the function tutorial for full syntax details.

Exercise: FSM Refactoring with an Impure Function

Previously we calculated time delays using a pure function CounterVal and manually reset the counter whenever the timer expired. The following impure function consolidates this logic:

Testbench (T22_ImpureFunctionTb)

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

entity T22_ImpureFunctionTb is
end entity;

architecture sim of T22_ImpureFunctionTb is

    -- Low clock frequency to accelerate simulation
    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

    -- DUT instantiation
    i_TrafficLights : entity work.T22_TrafficLights(rtl)
    generic map(ClockFrequencyHz => ClockFrequencyHz)
    port map (
        Clk         => Clk,
        nRst        => nRst,
        NorthRed    => NorthRed,
        NorthYellow => NorthYellow,
        NorthGreen  => NorthGreen,
        WestRed     => WestRed,
        WestYellow  => WestYellow,
        WestGreen   => WestGreen);

    -- Clock generation
    Clk <= not Clk after ClockPeriod / 2;

    -- Testbench sequence
    process is
    begin
        wait until rising_edge(Clk);
        wait until rising_edge(Clk);

        -- Release reset
        nRst <= '1';

        wait;
    end process;

end architecture;

Traffic Lights Module (T22_TrafficLights)

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

entity T22_TrafficLights is
generic(ClockFrequencyHz : integer);
port(
    Clk         : in std_logic;
    nRst        : in std_logic; -- Negative reset
    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 T22_TrafficLights is

    -- Pure function to compute clock cycles for a given time
    function CounterVal(Minutes : integer := 0;
                        Seconds : integer := 0) return integer is
        variable TotalSeconds : integer;
    begin
        TotalSeconds := Seconds + Minutes * 60;
        return TotalSeconds * ClockFrequencyHz -1;
    end function;

    -- FSM state definition
    type t_State is (NorthNext, StartNorth, North, StopNorth,
                     WestNext, StartWest, West, StopWest);
    signal State : t_State;

    -- Counter for clock periods, up to one minute
    signal Counter : integer range 0 to ClockFrequencyHz * 60;

begin

    process(Clk) is

        -- Impure function that reads and drives Counter
        impure function CounterExpired(Minutes : integer := 0;
                                       Seconds : integer := 0)
                                       return boolean is
        begin
            if Counter = CounterVal(Minutes, Seconds) then
                Counter <= 0;
                return true;
            else
                return false;
            end if;
        end function;

    begin
        if rising_edge(Clk) then
            if nRst = '0' then
                -- Reset state and signals
                State   <= NorthNext;
                Counter <= 0;
                NorthRed    <= '1';
                NorthYellow <= '0';
                NorthGreen  <= '0';
                WestRed     <= '1';
                WestYellow  <= '0';
                WestGreen   <= '0';

            else
                -- Default outputs
                NorthRed    <= '0';
                NorthYellow <= '0';
                NorthGreen  <= '0';
                WestRed     <= '0';
                WestYellow  <= '0';
                WestGreen   <= '0';

                Counter <= Counter + 1;

                case State is

                    when NorthNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        if CounterExpired(Seconds => 5) then
                            State <= StartNorth;
                        end if;

                    when StartNorth =>
                        NorthRed    <= '1';
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        if CounterExpired(Seconds => 5) then
                            State <= North;
                        end if;

                    when North =>
                        NorthGreen <= '1';
                        WestRed    <= '1';
                        if CounterExpired(Minutes => 1) then
                            State <= StopNorth;
                        end if;

                    when StopNorth =>
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        if CounterExpired(Seconds => 5) then
                            State <= WestNext;
                        end if;

                    when WestNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        if CounterExpired(Seconds => 5) then
                            State <= StartWest;
                        end if;

                    when StartWest =>
                        NorthRed   <= '1';
                        WestRed    <= '1';
                        WestYellow <= '1';
                        if CounterExpired(Seconds => 5) then
                            State <= West;
                        end if;

                    when West =>
                        NorthRed  <= '1';
                        WestGreen <= '1';
                        if CounterExpired(Minutes => 1) then
                            State <= StopWest;
                        end if;

                    when StopWest =>
                        NorthRed   <= '1';
                        WestYellow <= '1';
                        if CounterExpired(Seconds => 5) then
                            State <= NorthNext;
                        end if;

                end case;
            end if;
        end if;
    end process;

end architecture;

Waveform Insight

After issuing the run 5 min command in ModelSim, the simulation shows identical behaviour to the original FSM—only the code has become cleaner. The counter reset logic is now encapsulated within CounterExpired, reducing duplication and potential errors.

Using Impure Functions in VHDL: Enhancing FSM Readability and Maintainability

Analysis

The impure function transfers the counter comparison and reset from multiple FSM states into a single subprogram. This change improves readability: CounterExpired(Seconds => 5) clearly conveys intent, whereas Counter = CounterVal(Seconds => 5) is more verbose.

While impure functions can obscure side effects if overused, keeping them local to a process ensures that the surrounding code remains deterministic and easy to audit. In practice, they are more frequently employed in testbenches where correctness is important but the risk of subtle bugs is lower than in synthesizable RTL.

Using Impure Functions in VHDL: Enhancing FSM Readability and Maintainability

Key Takeaways

Proceed to the next tutorial to explore advanced VHDL techniques.

VHDL

  1. Leveraging In‑Process Procedures for Cleaner VHDL FSM Design
  2. Mastering VHDL Functions: A Practical Guide to Efficient Design
  3. Using Procedures in VHDL: Simplify Your Design with Reusable Code
  4. Mastering VHDL Port Map Instantiation: A Practical Guide
  5. Mastering the Case-When Statement in VHDL: Efficient Multiplexer Design
  6. Mastering Signed and Unsigned Types in VHDL: A Practical Guide
  7. Mastering VHDL Wait Statements: Wait On, Wait Until, and Combined Usage
  8. Mastering While Loops in VHDL: Dynamic Iteration Control
  9. Mastering For‑Loops in VHDL: A Practical Guide
  10. Mastering Loop and Exit Constructs in VHDL: A Practical Guide