Como usar uma função impura em VHDL
Uma função impura pode ler ou escrever qualquer sinal dentro de seu escopo, inclusive aqueles que não estão na lista de parâmetros. Dizemos que a função tem efeitos colaterais .
O que queremos dizer com efeitos colaterais é que não é garantido que a função retornará o mesmo valor toda vez que for chamada com os mesmos parâmetros. Se a função pode ler sinais que não estão na lista de parâmetros, o valor de retorno também pode depender desses parâmetros de sombra. Além disso, a função pode estar alterando sinais externos que não são atribuídos a partir de seu valor de retorno.
Esta postagem do blog faz parte da série de tutoriais básicos de VHDL.
Embora possamos declarar funções impuras em qualquer lugar em que possamos declarar uma função normal e pura, só faz sentido usá-las dentro de processos. Quando declarado na arquitetura onde normalmente declaramos nossos sinais, nenhum dos sinais estará em seu escopo em tempo de compilação. Assim, uma função impura não pode fazer nada mais do que uma função pura pode quando declarada na arquitetura ou dentro de um pacote.
A motivação para o uso de funções impuras é principalmente a organização do código. Poderíamos manipular qualquer sinal com uma função pura simplesmente adicionando-o à lista de parâmetros, mas se a lista de parâmetros se tornar muito longa, ofuscará em vez de simplificar.
A sintaxe para declarar uma função impura é simplesmente escrever
impure function
em vez de function
ao declará-lo. Consulte o tutorial de função para obter a sintaxe de uma função genérica. Exercício
No tutorial anterior, simplificamos nosso código de máquina de estado finito (FSM) usando uma função para calcular valores de atraso de tempo. Fornecemos os parâmetros Minutes e Seconds para especificar por quanto tempo queríamos atrasar cada mudança de estado.
Se o
CounterVal
função retornou true
, o tempo havia expirado e era hora de ir para o próximo estado FSM. No mesmo processo, também tivemos que redefinir o Counter
sinal, caso contrário, a função não funcionaria no próximo estado. O cronômetro já teria expirado. O
Counter
sinal sempre seria definido como 0
quando a função retornou true. Não seria melhor se isso acontecesse no CounterVal
função em vez de vários lugares no código de máquina de estado? Neste tutorial em vídeo vamos melhorar o código FSM do tutorial anterior usando uma função impura:
O código final para a função impura 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;
O código final do módulo do semáforo :
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;
A forma de onda depois de inserirmos o
run 5 min
comando no console ModelSim:Análise
Como podemos ver na forma de onda, a saída do módulo permanece inalterada depois que adicionamos a função impura. Nós não mudamos a lógica, apenas o código.
A avaliação do
Counter
sinal foi movido do código FSM para a nova função impura CounterExpired
. O Counter <= 0;
linha para limpar o Counter
sinal também foi movido para a função impura. O resultado é um código FSM mais legível que pode ser mantido mais facilmente. Isso é subjetivo, mas para mim
CounterExpired(Seconds => 5)
é mais agradável aos olhos do que Counter = CounterVal(Seconds => 5)
. Até onde você deve ir com o uso de funções impuras depende inteiramente de você e de quem paga por seus serviços. Algumas pessoas acham que devem ser usadas com cautela porque pode ser mais difícil ver todas as causas e efeitos de um algoritmo oculto em um subprograma. Outros, como eu, acham que, desde que você deixe suas intenções claras, quanto mais fácil de ler o código, na verdade, ele fica menos propenso a erros.
Por esse motivo, é mais provável que você encontre funções impuras no código do testbench do que nos módulos de produção. Os testbenches são normalmente mais complexos do que o módulo que estão testando, e o requisito de correção do código é menos rigoroso do que para o código RTL.
Retirada
- Funções impuras podem ler ou conduzir sinais que não estão em sua lista de parâmetros
- Faz sentido declarar funções impuras dentro de um processo
Ir para o próximo tutorial »
VHDL
- Como usamos o molibdênio?
- Como criar uma lista de strings em VHDL
- Como parar a simulação em um testbench VHDL
- Como criar um controlador PWM em VHDL
- Como gerar números aleatórios em VHDL
- Como usar um procedimento em um processo em VHDL
- Como usar uma função em VHDL
- Função realloc() na Biblioteca C:Como usar? Sintaxe e Exemplo
- Função free() na biblioteca C:Como usar? Aprenda com o Exemplo
- Como usar um moedor de corte