An impure function can read or write any signal within its scope, also those that are not on the parameter list. We say that the function has side effects.
What we mean by “side effects” is that it is not guaranteed that the function will return the same value every time it is called with the same parameters. If the function can read signals that are not on the parameter list, the return value may depend on these shadow parameters as well. Also, the function may be altering external signals which are not assigned from its return value.
This blog post is part of the Basic VHDL Tutorials series.
Although we can declare impure functions anywhere we can declare a normal, pure function, it only makes sense to use them within processes. When declared in the architecture where we normally declare our signals, none of the signals will be in its scope at compile time. Thus, an impure function cannot do anything more than a pure function can when declared in the architecture or within a package.
The motivation for using impure functions is chiefly decluttering of the code. We could manipulate any signal with a pure function simply by adding it to the parameter list, but if the parameter list becomes too long, it would obfuscate rather than simplify.
The syntax for declaring an impure function is simply writing impure function
instead of function
when declaring it. Refer to the function tutorial for the syntax of a generic function.
Exercise
In the previous tutorial, we simplified our finite-state machine (FSM) code by using a function for calculating time delay values. We provided the Minutes and Seconds parameters to specify for how long we wanted to delay each state change.
If the CounterVal
function returned true
, the time had expired and it was time to go to the next FSM state. In the same process, we also had to reset the Counter
signal, otherwise, the function wouldn’t work in the next state. The timer would already be expired.
The Counter
signal would always be set to 0
when the function returned true. Wouldn’t it be better if this happened in the CounterVal
function instead of multiple places in the state machine code?
In this video tutorial we will improve the FSM code from the previous tutorial using an impure function:
The final code for the impure function testbench:
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
-- We are using a low clock frequency to speed up the 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
-- The Device Under Test (DUT)
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);
-- Process for generating clock
Clk <= not Clk after ClockPeriod / 2;
-- Testbench sequence
process is
begin
wait until rising_edge(Clk);
wait until rising_edge(Clk);
-- Take the DUT out of reset
nRst <= '1';
wait;
end process;
end architecture;
The final code for the traffic lights module:
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
-- Calculate the number of clock cycles in minutes/seconds
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;
-- Enumerated type declaration and state signal declaration
type t_State is (NorthNext, StartNorth, North, StopNorth,
WestNext, StartWest, West, StopWest);
signal State : t_State;
-- Counter for counting clock periods, 1 minute max
signal Counter : integer range 0 to ClockFrequencyHz * 60;
begin
process(Clk) is
-- This impure function reads and drives the Counter signal
-- which is not on the parameter list.
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 values
State <= NorthNext;
Counter <= 0;
NorthRed <= '1';
NorthYellow <= '0';
NorthGreen <= '0';
WestRed <= '1';
WestYellow <= '0';
WestGreen <= '0';
else
-- Default values
NorthRed <= '0';
NorthYellow <= '0';
NorthGreen <= '0';
WestRed <= '0';
WestYellow <= '0';
WestGreen <= '0';
Counter <= Counter + 1;
case State is
-- Red in all directions
when NorthNext =>
NorthRed <= '1';
WestRed <= '1';
-- If 5 seconds have passed
if CounterExpired(Seconds => 5) then
State <= StartNorth;
end if;
-- Red and yellow in north/south direction
when StartNorth =>
NorthRed <= '1';
NorthYellow <= '1';
WestRed <= '1';
-- If 5 seconds have passed
if CounterExpired(Seconds => 5) then
State <= North;
end if;
-- Green in north/south direction
when North =>
NorthGreen <= '1';
WestRed <= '1';
-- If 1 minute has passed
if CounterExpired(Minutes => 1) then
State <= StopNorth;
end if;
-- Yellow in north/south direction
when StopNorth =>
NorthYellow <= '1';
WestRed <= '1';
-- If 5 seconds have passed
if CounterExpired(Seconds => 5) then
State <= WestNext;
end if;
-- Red in all directions
when WestNext =>
NorthRed <= '1';
WestRed <= '1';
-- If 5 seconds have passed
if CounterExpired(Seconds => 5) then
State <= StartWest;
end if;
-- Red and yellow in west/east direction
when StartWest =>
NorthRed <= '1';
WestRed <= '1';
WestYellow <= '1';
-- If 5 seconds have passed
if CounterExpired(Seconds => 5) then
State <= West;
end if;
-- Green in west/east direction
when West =>
NorthRed <= '1';
WestGreen <= '1';
-- If 1 minute has passed
if CounterExpired(Minutes => 1) then
State <= StopWest;
end if;
-- Yellow in west/east direction
when StopWest =>
NorthRed <= '1';
WestYellow <= '1';
-- If 5 seconds have passed
if CounterExpired(Seconds => 5) then
State <= NorthNext;
end if;
end case;
end if;
end if;
end process;
end architecture;
The waveform after we entered the run 5 min
command in the ModelSim console:
Analysis
As we can see from the waveform, the module output remains unchanged after we added the impure function. We haven’t changed the logic at all, only the code.
The evaluation of the Counter
signal has been moved from the FSM code into the new impure function CounterExpired
. The Counter <= 0;
line for clearing the Counter
signal has also been moved into the impure function.
The result is a more readable FSM code which can be more easily maintained. This is subjective, but for me CounterExpired(Seconds => 5)
is easier on the eyes than Counter = CounterVal(Seconds => 5)
.
How far you should go with using impure functions is entirely up to you and whoever pays for your services. Some people feel that they should be used with caution because it can be harder to see through all the causes and effects of an algorithm hidden in a subprogram. Others, like me, feel that as long as you make your intentions clear, the easier to read code actually makes it less error-prone.
For this reason, you are more likely to find impure functions in testbench code than in production modules. Testbenches are typically more complex than the module they are testing, and the requirement for code correctness is less strict than for RTL code.
Get free access to the Basic VHDL Course
Download the course material and get started.
You will receive a Zip with exercises for the 23 video lessons as VHDL files where you fill in the blanks, code answers, and a link to the course.
By submitting, you consent to receive marketing emails from VHDLwhiz (unsubscribe anytime).
Takeaway
- Impure functions can read or drive signals that are not on its parameter list
- It only makes sense to declare impure functions within a process