It is possible to drive external signals from a procedure. As long as the signal is within the scope of the procedure, it can be accessed for reading or writing, even if it isn’t listed in the parameter list.

Procedures that are declared in the declarative region of the architecture, cannot drive any external signals. This is simply because there are no signals in its scope at compile time. A procedure declared within a process, on the other hand, will have access to all of the signals that the process can see.

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

Such procedures can be used for decluttering algorithms in processes where the same operations occur several times. We could use a normal procedure where all the inputs and outputs are assigned to local signals when you call it, but that is not the point. By omitting the input and output signals from the procedure call, we must type less, and more importantly, we make the code more readable.

Imagine a process implementing a complex communication protocol. It would be a lot easier to understand the execution flow of the main algorithm if some operations were replaced by procedure calls like RequestToSend() or SendAutorizationHeader(). You would know what those lines did just be looking at the procedure names.

Exercise

In the previous tutorial, we simplified our finite-state machine (FSM) code by using an impure function. We were driving the Counter signal from the impure function, and we used the return value to determine when to change state. But what if we want to move the assignment of the State signal into the function as well, and ignore the return value?

It’s not possible to call a function without assigning the return value to something in VHDL. If we try to do so, ModelSim will produce the compile error: No feasible entries for subprogram “CounterExpired”.

Instead, we can use a procedure for this. A procedure declared within a process can access any signal within the scope of that process. This is similar to the impure function, but since it’s a procedure, there is no return value.

In this video tutorial we will simplify the FSM code by using a procedure declared in a process:

The final code for the Procedure in Process 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
 
    -- 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.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);
 
 
    -- 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 T23_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 T23_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;
 
    -- Counter for counting clock periods, 1 minute max
    signal Counter : integer range 0 to ClockFrequencyHz * 60;
 
begin
 
    process(Clk) is
 
        -- Procedure for changing state after a given time
        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
                -- 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';
                        ChangeState(StartNorth, Seconds => 5);
 
                    -- Red and yellow in north/south direction
                    when StartNorth =>
                        NorthRed    <= '1';
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        ChangeState(North, Seconds => 5);
 
                    -- Green in north/south direction
                    when North =>
                        NorthGreen <= '1';
                        WestRed    <= '1';
                        ChangeState(StopNorth, Minutes => 1);
 
                    -- Yellow in north/south direction
                    when StopNorth =>
                        NorthYellow <= '1';
                        WestRed     <= '1';
                        ChangeState(WestNext, Seconds => 5);
 
                    -- Red in all directions
                    when WestNext =>
                        NorthRed <= '1';
                        WestRed  <= '1';
                        ChangeState(StartWest, Seconds => 5);
 
                    -- Red and yellow in west/east direction
                    when StartWest =>
                        NorthRed   <= '1';
                        WestRed    <= '1';
                        WestYellow <= '1';
                        ChangeState(West, Seconds => 5);
 
                    -- Green in west/east direction
                    when West =>
                        NorthRed  <= '1';
                        WestGreen <= '1';
                        ChangeState(StopWest, Minutes => 1);
 
                    -- Yellow in west/east direction
                    when StopWest =>
                        NorthRed   <= '1';
                        WestYellow <= '1';
                        ChangeState(NorthNext, Seconds => 5);
 
                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

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 haven’t changed the behavior of the module, and we can see that the waveform is unchanged.

Compared to the code from the tutorial where we initially created the traffic lights module, the FSM code is much more readable now. You can easily follow the algorithm that it implements by reading the code. Having the timer and state change logic in a single procedure is beneficial because it ensures that it’s implemented equally everywhere it’s used.

Takeaway

  • A procedure declared in a process can access any signal within the scope of that process
  • Procedures within processes can be used for improving code readability

Similar Posts

4 Comments

  1. Thank you for this blog post! While writing procedure why did you not indicate “in”- “out”s and “signals”-“constant”s, like you did in the procedure video? Is it about “procedure” being in “progress”?

    1. That’s a good observation and a great question!

      Let’s see what the VHDL standard has to say:
      “If no object class is explicitly given, constant is assumed.”

      And a constant always has “in” mode. Therefore, our parameter list is implicitly the same as this:

      procedure ChangeState(
          constant ToState : in t_State;
          constant Minutes : in integer := 0;
          constant Seconds : in integer := 0)
      
  2. Great lesson as always. Just a contribution: I think there is a error in the text where you say “This is similar to the impure process”, you actually mean “impure function”, right?

Leave a Reply

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