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:
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.
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
- 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
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”?
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:
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?
Thanks, Rafael. You are entirely correct. I’ve changed the erroneous text to “impure function”. ?