Programación en VHDL/Organización del código

De Wikilibros, la colección de libros de texto de contenido libre.
← Arquitectura Organización del código Otros conceptos →


En descripciones de sistemas complejos es necesario una organización que permita al diseñador trabajar con mayor comodidad. En el capítulo anterior se vio como era posible agrupar una serie de sentencias mediante módulos. Pero existen otras formas de organizar el código, a través de subprogramas, que harán más legibles dichos sistemas. Por otro lado, estos subprogramas pueden ser agrupados junto con definiciones de tipos, bloques, ... en estructuras, lo que formarían los denominados paquetes, que a su vez con elementos de configuración describirían una librería.

Subprogramas[editar]

Como en otros lenguajes de programación, en VHDL es posible estructurar el código mediante el uso de subprogramas. Realmente, un subprograma es una función o procedimiento que realiza una determinada tarea, aunque existen ciertas diferencias entre ambas.

  • Una función devuelve un valor y un procedimiento devuelve los valores a través de los parámetros que le han sido pasados como argumentos. Por ello, las primeras deberán contener la palabra reservada RETURN, mientras que las segundas no tienen necesidad de disponer dicha sentencia, en caso de tener una sentencia de ese tipo interrumpirá la ejecución del procedimiento.
  • A consecuencia de la anterior, en una función todos sus parámetros son de entrada, por lo que sólo pueden ser leidos dentro de la misma, por el contrario en un procedimiento los parámetros pueden ser de entrada, de salida o de entrada y salida (unidireccionales o bidireccionales).
  • Las funciones se usan en expresiones, sin embargo, los procedimientos se llaman como una sentencia secuencial o concurrente.
  • Los procedimientos pueden tener efectos colaterales al poder cambiar señales externas que han sido pasadas como parámetros, por otro lado las funciones no poseen estos efectos.
  • Las funciones nunca pueden tener una sentencia WAIT, pero los procedimientos sí.

Declaración de funciones y procedimientos[editar]

Las declaraciones de estos elementos pueden realizarse en la parte declarativas de las arquitecturas, bloques, paquetes, etc. A continuación, se muestra la estructura de un procedimiento.

     PROCEDURE nombre[(parámetros)] IS
       [declaraciones]
     BEGIN
       [sentencias]
     END [PROCEDURE] [nombre];

La estructura de las funciones corresponden a las siguientes líneas.

     [PURE | IMPURE]
     FUNCTION nombre[(parámetros)] RETURN tipo IS
       [declaraciones]
     BEGIN
       [sentencias]          -- Debe incluir un RETURN 
     END [FUNCTION] [nombre];

Como ya se explicó, la lista de parámetros es opcional en ambos casos. Estos parámetros se declaran de forma similar a como se hacen los puertos de una entidad.

 <nombre del puerto> : <tipo de puerto> <tipo de objeto>

Dependiendo de la estructura que se utilice, funciones o procedimientos, los parámetros tendrán un significado u otro. En las funciones sólo es posible utilizar el tipo de puerto IN, mientras que en los procedimientos pueden usarse los tipos IN, OUT e INOUT. Además, en las funciones el parámetro puede ser CONSTANT o SIGNAL, por defecto es CONSTANT. Por otro lado, en los procedimientos los parámetros de tipo IN son CONSTANT por defecto y VARIABLE para el resto, aunque también es posible utilizar SIGNAL siempre que se declare explícitamente. No se aconseja utilizar señales como parámetros, por los efectos que pueden tener en la ejecución de un programa.

Las funciones PURE o puras devuelven el mismo valor para unos parámetros de entrada determinados. Mientras que una función es IMPURE o impura si para los mismos valores de entrada se devuelve distinto valor. Estas últimas pueden depender de una variable o señal global. Realmente, estas palabras reservadas hacen de comentario, puesto que una función no se hace impura o pura por indicarlo.

Un ejemplo de una función sería el siguiente.


     FUNCTION es_uno(din : std_logic_vector(31 downto 0)) RETURN std_logic IS
       VARIABLE val : std_logic;
     BEGIN
       IF din = X"00000001" THEN
         val := '1';
       ELSE
         val := '0';
       END IF;
       RETURN val;
     END es_uno;

Por otro lado, un ejemplo de un procedimiento podría pertenecer a las siguientes líneas.


     PROCEDURE es_uno(din : std_logic_vector(31 downto 0)
                      dout : std_logic) IS
     BEGIN
       IF din = X"00000001" THEN
         dout := '1';
       ELSE
         dout := '0';
       END IF;
     END es_uno;

En la asignación a la señal de salida se realiza con el operador :=, puesto que no es una señal, es decir que se trata de un tipo VARIABLE. Si se tratara de una señal se haría con el operador de asignación <=.

Llamadas a subprogramas[editar]

Es muy sencillo realizar una invocación a un subprograma, basta con indicar el nombre de dicho subprograma seguido de los argumentos, los cuales irán entre paréntesis. En VHDL existen varias formas de pasar los parámetros a un subprograma.

  • Asociación implícita: Poniendo los parámetros en el mismo orden en el que se declaran en el subprograma. Por ejemplo, si se dispone del siguiente procedimiento.

     PROCEDURE limite(CONSTANT conj : IN std_logic_vector(3 DOWNTO 0);
                      VARIABLE min, max : INOUT integer) IS
                                 ...
  
     limite(cjt(31 DOWNTO 28), valmin, valmax); -- Llamada al procedimiento

  • Asociación explícita: Los parámetros se colocan en cualquier orden. Un ejemplo de la llamada al procedimiento anterior podría ser la siguiente.

     limite(min=>valmin, max=>valmax, conj=>cjt(31 DOWNTO 28));

  • Parámetros libres: En VHDL es posible dejar parámetros por especificar, de forma que tengan unos valores por defecto, y en el caso de pasar un valor a dichos argumentos puedan tomar el valor especificado. Un ejemplo de este tipo de llamadas sería el siguiente.

     PROCEDURE lee(longuitud : IN integer := 200) IS
     BEGIN
                ....
     END PROCEDURE;

     -- Posibles llamadas
     lee;
     lee(350);

Como ya se indicó al inicio de este capítulo, los subprogramas pueden ser llamados desde un entorno secuencial o concurrente. En caso de ser llamado en un entorno concurrente el procedimiento se ejecutará de forma similar a un bloque PROCESS, por lo que hay que tener alguna sentencia que permita suspender la ejecución, porque podría ejecutarse de forma continua. Este tipo de sentencias podría ser una lista sensible o una sentencia WAIT, como ya se explicó en los bloques PROCESS, como se indicó, no es posible utilizar la lista de sensibilidad junto con una sentencia WAIT. Este tipo de sentencias es posible que detengan un procedimiento para siempre si son utilizadas en entornos secuenciales.

Sobrecarga de operadores[editar]

Como en otros lenguajes de programación, en VHDL también es posible sobracargar los métodos, es decir, tener funciones con el mismo nombre pero con distintos parámetros. Aunque, en este lenguaje hay que tener un poco de cuidado a la hora de declarar los procedimientos, puesto que al ser posible dejar libres algunos argumentos es muy probable encontrar situaciones en las que dos funciones son llamadas de forma idéntica cuando se hace sin parámetros. Por ejemplo, si se dispone de los siguientes procedimientos la llamada a éstos sin parámetros sería la misma, y no habría forma de diferenciar a qué método se refiere.


     PROCEDURE lee(longuitud : IN integer := 20) IS
     BEGIN
                ....
     END PROCEDURE;

     PROCEDURE lee(factor : IN real := 100.0) IS
     BEGIN
                ....
     END PROCEDURE;

     lee;

Paquetes[editar]

Como se comentó al principio, un paquete consta de un conjunto de subprogramas, constantes, declaraciones, etc., con la intención de implementar algún servicio. Así se pueden hacer visibles las interfaces de los subprogramas y ocultar su descripción.

Definición de paquetes[editar]

Los paquetes se separan en dos zonas: declaraciones y cuerpo, aunque esta última puede ser eliminada si no se definen funciones y/o procedimientos. Las siguientes líneas muestra la estructura de un paquete.

     -- Declaración de paquete
     PACKAGE nombre IS
       declaraciones
     END [PACKAGE] [nombre];

     -- Declaración del cuerpo
     PACKAGE BODY nombre IS
       declaraciones
       subprogramas
           ...
     END [PACKAGE BODY] [nombre];

El nombre del paquete debe coincidir en la declaración del paquete y del cuerpo. A continuación, se muestra un ejemplo de un paquete.


     -- Declaración de paquete
     PACKAGE mi_paquete IS
       SUBTYPE dir_type IS std_logic_vector(31 DOWNTO 0);
       SUBTYPE dato_type IS std_logic_vector(15 DOWNTO 0);
       CONSTANT inicio : dir_type;    -- Hay que definirlo en el BODY
       FUNCTION inttodato(valor : integer) RETURN dato_type;
       PROCEDURE datotoint(dato : IN dato_type; valor : OUT integer);
     END mi_paquete;

     -- Declaración del cuerpo
     PACKAGE BODY mi_paquete IS
       CONSTANT inicio : dir_type := X"FFFF0000";

       FUNCTION inttodato(valor : integer) RETURN dato_type IS
         -- Cuerpo de la función
       END inttodato;

       PROCEDURE datotoint(dato : IN dato_type; valor : OUT integer) IS
         -- Cuerpo del procedimiento
       END datotoint;
       
     END PACKAGE BODY mi_paquete;

Para acceder a los tipos creados en un paquete, se debe indicar el nombre del paquete seguido del elemento que se desea utilizar, separados por un punto. Para el ejemplo anterior sería algo parecido a las siguientes líneas.


     VARIABLE dir : work.mi_paquete.dir_type;
     dir := work.mi_paquete.inicio;

Existe otra forma de realizarlo, haciendo visible al paquete. De esta forma no se necesitará el punto ni tampoco indicar el nombre del paquete. Para ello, se debe utilizar la sentencia USE seguido del paquete que se va a utilizar y el método a utilizar, todo ello separado por puntos. El ejemplo anterior se podría realizar de la siguiente forma.


     USE work.mi_paquete.ALL
     VARIABLE dir : dir_type;
     dir := inicio;

Librerías[editar]

Hasta ahora se han visto varios elementos del lenguaje, como pueden ser las entidades, las arquitecturas, los paquetes, etc. Cuando se realiza una descripción en VHDL se utilizan estas unidades, en uno o más ficheros, éstos se denominan ficheros de diseño. Posteriormente, estos ficheros serán compilados para obtener una librería o biblioteca de diseño, de forma que esta biblioteca contiene los elementos que componen el circuito. La biblioteca donde se guardan los resultados de la compilación se denomina work.

Una librería se compone de dos partes bien diferenciadas, dependiendo de las unidades que la formen. Por un lado, están las unidades primarias, que corresponderán a entidades, paquetes y archivos de configuración. Mientras que las unidades secundarias serán arquitecturas y cuerpos de paquetes. Por lo tanto, se puede sacar la conclusión de que cada unidad secundaria deberá estar asociada con una unidad primaria.

Al realizar una compilación se analizarán las unidades que vayan apareciendo en el texto. Por consiguiente, es importante establecer un orden lógico de las distintas unidades, para que de esta forma se puedan cumplir las dependencias existentes entre las mismas. La forma que toma la librería una vez compilada es muy diversa; dependiendo de la herramienta de compilación utilizada así será el resultado obtenido, esto se debe a que en VHDL no existe un estándar para crear bibliotecas.

Para incluir una librería a un diseño basta con utilizar la palabra reservada LIBRARY seguida del nombre de la biblioteca a utilizar. Además, también es posible hacer visibles elementos internos de estas bibliotecas con el uso de la sentencia USE, como se explicó en el apartado anterior. En el caso de querer hacer visible todos los elementos de un paquete se puede utilizar la palabra reservada ALL.


     LIBRARY mis_componentes;
     USE mis_componentes.logic.ALL;

En VHDL hay dos librerías que no hacen falta importarlas. Por un lado está la librería work, que contiene las unidades que se están compilando, y por otro lado, la librería std que contiene los paquetes standard y textio, las cuales contienen definiciones de tipos y funciones para el acceso a ficheros de texto.

Librería ieee[editar]

Una de las bibliotecas más utilizadas en el mundo de la industria es la denominada ieee, la cual contiene algunos tipos y funciones que completan a las que vienen por defecto en el propio lenguaje. Dentro de la librería existe un paquete denominado std_logic_1164, con el cual se pueden trabajar con un sistema de nueve niveles lógicos, como puede ser: valor desconocido, alta impedancia, etc. El siguiente código muestra parte de este paquete.

     PACKAGE std_logic_1164 IS
       TYPE std_ulogic IS( 'U', -- Indefinido
                           'X', -- Desconocido
                           '0', -- 0
                           '1', -- 1
                           'Z', -- Alta impedancia
                           'W', -- Desconocido
                           'L', -- LOW (weak low o 0 débil)
                           'H', -- HIGH (weak high o 1 débil)
                           '-'  -- Desconocido
                          );

     TYPE std_ulogic_vector IS ARRAY(NATURAL RANGE <>) OF std_ulogic;

     FUNCTION resolved(s : std_ulogic_vector) RETURN std_ulogic;

     SUBTYPE std_logic IS resolved std_ulogic;

     TYPE std_logic_vector IS ARRAY (NATURAL RANGE <>) OF std_logic;


← Arquitectura Organización del código Otros conceptos →