La principal, pero no única, aplicación de funciones es ejecutar operaciones que se repiten en el código VHDL. Similar a las funciones usadas en otros lenguajes como 'C'. Sin embargo debido a ésta similitud diseñadores con conocimientos de lenguajes de programación cometen el error de abusar del uso de las funciones como forma natural de jerarquía. Por el contrario, la forma natural de jerarquía en VHDL es el componente compuesto de entidad y arquitectura.
Algunos puntos importantes para refrescar con respecto a las funciones:
- Las funciones retornan un único valor que será asignado a un dato objeto. Funciones NO pueden ser llamadas por sí solas.
- Pueden ser invocadas como expresión de una instrucción concurrente o como una expresión de una
instrucción secuencial (dentro de un proceso), aunque la función en sí SOLO puede tener instrucciones secuenciales.
- Variable declaradas dentro de la función NO retienen el valor entre ejecuciones. Ellas son re-inicializadas cada vez que la función es llamada. Totalmente distinto al comportamiento de las variables en los procesos.
La sintaxis general de una función es la siguiente:
function function_name(lista_parametros)return tipo_retorno is
declaraciones; -- se aconseja variables o constantes únicamente
begin
instrucciones_secuenciales;
return simple_valor;
end [function] [function_name];
Donde:
- "lista_parametros" se refiere a los parámetros que se asociarán a los parámetros con que se invoca a la función. La asociación de los parámetros, cuando se invoca la función, puede ser por posición o por nombre. El único modo permitido para los objetos definidos en la lista de parámetros es el modo 'in', que por defecto se asigna. La única clase permitida es clase signal. Importante: declare los parámetros sin un determinado tamaño, es decir genéricos. De este modo la misma función puede ser invocada con distintos tamaños de vectores por ejemplo.
- 'declaraciones' se refiere a la parte declarativa de la función. Las únicas clases permitidas para ser declaradas son clase variable y constante. Importante: Variables pueden ser declaradas y usadas en las funciones para acumular resultados o mantener valores intermedios. Pero, las variables dentro de la función NO mantienen el valor entre llamados a la función.
- 'instrucciones_secuenciales' se refiere a la parte de la función donde se escribirán las respectivas instrucciones secuenciales que hacen a la función.
- 'return_simple_valor' se refiere a que toda función debe tener por lo menos un return. Pueden haber varios return pero solo uno se ejecutará. Puede que return sea una expresión compleja que calcula directamente el valor a retornar o sea una simple variable que tiene el valor a retornar.
- 'tipo_retorno' se refiere al tipo (type) del dato objeto de retorno. Este valor sera asignado al objeto del lado izquierdo de la instrucción que invoca a la función. Por lo que ambos tipos deben coincidir.
Muuuy Importante: los valores iniciales asignados a las variables durante su declaración SON sintetizables !
Una función puede ser declarada en:
- La parte declarativa de la arquitectura del componente. Esto se aconseja hacerlo cuando la función se usará solo en ese componente.
- La parte declarativa de un proceso. Rara vez usado.
- Puede ser declarada dentro de otra function o procedure.
- En un paquete, En este caso la función queda disponible para todo los componentes del proyecto. Para poder acceder a la función desde cualquier componente recuerde usar:
use work.nombre_del_paquete.all;
Una de las características mas útil de las funciones es la posibilidad de definir los parámetros de entradas como un arreglo sin restricciones, por lo que una función puede ser vista como una familia de funciones, una para cada posible tamaño del/de los parámetros de entrada. Pero para que funciones correctamente se debe tener mucho cuidado en escribir el código de la función en forma genérica sobre todo usando atributos en el dato objeto de entrada para obtener por ejemplo tamaño, rango, etc.
Ejemplo de una función:
library ieee;
use ieee.std_logic_1164.all;
package my_pack is
function vector_matches(va,vb: std_logic_vector) return natural;
end package;
package body my_pack is
function vector_matches(va,vb:std_logic_vector)return natural is
variable va_tmp:std_logic_vector(va'length-1 downto 0):=va;
variable vb_tmp:std_logic_vector(vb'length-1 downto 0):=vb;
begin
assert (va_tmp'length = vb_tmp'length)
report ”the vectors have different size”
end package body;
use ieee.std_logic_1164.all;
package my_pack is
function vector_matches(va,vb: std_logic_vector) return natural;
end package;
package body my_pack is
function vector_matches(va,vb:std_logic_vector)return natural is
variable va_tmp:std_logic_vector(va'length-1 downto 0):=va;
variable vb_tmp:std_logic_vector(vb'length-1 downto 0):=vb;
begin
assert (va_tmp'length = vb_tmp'length)
report ”the vectors have different size”
severity failure;
for i in va_tmp'range loop
if va_tmp(i) = vb_tmp(i) then
cnt := cnt + 1;
end if;
end loop;
return cnt;
end function;end package body;
Básicamente el archivo conteniendo el paquete my_pack (por ejemplo my_pack.vhd) debe ser importado junto con los otros componentes .vhd en el proyecto que se esté ejecutando. La herramienta de síntesis al encontrar 'use work.my_pack.all' importará la(s) función(es) contenidas en el paquete y las reemplazará en el código de acuerdo a su uso. Ejemplo de uso de la función declarada en el paquete my_pack:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_1164.all;
use work.my_pack.all;
...
entity example is
port(...);
end;
architecture beh of example is
begin
...
q_d1d2 <= vector_matches(d1,d2);-- concurrent call
-- parametros por posicion
ftn_ex_proc:process(d3,d4)
begin
q_d3d4 <= vector_matches(vb=>d3, va=>d4);-- sequential call
-- parametros por nombre
end process ftn_ex_proc;
. . .
. . .
end beh;