Освоение функций VHDL: практическое руководство по эффективному дизайну
Функции — это подпрограммы в VHDL, которые можно использовать для реализации часто используемых алгоритмов. Функция принимает ноль или более входных значений и всегда возвращает значение. Помимо возвращаемого значения, то, что отличает функцию от процедуры, — это то, что она не может содержать операторы Wait. Это означает, что функции всегда потребляют нулевое симуляционное время.
Если вы знакомы с функциями или методами из других языков программирования, VHDL-функции должны быть легко понятны. В VHDL мы не можем опускать возвращаемое значение или возвращать void — функция всегда должна что-то возвращать, и возвращаемое значение должно быть присвоено чему-то.
Эта статья является частью серии базовых уроков VHDL.
В VHDL существует два типа функций: чистые (pure) и нечистые (impure). То, что функция чиста, означает, что ей не разрешается модифицировать или читать какие-либо внешние сигналы. Мы можем быть уверены, что когда мы вызываем чистую функцию с определенными аргументами, она всегда вернет одно и то же значение. Мы говорим, что у функции нет побочных эффектов.
Синтаксис объявления функции в 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;
Волновая форма после ввода команды 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, и все таймеры были бы исправлены сразу.
Если мы рассмотрим последний скриншот волновой формы, мы увидим, почему нам нужно вычитать 1 из значения таймера, возвращаемого функцией CounterVal. Эта волновая форма исследует длительность состояния StartNorth, оно должно длиться ровно пять секунд. Когда сигнал State меняется на StartNorth, значение Counter равно 0, и оно меняется только после следующего тактового цикла. Так что если бы мы считали до 500 тактовых циклов, состояние StartNorth на самом деле длилось бы 501 цикл. С нашим тестбенчем, работающим на 100 Гц, 500 тактовых циклов — это ровно пять секунд.

Выводы
- Функции могут принимать ноль или более параметров, но всегда возвращают значение
- Функции не могут содержать операторы
wait - Чистые функции не могут иметь побочных эффектов, в то время как нечистые могут.
Переходите к следующему уроку »
VHDL
- Как молибден формирует современную промышленность: применение, преимущества и динамика поставок
- Создание списков строк в VHDL: лучшие практики и пример
- Корректное завершение симуляции VHDL-тестбенча
- Реализация PWM-контроллера в VHDL: проектирование, симуляция и FPGA-демо
- Генерация случайных чисел в VHDL: от Uniform к OSVVM
- Использование внутрипроцессных процедур для более чистого проектирования FSM на VHDL
- Использование нечистых функций в VHDL: повышение читаемости и поддерживаемости FSM
- Использование realloc() в C: синтаксис, лучшие практики и пример
- Освоение функции free() в C: практическое руководство и пример
- Освоение техник шлифовального станка для режущих инструментов: практическое руководство