如何在 VHDL 中使用不纯函数
不纯函数可以读取或写入其范围内的任何信号,包括那些不在参数列表中的信号。我们说这个函数有副作用 .
我们所说的副作用的意思是,不能保证每次使用相同的参数调用函数时都会返回相同的值。如果函数可以读取不在参数列表中的信号,则返回值也可能取决于这些影子参数。此外,该函数可能正在更改未从其返回值分配的外部信号。
这篇博文是基本 VHDL 教程系列的一部分。
尽管我们可以在任何可以声明普通纯函数的地方声明不纯函数,但只有在进程中使用它们才有意义。当在我们通常声明信号的架构中声明时,在编译时没有任何信号在其范围内。因此,在架构或包中声明时,不纯函数只能做纯函数所能做的事情。
使用不纯函数的动机主要是整理代码。我们可以简单地通过将任何信号添加到参数列表中来使用纯函数来操作任何信号,但是如果参数列表变得太长,它会混淆而不是简化。
声明不纯函数的语法就是简单地写 impure function
而不是 function
声明时。泛型函数的语法参考函数教程。
运动
在上一教程中,我们通过使用计算时间延迟值的函数简化了有限状态机 (FSM) 代码。我们提供了 Minutes 和 Seconds 参数来指定我们希望将每次状态更改延迟多长时间。
如果 CounterVal
函数返回 true
,时间已过,是时候进入下一个 FSM 状态了。在同一过程中,我们还必须重置 Counter
信号,否则,该函数将无法在下一个状态下工作。计时器已经过期了。
Counter
信号将始终设置为 0
当函数返回真时。如果这发生在 CounterVal
中不是更好吗 函数而不是状态机代码中的多个位置?
在本视频教程中,我们将使用不纯函数改进上一教程中的 FSM 代码:
不纯函数 testbench 的最终代码 :
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity T22_ImpureFunctionTb is end entity; architecture sim of T22_ImpureFunctionTb 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.T22_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 T22_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 T22_TrafficLights is -- 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; -- 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 -- This impure function reads and drives the Counter signal -- which is not on the parameter list. impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean is begin if Counter = CounterVal(Minutes, Seconds) then Counter <= 0; return true; else return false; end if; end function; 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'; -- If 5 seconds have passed if CounterExpired(Seconds => 5) then State <= StartNorth; end if; -- Red and yellow in north/south direction when StartNorth => NorthRed <= '1'; NorthYellow <= '1'; WestRed <= '1'; -- If 5 seconds have passed if CounterExpired(Seconds => 5) then State <= North; end if; -- Green in north/south direction when North => NorthGreen <= '1'; WestRed <= '1'; -- If 1 minute has passed if CounterExpired(Minutes => 1) then State <= StopNorth; end if; -- Yellow in north/south direction when StopNorth => NorthYellow <= '1'; WestRed <= '1'; -- If 5 seconds have passed if CounterExpired(Seconds => 5) then State <= WestNext; end if; -- Red in all directions when WestNext => NorthRed <= '1'; WestRed <= '1'; -- If 5 seconds have passed if CounterExpired(Seconds => 5) then State <= StartWest; end if; -- Red and yellow in west/east direction when StartWest => NorthRed <= '1'; WestRed <= '1'; WestYellow <= '1'; -- If 5 seconds have passed if CounterExpired(Seconds => 5) then State <= West; end if; -- Green in west/east direction when West => NorthRed <= '1'; WestGreen <= '1'; -- If 1 minute has passed if CounterExpired(Minutes => 1) then State <= StopWest; end if; -- Yellow in west/east direction when StopWest => NorthRed <= '1'; WestYellow <= '1'; -- If 5 seconds have passed if CounterExpired(Seconds => 5) then State <= NorthNext; end if; end case; end if; end if; end process; end architecture;
我们输入run 5 min
后的波形 ModelSim 控制台中的命令:
分析
从波形可以看出,我们添加了不纯函数后,模块输出保持不变。我们根本没有改变逻辑,只改变了代码。
Counter
的评价 信号已从 FSM 代码移至新的不纯函数 CounterExpired
. Counter <= 0;
用于清除 Counter
的行 信号也被移到了不纯函数中。
结果是更易于维护的更易读的 FSM 代码。这是主观的,但对我来说 CounterExpired(Seconds => 5)
比 Counter = CounterVal(Seconds => 5)
更美观 .
使用不纯函数应该走多远完全取决于您和为您的服务付费的人。有些人认为应该谨慎使用它们,因为很难看清隐藏在子程序中的算法的所有原因和影响。其他人,比如我,觉得只要你的意图明确,更容易阅读的代码实际上更不容易出错。
因此,与生产模块相比,您更有可能在测试平台代码中找到不纯的函数。测试台通常比它们正在测试的模块更复杂,并且对代码正确性的要求没有 RTL 代码那么严格。
外卖
- 不纯函数可以读取或驱动不在其参数列表中的信号
- 只有在进程中声明不纯函数才有意义
转到下一个教程 »
VHDL