如何在 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