如何在 VHDL 中使用函数
函数是 VHDL 中的子程序,可用于实现常用算法。一个函数接受零个或多个输入值,它总是返回一个值。除了返回值之外,函数与过程的不同之处在于它不能包含等待语句。这意味着函数总是消耗零仿真时间。
如果你熟悉其他编程语言的函数或方法,VHDL函数应该很容易掌握。在VHDL中,我们不能省略返回值或返回void,函数总是要返回一些东西,返回值必须分配给一些东西。
这篇博文是基本 VHDL 教程系列的一部分。
在 VHDL 中,有两种类型的函数,纯 和不纯 功能。一个函数是纯的意味着它不会被允许修改或读取任何外部信号。我们可以肯定,当我们调用带有特定参数的纯函数时,它总是会返回相同的值。我们说这个函数没有任何副作用 .
在 VHDL 中声明函数的语法是:
[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;
pure/impure 关键字是可选的,但如果省略该关键字,它将默认为 pure。所有参数都被视为函数内部的常量。因此,它们无法更改。默认值是可选的,并且函数必须始终在 return
处终止 声明。
函数在 in
之间有自己的声明区域 和 begin
关键词。此处声明的常量、信号或变量仅在函数内部有效,不会在后续调用函数时保留其值。
运动
在本教程中,我们将重点介绍纯函数,不纯函数将在本系列后面的教程中介绍。
在上一个教程中,我们使用有限状态机 (FSM) 创建了一个交通灯控制器模块。我们将许多包含计时器计算的行从一种状态复制粘贴到另一种状态,只稍微改变了一个常数。
了解如何使用函数来简化状态机代码:
函数 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;
红绿灯模块的最终代码 :
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;
我们输入run 5 min
后的波形 ModelSim 控制台中的命令:
在与 StartNorth
之间的转换处添加了光标的波形 状态:
分析
我们替换了上一教程 if Counter = ClockFrequencyHz * 5 -1 then
中的计时器计算 调用新的 CounterVal
我们创建的函数:if Counter = CounterVal(Seconds => 5) then
.
从第一个波形截图我们可以看出,模块的功能没有改变。将函数用于重复性任务是良好的设计实践。特别是如果您可以用包含 Minutes
等术语的更易读的行替换计算 和 Seconds
.
使用函数的另一个好处是我们可以一次更改所有计时器的实现,而不是逐行进行。例如,如果我们写了 return TotalSeconds * ClockFrequencyHz;
在 CounterVal
函数,所有的定时器都会持续一个时钟周期太长。然后我们可以将其更改为 return TotalSeconds * ClockFrequencyHz -1;
在 CounterVal
函数,所有的定时器都会一次固定。
如果我们检查最后一个波形截图,我们可以看到为什么我们需要从 CounterVal
返回的计时器值中减去 1 功能。此波形检查 StartNorth
的持续时间 状态,它应该持续整整五秒钟。当 State
信号更改为 StartNorth
, Counter
值为 0,并且仅在下一个时钟周期后更改。因此,如果我们最多计算 500 个时钟周期,则 StartNorth
状态实际上会持续 501 个周期。我们的测试台以 100 Hz 运行,500 个时钟周期正好是 5 秒。
外卖
- 函数可以接受零个或多个参数,但它们总是返回一个值
- 函数不能包含
wait
声明 - 纯函数不会有副作用,而不纯函数可以。
转到下一个教程 »
VHDL