Usando Funções Impuras em VHDL: Melhorando Legibilidade e Manutenibilidade de FSM
Em VHDL, uma função impura pode acessar ou modificar qualquer sinal dentro de seu escopo léxico, incluindo sinais que não estão listados como parâmetros. Esta capacidade dá à função efeitos colaterais — seu valor de retorno pode variar mesmo quando os mesmos argumentos são fornecidos, e ela pode alterar sinais externos que não são diretamente retornados.
Enquanto funções puras são confinadas a comportamento determinístico, funções impuras são inestimáveis para limpar lógica complexa de máquina de estado movendo tarefas repetitivas ou com efeitos colaterais para um único subprograma bem nomeado.
Nota: Funções impuras são melhor declaradas dentro de um processo. Declará-las no nível de arquitetura ou pacote não oferece vantagem, pois o escopo da função seria limitado aos sinais que já estão no escopo em tempo de compilação.
Quando e Por Que Usar Funções Impuras
Cenários típicos incluem:
- Resetar um contador quando um temporizador expira.
- Verificar se uma contagem regressiva terminou sem duplicar a lógica de comparação do contador.
- Encapsular manipulação de sinal que de outra forma deixaria o FSM principal confuso.
Usar uma função impura mantém a lógica principal da máquina de estado legível enquanto preserva o comportamento com efeitos colaterais exigido pelo design.
Sintaxe
Para declarar uma função impura, simplesmente anteponha a palavra-chave impure antes de function:
impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean;
Consulte o tutorial de funções para detalhes completos de sintaxe.
Exercício: Refatoração de FSM com Função Impura
Anteriormente, calculávamos atrasos de tempo usando uma função pura CounterVal e resetávamos manualmente o contador sempre que o temporizador expirava. A seguinte função impura consolida esta lógica:
Testbench (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 -- Frequência de clock baixa para acelerar simulação 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 -- Instanciação do 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); -- Geração de clock Clk <= not Clk after ClockPeriod / 2; -- Sequência do testbench process is begin wait until rising_edge(Clk); wait until rising_edge(Clk); -- Liberar reset nRst <= '1'; wait; end process; end architecture;
Módulo Semáforos (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; -- Reset negativo 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 -- Função pura para calcular ciclos de clock para um tempo dado 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; -- Definição de estado do FSM type t_State is (NorthNext, StartNorth, North, StopNorth, WestNext, StartWest, West, StopWest); signal State : t_State; -- Contador para períodos de clock, até um minuto signal Counter : integer range 0 to ClockFrequencyHz * 60; begin process(Clk) is -- Função impura que lê e conduz 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 estado e sinais State <= NorthNext; Counter <= 0; NorthRed <= '1'; NorthYellow <= '0'; NorthGreen <= '0'; WestRed <= '1'; WestYellow <= '0'; WestGreen <= '0'; else -- Saídas padrão 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; Visão da Forma de Onda
Após emitir o comando run 5 min no ModelSim, a simulação mostra comportamento idêntico ao FSM original — apenas o código se tornou mais limpo. A lógica de reset do contador agora está encapsulada dentro de CounterExpired, reduzindo duplicação e potenciais erros.
Análise
A função impura transfere a comparação e reset do contador de múltiplos estados do FSM para um único subprograma. Esta mudança melhora a legibilidade: CounterExpired(Seconds => 5) transmite claramente a intenção, enquanto Counter = CounterVal(Seconds => 5) é mais verboso.
Embora funções impuras possam obscurecer efeitos colaterais se usadas em excesso, mantê-las locais a um processo garante que o código circundante permaneça determinístico e fácil de auditar. Na prática, elas são mais frequentemente empregadas em testbenches onde a correção é importante, mas o risco de bugs sutis é menor que em RTL sintetizável.
Principais Conclusões
- Funções impuras podem ler ou conduzir sinais não listados como parâmetros.
- Declare-as dentro de um processo para aproveitar o escopo local de sinais.
- Use-as para encapsular lógica repetitiva com efeitos colaterais, melhorando clareza do código e reduzindo duplicação.
Prossiga para o próximo tutorial para explorar técnicas avançadas de VHDL.
VHDL
- Como o Molibdênio Molda a Indústria Moderna: Usos, Benefícios e Dinâmica de Fornecimento
- Criando Listas de Strings em VHDL: Boas Práticas e Exemplo
- Finalizando Graciosamente uma Simulação de Testbench VHDL
- Implementando um Controlador PWM em VHDL: Projeto, Simulação e Demo em FPGA
- Gerando Números Aleatórios em VHDL: De Uniforme a OSVVM
- Aproveitando Procedimentos In-Process para Design de FSM em VHDL Mais Limpo
- Dominando Funções VHDL: Guia Prático para Design Eficiente
- Usando realloc() em C: Sintaxe, Boas Práticas e Exemplo
- Dominando a Função free() em C: Guia Prático e Exemplo
- Dominando Técnicas de Retificadora de Ferramentas e Cortadores: Guia Prático