Programación en VHDL/Arquitectura

← Entidad Arquitectura Organización del código →


Como se ha dicho en el capítulo anterior, la arquitectura es lo que define cómo se comporta un circuito. En el primer capítulo también se mostraron varias arquitecturas en las que se describía un multiplexor. La primera línea de cada una era

     ARCHITECTURE mux_comportamiento OF mux IS
     ARCHITECTURE mux_rtl OF mux IS
     ARCHITECTURE mux_estructural OF mux IS

Los nombres de cada arquitectura son mux_comportamiento, mux_rtl y mux_estructural respectivamente. Todas están asociadas a la entidad mux. El nombre de la arquitectura se usará para indicar qué arquitectura se debe usar en caso que haya varias para una misma entidad.

Después de esta línea pueden aparecer varias instrucciones para indicar la declaración de señales, componentes, funciones, etc.. Estas señales son internas, es decir, a ellas no se puede acceder desde la entidad, por los que los circuitos de nivel superior no podrían acceder a ellas. En un símil con un microprocesador, estas señales podrían ser las líneas que comunican la unidad central con la ALU, a las que no se puede acceder directamente desde el exterior del microprocesador. Obsérvese que en este caso no se indica si son entradas o salidas, puesto que al ser internas pueden ser leídas o escritas sin ningún problema. En esta parte de la arquitectura también pueden aparecer otros elementos, como pueden ser las constantes. Lo siguiente es la palabra clave BEGIN, que da paso a la descripción del circuito, mediante una serie de sentencias. Por lo tanto, la sintaxis de una arquitectura sería.

    ARCHITECTURE nombre OF nombre_entidad IS
      [declaraciones]
    BEGIN
      [sentencias concurrentes]
    END [ARCHITECTURE] [nombre];

Un ejemplo de una arquitectura podría ser la siguiente.


    ARCHITECTURE mux_rtl OF mux IS
      SIGNAL int1, int2, int3 : BIT;
    BEGIN
      int1 <= NOT control;
      int2 <= entrada1 AND int1;
      int3 <= entrada2 AND S;
      salida <= int2 OR int3;
    END mul_rtl;

La descripción puede ser de tres tipos:

  1. Descripción de flujo de datos
  2. Descripción de comportamiento
  3. Descripción estructural

Descripción de flujo de datos editar

A la hora de plantearse crear un programa en VHDL no hay que pensar como si fuera un programa típico para ordenador. No hay que olvidar que en VHDL hay que describir un hardware, algo que no se hace en un programa para ordenador. Un circuito electrónico puede tener muchos elementos que estén ejecutando acciones a la vez, por ejemplo en un circuito puede tener una entrada que se aplique a dos puertas lógicas y de cada una obtener una salida, en este caso tendría dos caminos en los que se ejecutarían acciones (operaciones lógicas (AND,OR,NOT,IF), de unión, de intersección y de complemento) de forma paralela. Esto es lo que se llama concurrencia.

VHDL es un lenguaje concurrente, como consecuencia no se seguirá el orden en que están escritas las instrucciones a la hora de ejecutar el código. De hecho, si hay dos instrucciones, no tiene porqué ejecutarse una antes que otra, pueden ejecutarse a la vez.

Sentencias Concurrentes editar

La instrucción básica de la ejecución concurrente es la asignación entre señales a través del símbolo <=. Para facilitar la asignación de las señales VHDL incluye elementos de alto nivel como son instrucciones condicionales, de selección, etc, que se verán a continuación.

WHEN ... ELSE editar

Sentencia de selección múltiple. En hardware es necesario incluir todas las opciones posibles. En este caso es obligatorio siempre acabar la expresión con un ELSE.

     <señal> <= <asignación1> WHEN <condición1> ELSE
                <asignación2> WHEN <condición2> ELSE
                              ...
                <asignaciónN> WHEN <condiciónN> ELSE
                <asignaciónM>;

Un posible ejemplo de este tipo de sentencias podría ser la siguiente.


     s <= "00" WHEN a = b ELSE
          "01" WHEN a > b ELSE
          "11";

Siempre es obligatorio asignar algo, aunque es posible no realizar acción alguna, para ello se utiliza la palabra reservada UNAFFECTED. De esta forma se asignará el mismo valor que tenía la señal.

     s1 <= d1 WHEN control = '1' ELSE UNAFFECTED;
     s2 <= d2 WHEN control = '1' ELSE s2;

Las dos sentencias anteriores parecen iguales, pero en la segunda se produce una transacción, aspecto que en la primera no sucede.

WITH ... SELECT ... WHEN editar

Es similar a las sentencias CASE o SWITCH de C. La asignación se hace según el contenido de un objeto o resultado de cierta expresión.

     WITH <señal1> SELECT
       <señal2> <= <asignación1> WHEN <estado_señal1>, 
                   <asignación2> WHEN <estado_señal2>,
                                 ...
                   <asignaciónN> WHEN OTHERS;

Un ejemplo de esta sentencia es la siguiente.


     WITH estado SELECT
       semaforo <= "rojo"     WHEN "01",
                   "verde"    WHEN "10",
                   "amarillo" WHEN "11",
                   "roto"     WHEN OTHERS;

La cláusula WHEN OTHERS especifica todos los demás valores que no han sido contemplados. También es posible utilizar la opción que se contempló en el caso anterior (UNAFFECTED).

BLOCK editar

En ocasiones interesa agrupar un conjunto de sentencias en bloques. Estos bloques permiten dividir el sistema en módulos, estos módulos pueden estar compuestos de otros módulos. La estructura general es la siguiente.

     block_id: BLOCK(expresión de guardia)
       declaraciones
     BEGIN
       sentencias concurrentes
     END BLOCK block_id;

El nombre del bloque es opcional (block_id), al igual que la expresión de guardia. Un ejemplo de esto podría ser el siguiente.


     latch: BLOCK(clk='1')
     BEGIN
       q <= GUARDED d;
     END BLOCK latch;

Descripción de comportamiento editar

Como la programación concurrente no siempre es la mejor forma de describir ideas, VHDL incorpora la programación serie, la cual se define en bloques indicados con la sentencia PROCESS. En un mismo diseño puede haber varios bloques de este tipo, cada uno de estos bloques corresponderá a una instrucción concurrente. Es decir, internamente la ejecución de las instrucciones de los PROCESS es serie, pero entre los bloques es concurrente.

A continuación se verán la estructuras más comunes de la ejecución serie y sus características.

PROCESS editar

Un PROCESS, como se ha dicho antes, es una sentencia concurrente en el sentido de que todos los PROCESS y todas las demás sentencias concurrentes se ejecutarán sin un orden establecido. No obstante las sentencias que hay dentro del PROCESS se ejecutan de forma secuencial.

Por lo tanto se puede decir que una estructura secuencial va en el interior de un PROCESS.

La estructura genérica de esta sentencia es:

     PROCESS [lista de sensibilidad]
       [declaración de variables]
     BEGIN
       [sentencias secuenciales]
     END PROCESS;

La lista de sensibilidad es una serie de señales que, al cambiar de valor, hacen que se ejecute el PROCESS.

Un ejemplo sería:

     PROCESS(señal1, señal2)
     ...

El PROCESS anterior sólo se ejecutará cuando señal1 o señal2 cambien de valor.

Variables y Señales editar

Hay que distinguir las señales y las variables, las señales se declaran entre ARCHITECTURE y su correspondiente BEGIN mientras que las variables se declaran entre PROCESS y su BEGIN. Dentro de un PROCESS pueden usarse ambas, pero hay una diferencia importante entre ellas: las señales sólo se actualizan al terminar el proceso en el que se usan, mientras que las variables se actualizan instantáneamente, es decir, su valor cambia en el momento de la asignación.

Un ejemplo es:


     ENTITY ejemplo IS
       PORT (c: IN std_logic;
         d: OUT std_logic);
     END ENTITY;
 
     ARCHITECTURE ejemplo_arch OF ejemplo IS
       SIGNAL a,b: std_logic;
     BEGIN
       PROCESS(c)
         VARIABLE z: std_logic;
       BEGIN
         a<= c and b;    --asignación de señales: después de ejecutarse esta línea, "a" seguirá valiendo lo mismo, sólo se actualiza al acabar el PROCESS
         z:= a or c;     --asignación de variables: en el momento de ejecutarse esta línea z valdrá "a or c" (el valor que tenía a cuando empezó el PROCESS)
       END PROCESS;
     END ARCHITECTURE;

Sentencias secuenciales editar

IF ... THEN ... ELSE editar

Permite la ejecución de un bloque de código dependiendo de una o varias condiciones.

     IF <condición1> THEN
        [sentencias 1]
     ELSIF <condición2> THEN
        [sentencias 2]
     ELSE
        [sentencias N]
     END IF;

Un ejemplo es:


     IF (reloj='1' AND enable='1') THEN
       salida<=entrada;
     ELSIF (enable='1') THEN
       salida<=tmp;
     ELSE
       salida<='0';
     END IF;

CASE editar

Es parecido al anterior porque también ejecuta un bloque de código condicionalmente, pero en esta ocasión se evalúa una expresión en vez de una condición. Se debe recordar que se deben tener en cuenta todos los casos, es decir, incluir como última opción la sentencia WHEN OTHERS.

    CASE <expresión> IS
       WHEN <valor1> => [sentencias1]
       WHEN <valor2> => [sentencias2]
       WHEN <rango de valores> => [sentenciasN]
       WHEN OTHERS => [sentenciasM]
    END CASE;

Un ejemplo es:


     CASE a IS
       WHEN 0        =>    B:=0;
       WHEN 1 to 50  =>    B:=1;
       WHEN 99 to 51 =>    B:=2;
       WHEN OTHERS   =>    B:=3;
     END CASE;

LOOP editar

LOOP es la forma de hacer bucles en VHDL. Sería el equivalente a un FOR o WHILE de un lenguaje convencional.

Su estructura es:

     [etiqueta:] [WHILE <condición> | FOR <condición>] LOOP
       [sentencias]
       [exit;]
       [next;]
     END LOOP [etiqueta];

Un ejemplo de bucles anidados es:


     bucle1: LOOP
       a:=A+1;
       b:=20;
       bucle2: LOOP
         IF b < (a*b) THEN
           EXIT bucle2;
         END IF;
         b:=b+a;
       END LOOP bucle2;
       EXIT bucle1 WHEN a>10;
     END LOOP bucle1;

Otro ejemplo, este con FOR es:


     bucle1: FOR a IN 1 TO 10 LOOP
       b:=20;
       bucle2: LOOP
         IF b<(a*a) THEN
           EXIT bucle2;
         END IF;
         b:=b-a;
       END LOOP bucle2;
     END LOOP bucle1;

Otro más con WHILE


     cuenta := 10;
     bucle1: WHILE cuenta >= 0 LOOP
       cuenta := cuenta + 1;
       b:=20;
       bucle2: LOOP
         IF b<(a*a) THEN
           EXIT bucle2;
         END IF;
         b := b-a;
       END LOOP bucle2;
     END LOOP bucle1;

NEXT y EXIT editar

NEXT permite detener la ejecución actual y seguir con la siguiente.

     [id_next:]
     NEXT [id_bucle] [WHEN condición];

Como se puede suponer, la sentencia EXIT hace que se salga del bucle superior al que se ejecuta.

     [id_exit:]
     EXIT [id_bucle] [WHEN condición];

Se puede ver su uso en los ejemplos del apartado anterior.

ASSERT editar

Se usa para verificar una condición y, en caso de que proceda, dar un aviso.

La sintáxis es:

     ASSERT <condición>
       [REPORT <expresión>]
       [SEVERITY <expresión>];

Este comando se estudiará en el subcapítulo de notificaciones, en la sección de bancos de prueba. Puesto que el uso de este comando se realiza únicamente en la simulación de circuitos.

WAIT editar

La ejecución de un bloque PROCESS se realiza de forma continuada, como si de un bucle infinito se tratara (se ejecutan todas las sentencias y se vuelven a repetir). Esto no tiene mucho sentido, puesto que continuamente se ejecutaría lo mismo una y otra vez, sería interesante poder parar la ejecución. Una forma de hacerlo es mediante las listas de sensibilidad, las cuales se han visto anteriormente, aunque existe otra forma de hacerlo mediante la sentencia WAIT, pero es algo más complejo.

     WAIT ON lista_sensible UNTIL condicion FOR timeout;

La lista_sensible es un conjunto de señales separadas por comas. La condición es una sentencia que activará de nuevo la ejecución. El timeout es el tiempo durante el cual la ejecución está detenida.

No es necesario utilizar las tres opciones, en caso de hacerlo la primera condición que se cumpla volverá a activar la ejecución.

     WAIT ON pulso;
     WAIT UNTIL counter = 5;
     WAIT FOR 10 ns;
     WAIT ON pulso, sensor UNTIL counter = 5;
     WAIT ON pulso UNTIL counter = 5 FOR 10 ns;

Si se utiliza una lista de sensibilidad no es posible utilizar la sentencia WAIT, sin embargo si es posible utilizar varias sentencias WAIT cuando esta actúa como condición de activación. Este comando se estudiará en el subcapítulo de retrasos, en la sección de bancos de prueba.

Descripción estructural editar

Las dos descripciones anteriores son las más utilizadas por los diseñadores, ya que son más cercanos al pensamiento humano. Aunque existe otro tipo de descripción, que permite la realización de diseños jerárquicos. VHDL dispone de diferentes mecanismos para la descripción estructural.

Definición de componentes editar

En VHDL es posible declarar componentes dentro de un diseño mediante la palabra COMPONENT. Un componente se corresponde con una entidad que ha sido declarada en otro módulo del diseño, o incluso en alguna biblioteca, la declaración de este elemento se realizará en la parte declarativa de la arquitectura del módulo que se está desarrollando. La sintaxis para declarar un componente es muy parecida a la de una entidad.

     COMPONENT nombre [IS]
       [GENERIC(lista_parametros);]
       [PORT(lista_de_puertos);]
     END COMPONENT nombre;

Si se dispone de un compilador de VHDL'93 no será necesario incluir en los diseños la parte declarativa de los componentes, es decir se pasaría a referenciarlos de forma directa. Un ejemplo de un componente podría ser el siguiente.


     COMPONENT mux IS
       GENERIC(
         C_AWIDTH : integer;
         C_DWIDTH : integer
       );
       PORT(
         control  : IN  bit;
         entrada1 : IN  bit;
         entrada2 : IN  bit;
         salida   : OUT bit
       );
     END COMPONENT mux;

Referencia de componentes editar

La referencia de componentes consiste en copiar en la arquitectura aquel componente que se quiera utilizar, tantas veces como sea necesario para construir el diseño. Para ello, la sintaxis que presenta la instanciación de un componente es la siguiente.

     ref_id:
       [COMPONENT] id_componente | ENTITY id_entidad [(id_arquitectura)] | CONFIGURATION id_configuración
       [GENERIC MAP (parametros)]
       [PORT MAP (puertos)];

Un ejemplo de referenciación del componente anterior sería.


     mux_1 : 
       COMPONENT mux
       GENERIC MAP (
         C_AWIDTH => C_AWIDTH,
         C_DWIDTH => C_DWIDTH
       )
       PORT MAP (
         control  => ctrl,
         entrada1 => e1,
         entrada2 => e2,
         salida   => sal
       );

Las señales ctrl, e1, e2 y sal deben ser declaradas previamente en la sección de declaraciones de la arquitectura, estas señales sirven para poder conectar unos componentes con otros. También deben declararse las variables que se utilizan en la sección GENERIC.


← Entidad Arquitectura Organización del código →