Functions are subprograms in VHDL which can be used for implementing frequently used algorithms. A function takes zero or more input values, and it always returns a value. In addition to the return value, what sets a function apart from a procedure, is that it cannot contain Wait-statements. This means that functions always consume zero simulation time.

If you are familiar with functions or methods from other programming languages, VHDL functions should be easy to grasp. In VHDL we cannot omit the return value or return void, a function always has to return something and the return value has to be assigned to something.

This blog post is part of the Basic VHDL Tutorials series.

In VHDL, there are two types of functions, pure and impure functions. That a function is pure means that it will not be allowed to modify or read any external signal. We can be certain that when we call a pure function with certain arguments, it will always return the same value. We say that the function doesn’t have any side effects.

The syntax for declaring a function in VHDL is:

[pure|impure] function <function_name> (
        <parameter1_name> : <parameter1_type> := <default_value>;
        <parameter2_name> : <parameter2_type> := <default_value>;
                                    ... ) return <return_type> is
    <constant_or_variable_declaration>
begin
    <code_performed_by_the_function>
    return <value>
end function;

The pure/impure keyword is optional, although it will default to pure if the keyword is omitted. All parameters are treated as constants inside of the function. Thus, they cannot be changed. The default values are optional, and the function must always terminate at a return statement.

Functions have their own declarative region between the is and begin keywords. Constants, signals, or variables declared here are valid only within the function itself, and they will not retain their values through subsequent calls to the function.

Exercise

In this tutorial, we are going to focus on the pure function. Impure functions will be covered in a later tutorial in this series.

In the previous tutorial, we created a traffic lights controller module using a finite-state machine (FSM). We copy-pasted many of the lines containing timer calculations from one state to another, only changing one constant slightly.

Find out how you can simplify the state machine code by using a function:

The final code for the function testbench:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
entity T21_FunctionTb is
end entity;
 
architecture sim of T21_FunctionTb 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.T21_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 T21_TrafficLights is
generic(ClockFrequencyHz : natural);
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 T21_TrafficLights is
 
    -- Enumerated type declaration and state signal declaration
    type t_State is (NorthNext, StartNorth, North, StopNorth,
                        WestNext, StartWest, West, StopWest);
    signal State : t_State;
 
    -- 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;
 
    -- Counter for counting clock periods, 1 minute max
    signal Counter : integer range 0 to CounterVal(Minutes => 1) +1;
 
begin
 
    process(Clk) is
    begin
 
        if rising_edge(Clk) then
            if nRst = '0' then
                -- Reset values
                NorthRed    <= '1';
                NorthYellow <= '0';
                NorthGreen  <= '0';
                WestRed     <= '1';
                WestYellow  <= '0';
                WestGreen   <= '0';
                State       <= NorthNext;
                Counter     <= 0;
 
            else
                -- Default values
                NorthRed    <= '0';
                NorthYellow <= '0';
                NorthGreen  <= '0';
                WestRed     <= '0';
                WestYellow  <= '0';
                WestGreen   <= '0';
 
                Counter <= Counter + 1;
 
                case State is
 
                    -- Red light in all directions
                    when NorthNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        -- If 5 seconds have passed
                        if Counter = CounterVal(Seconds => 5) then
                            Counter <= 0;
                            State   <= StartNorth;
                        end if;
 
                    -- Yellow light in north/south directions
                    when StartNorth =>
                        NorthRed    <= '1';
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        -- If 5 seconds have passed
                        if Counter = CounterVal(Seconds => 5) then
                            Counter <= 0;
                            State   <= North;
                        end if;
 
                    -- Green light in north/south directions
                    when North =>
                        NorthGreen <= '1';
                        WestRed    <= '1';
                        -- If 1 minute has passed
                        if Counter = CounterVal(Minutes => 1) then
                            Counter <= 0;
                            State   <= StopNorth;
                        end if;
 
                    -- Red and yellow light in north/south direction
                    when StopNorth =>
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        -- If 5 seconds have passed
                        if Counter = CounterVal(Seconds => 5) then
                            Counter <= 0;
                            State   <= WestNext;
                        end if;
 
                    -- Red light in all directions
                    when WestNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        -- If 5 seconds have passedf
                        if Counter = CounterVal(Seconds => 5) then
                            Counter <= 0;
                            State   <= StartWest;
                        end if;
 
                    -- Yellow light in west/east direction
                    when StartWest =>
                        NorthRed   <= '1';
                        WestRed    <= '1';
                        WestYellow <= '1';
                        -- If 5 seconds have passed
                        if Counter = CounterVal(Seconds => 5) then
                            Counter <= 0;
                            State   <= West;
                        end if;
 
                    -- Green light in west/east direction
                    when West =>
                        NorthRed  <= '1';
                        WestGreen <= '1';
                        -- If 1 minute has passed
                        if Counter = CounterVal(Minutes => 1) then
                            Counter <= 0;
                            State   <= StopWest;
                        end if;
 
                    -- Red and yellow light in west/east direction
                    when StopWest =>
                        NorthRed   <= '1';
                        WestYellow <= '1';
                        -- If 5 seconds have passed
                        if Counter = CounterVal(Seconds => 5) then
                            Counter <= 0;
                            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:

intersection_waveform

The waveform with cursors added at the transitions to and from the StartNorth state:

split_5_seconds_waveforms

Need the Questa/ModelSim project files?

Let me send you a Zip with everything you need to get started in 30 seconds

How does it work?

Tested on Windows and Linux Loading Gif.. How it works

Analysis

We replaced the timer calculations from the previous tutorial if Counter = ClockFrequencyHz * 5 -1 then with a call to the new CounterVal function we created: if Counter = CounterVal(Seconds => 5) then.

We can see from the first waveform screenshot that the module’s function is unchanged. Using functions for repetitive tasks is good design practice. Especially if you can replace calculations with more readable lines containing terms like Minutes and Seconds.

Another advantage of using functions is that we can change the implementation of all the timers at once, instead of doing it line by line. For example, if we had written return TotalSeconds * ClockFrequencyHz; in the CounterVal function, all the timers would have lasted one clock cycle too long. We could then change this to return TotalSeconds * ClockFrequencyHz -1; in the CounterVal function, and all the timers would be fixed at once.

If we examine the last waveform screenshot, we can see why we need to subtract 1 from the timer value that is returned from the CounterVal function. This waveform examines the duration of the StartNorth state, it should last for exactly five seconds. When the State signal changes to StartNorth, the Counter value is 0, and it only changes after the next clock cycle. So, if we had counted up to 500 clock cycles, the StartNorth state would have actually lasted for 501 cycles. With our testbench running at 100 Hz, 500 clock cycles is exactly five seconds.

Takeaway

  • Functions can take zero or more parameters, but they always return a value
  • Functions cannot contain wait statements
  • Pure functions cannot have side effects, while impure functions can.

Go to the next tutorial »

Similar Posts

4 Comments

  1. Hi, You have nice tutorials. I have one question on functions.
    Can we put process in a function. Generally I observed that its only arithmetic operations in the function. Below is my code using a process( ) in function.
    But the synthesizer throws errors

    entity edge_detector is
    port (
      i_clk                       : in  std_logic;
      i_rstb                      : in  std_logic;
      i_input                     : in  std_logic;
      o_pulse                     : out std_logic);
    end edge_detector;
    architecture rtl of edge_detector is
    
    FUNCTION edg_detect (SIGNAL clk,rstb,f_input : std_logic) RETURN std_logic IS
    		variable r0: std_logic;
    		variable r1: std_logic;
    		variable f_pulse: std_logic;
    	BEGIN
    		p_rising_edge_detector : process(clk,rstb)
    		begin
    			if(rstb='0') then
    				r0 := '0';
    				r1 := '0';
    			elsif(rising_edge(clk)) then
    				r0 := f_input;
    				r1 := r0;
    			end if;
    		end process ;
    		
    	 f_pulse <= not r1 and r0;
    	 return f_pulse;
    	 
    	END edg_detect;
    
    begin
    		o_pulse <= edg_detect(i_clk,i_rstb,i_input);
    end rtl;
    
    1. Hello, Ravi. No, you can’t have a process inside of a subprogram like a function. Furthermore, functions can’t contain WAIT statements (rising_edge is a shorthand notation containing a WAIT statement).

      You can use a procedure to create a reusable process, which is what I think you are trying to do. In the listing below I have modified (but not tested) your example code.

      entity edge_detector is
        port (
          i_clk                       : in  std_logic;
          i_rstb                      : in  std_logic;
          i_input                     : in  std_logic;
          o_pulse                     : inout std_logic);
        end edge_detector;
      architecture rtl of edge_detector is
         
        procedure edg_detect (
          signal clk,rstb,f_input : in std_logic;
          signal f_pulse : inout std_logic) IS
          variable r0: std_logic;
          variable r1: std_logic;
        BEGIN
          if(rstb='0') then
              r0 := '0';
              r1 := '0';
          elsif(rising_edge(clk)) then
              r0 := f_input;
              r1 := r0;
          end if;
                
          f_pulse <= not r1 and r0;
        END edg_detect;
         
        begin
          edg_detect(i_clk, i_rstb, i_input, o_pulse);
      end rtl;
      

      You can then call edg_detect multiple times to operate on different signals in your architecture.

      But if you only want to operate on one set of signals, it’s straightforward to just use a regular process.

  2. Just a small note, Jonas. Near the end of the blog post, you have…

    “Functions have their own declarative region between the in and begin keywords.”

    I think that should be “between the *is* and begin keywords”.

    Love the tutorials. The veil is lifting and I’m starting to see the light! 🙂

    1. Thanks, Larry. I’ve fixed the typo now.

      By the way, I plan on running all of these older articles through the new spellchecker that I’m using. I have created the task, but I’ve yet to find the time. 😅

Leave a Reply

Your email address will not be published. Required fields are marked *