使用VHDL不纯函数提升FSM可读性与可维护性
在VHDL中,不纯函数可访问或修改其词法作用域内的任何信号,包括未列为参数的信号。此能力赋予函数副作用——即使提供相同参数,返回值也可能不同,且可能改变未直接返回的外部信号。
纯函数限于确定性行为,而不纯函数通过将重复或有副作用的任务移入单个命名良好的子程序,对清理复杂状态机逻辑极具价值。
注意: 不纯函数最好声明在进程内。在架构或包层级声明无优势,因函数作用域将限于编译时已在作用域内的信号。
何时及为何使用不纯函数
典型场景包括:
- 计时器到期时复位计数器。
- 检查倒计时是否结束,避免重复计数器比较逻辑。
- 封装原本会弄乱主FSM的信号操作。
使用不纯函数在保持核心状态机逻辑可读的同时,仍保留设计所需的副作用行为。
语法
声明不纯函数,只需在function前加impure关键字:
impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean;
完整语法详见函数教程。
练习:用不纯函数重构FSM
之前我们用纯函数CounterVal计算时间延迟,手动在计时器到期时复位计数器。以下不纯函数整合了此逻辑:
测试平台 (T22_ImpureFunctionTb)
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 -- Low clock frequency to accelerate 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 -- DUT instantiation 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); -- Clock generation Clk <= not Clk after ClockPeriod / 2; -- Testbench sequence process is begin wait until rising_edge(Clk); wait until rising_edge(Clk); -- Release reset nRst <= '1'; wait; end process; end architecture;
红绿灯模块 (T22_TrafficLights)
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 -- Pure function to compute clock cycles for a given time 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; -- FSM state definition type t_State is (NorthNext, StartNorth, North, StopNorth, WestNext, StartWest, West, StopWest); signal State : t_State; -- Counter for clock periods, up to one minute signal Counter : integer range 0 to ClockFrequencyHz * 60; begin process(Clk) is -- Impure function that reads and drives Counter 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 state and signals State <= NorthNext; Counter <= 0; NorthRed <= '1'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '1'; WestYellow <= '0'; WestGreen <= '0'; else -- Default outputs NorthRed <= '0'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '0'; WestYellow <= '0'; WestGreen <= '0'; Counter <= Counter + 1; case State is when NorthNext => NorthRed <= '1'; WestRed <= '1'; if CounterExpired(Seconds => 5) then State <= StartNorth; end if; when StartNorth => NorthRed <= '1'; NorthYellow <= '1'; WestRed <= '1'; if CounterExpired(Seconds => 5) then State <= North; end if; when North => NorthGreen <= '1'; WestRed <= '1'; if CounterExpired(Minutes => 1) then State <= StopNorth; end if; when StopNorth => NorthYellow <= '1'; WestRed <= '1'; if CounterExpired(Seconds => 5) then State <= WestNext; end if; when WestNext => NorthRed <= '1'; WestRed <= '1'; if CounterExpired(Seconds => 5) then State <= StartWest; end if; when StartWest => NorthRed <= '1'; WestRed <= '1'; WestYellow <= '1'; if CounterExpired(Seconds => 5) then State <= West; end if; when West => NorthRed <= '1'; WestGreen <= '1'; if CounterExpired(Minutes => 1) then State <= StopWest; end if; when StopWest => NorthRed <= '1'; WestYellow <= '1'; if CounterExpired(Seconds => 5) then State <= NorthNext; end if; end case; end if; end if; end process; end architecture;
波形洞察
在ModelSim中发出run 5 min命令后,仿真显示与原始FSM完全相同的行为——仅代码变得更整洁。计数器复位逻辑现封装在CounterExpired中,减少重复和潜在错误。
分析
不纯函数将计数器比较和复位从多个FSM状态转移到单一子程序。此变更提升可读性:CounterExpired(Seconds => 5)清晰表达意图,而Counter = CounterVal(Seconds => 5)更冗长。
虽不纯函数若滥用可能掩盖副作用,但将其局限在进程内确保周围代码保持确定性且易于审计。实践中,它们更常用于测试平台,因正确性重要但微妙bug风险低于可综合RTL。
关键要点
- 不纯函数可读写未列为参数的信号。
- 在进程内声明以利用局部信号作用域。
- 用于封装重复的副作用逻辑,提高代码清晰度并减少重复。
进入下一教程探索高级VHDL技术。
VHDL