Uso de funciones impuras en VHDL: Mejorando legibilidad y mantenibilidad de FSM
En VHDL, una función impura puede acceder o modificar cualquier señal dentro de su ámbito léxico, incluyendo señales que no están listadas como parámetros. Esta capacidad da a la función efectos secundarios —su valor de retorno puede variar incluso cuando se suministran los mismos argumentos, y puede alterar señales externas que no son devueltas directamente.
Mientras que las funciones puras se limitan a comportamiento determinista, las funciones impuras son invaluables para limpiar lógica compleja de máquinas de estado al mover tareas repetitivas o con efectos secundarios a un único subprograma bien nombrado.
Nota: Las funciones impuras se declaran mejor dentro de un proceso. Declararlas a nivel de arquitectura o paquete no ofrece ventaja, ya que el ámbito de la función se limitaría a las señales que ya están en ámbito en tiempo de compilación.
Cuándo y por qué usar funciones impuras
Escenarios típicos incluyen:
- Resetear un contador cuando expira un temporizador.
- Comprobar si una cuenta regresiva ha terminado sin duplicar la lógica de comparación del contador.
- Encapsular manipulación de señales que de otro modo ensuciaría la FSM principal.
Usar una función impura mantiene la lógica principal de la máquina de estados legible mientras preserva el comportamiento con efectos secundarios requerido por el diseño.
Sintaxis
Para declarar una función impura, simplemente anteponga la palabra clave impure antes de function:
impure function CounterExpired(Minutes : integer := 0; Seconds : integer := 0) return boolean;
Consulte el tutorial de funciones para detalles completos de sintaxis.
Ejercicio: Refactorización de FSM con función impura
Previamente calculábamos retrasos de tiempo usando una función pura CounterVal y reiniciábamos manualmente el contador cuando el temporizador expiraba. La siguiente función 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
-- Frecuencia de reloj baja para acelerar simulación
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
-- Instanciación 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
);
-- Generación de reloj
Clk <= not Clk after ClockPeriod / 2;
-- Secuencia 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
-- Función pura para calcular ciclos de reloj para un tiempo 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;
-- Definición de estados FSM
type t_State is (NorthNext, StartNorth, North, StopNorth, WestNext, StartWest, West, StopWest);
signal State : t_State;
-- Contador para períodos de reloj, hasta un minuto
signal Counter : integer range 0 to ClockFrequencyHz * 60;
begin
process(Clk) is
-- Función impura que lee y conduce 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 y señales
State <= NorthNext;
Counter <= 0;
NorthRed <= '1';
NorthYellow <= '0';
NorthGreen <= '0';
WestRed <= '1';
WestYellow <= '0';
WestGreen <= '0';
else
-- Salidas por defecto
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;
Análisis de forma de onda
Después de emitir el comando run 5 min en ModelSim, la simulación muestra comportamiento idéntico a la FSM original —solo el código se ha vuelto más limpio. La lógica de reset del contador ahora está encapsulada dentro de CounterExpired, reduciendo duplicación y errores potenciales.
Análisis
La función impura transfiere la comparación y reset del contador desde múltiples estados FSM a un solo subprograma. Este cambio mejora la legibilidad: CounterExpired(Seconds => 5) transmite claramente la intención, mientras que Counter = CounterVal(Seconds => 5) es más verboso.
Aunque las funciones impuras pueden ocultar efectos secundarios si se abusan, mantenerlas locales a un proceso asegura que el código circundante permanezca determinista y fácil de auditar. En la práctica, se emplean más frecuentemente en testbenches donde la corrección es importante pero el riesgo de bugs sutiles es menor que en RTL sintetizable.
Conclusiones clave
- Las funciones impuras pueden leer o conducir señales no listadas como parámetros.
- Declárelas dentro de un proceso para aprovechar el ámbito local de señales.
- Úselas para encapsular lógica repetitiva con efectos secundarios, mejorando claridad y reduciendo duplicación.
Proceda al siguiente tutorial para explorar técnicas avanzadas de VHDL.
VHDL
- Dominando las Técnicas de Afiladora de Herramientas: Guía Práctica
- Cómo el molibdeno moldea la industria moderna: usos, beneficios y dinámica de suministro
- Uso de realloc() en C: Sintaxis, mejores prácticas y ejemplo
- Dominando la función free() en C: Guía práctica y ejemplo
- Creación de listas de cadenas en VHDL: Buenas prácticas y ejemplo
- Finalizar elegantemente una simulación de testbench VHDL
- Implementación de un controlador PWM en VHDL: Diseño, simulación y demo en FPGA
- Generación de números aleatorios en VHDL: De uniforme a OSVVM
- Aprovechando procedimientos en-proceso para diseños FSM más limpios en VHDL
- Dominando funciones VHDL: Guía práctica para diseño eficiente
- Diseño de máquina de estados finitos en VHDL: Ejemplo práctico de semáforo