掌握VHDL函数:高效设计实战指南
函数是VHDL中的子程序,用于实现常用算法。函数接受零个或多个输入值,且总是返回一个值。除了返回值,函数与过程的区别在于不能包含Wait语句,意味着函数始终消耗零仿真时间。
若你熟悉其他语言的函数或方法,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语句终止。
函数在is和begin间有自己的声明区。此处声明的常量、信号或变量仅在函数内有效,后续调用不保留值。
练习
本教程聚焦纯函数,不纯函数将在系列后续教程讲解。
上一教程用有限状态机(FSM)创建了红绿灯控制器模块。我们从一个状态复制粘贴许多计时器计算行到另一状态,仅微调一个常量。
找出如何用函数简化状态机代码:
函数测试平台最终代码:
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;
在ModelSim控制台输入run 5 min命令后的波形:

在StartNorth状态转入/转出处加光标的波形:

分析
我们用新建CounterVal函数调用替换了上一教程if Counter = ClockFrequencyHz * 5 -1 then的计时器计算:if Counter = CounterVal(Seconds => 5) then。
从第一张波形截图可见模块功能未变。用函数处理重复任务是良好设计实践。特别是能用包含Minutes和Seconds等术语的可读行替换计算。
使用函数另一优势是可一次性修改所有计时器实现,而非逐行修改。例如若CounterVal函数写return TotalSeconds * ClockFrequencyHz;,所有计时器会多一个时钟周期。可在CounterVal函数改为return TotalSeconds * ClockFrequencyHz -1;,一次性修复所有计时器。
观察最后波形截图,可见为何需从CounterVal返回的计时器值减1。此波形考察StartNorth状态持续时间,应恰好5秒。State信号变为StartNorth时Counter值为0,仅在下一时钟周期变化。若计数到500周期,StartNorth实际持续501周期。测试平台100Hz下500周期正好5秒。

要点
- 函数可接受零或多个参数,但总是返回值
- 函数不能包含
wait语句 - 纯函数无副作用,不纯函数可有副作用。
进入下一教程 »
VHDL