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
SendAutorizationHeader(). You would know what those lines did just be looking at the procedure names.
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:
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.
- 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