apuntes_vhdl

54
LENGUAJE VHDL: elementos básicos y consideraciones de diseño. Carlos Medrano Sánchez Inmaculada Plaza García Escuela Universitaria Politécnica de Teruel Departamento de Ingeniería Electrónica y Comunicaciones Universidad de Zaragoza

Transcript of apuntes_vhdl

Page 1: apuntes_vhdl

LENGUAJE VHDL:elementos básicos y consideraciones de diseño.

Carlos Medrano SánchezInmaculada Plaza García

Escuela Universitaria Politécnica de TeruelDepartamento de Ingeniería Electrónica y Comunicaciones

Universidad de Zaragoza

Page 2: apuntes_vhdl
Page 3: apuntes_vhdl

INTRODUCCIÓN .

Los lenguajes de descripción hardware se han convertido en una potente herramienta para el diseño de circuitos digitales. Entre ellos, destaca el VHDL, estándar al que se dedican estos apuntes. Aunque existen muchos libros sobre VHDL, tanto en la literatura en inglés como en español, las presentes notas pretenden servir al estudiante como una guía rápida para aprender VHDL orientado a síntesis, con ejemplos concretos de los sistemas digitales típicos, afrontando además los problemas y dudas más comunes que aparecen al trabajar con este lenguaje.

En la Parte B se tratan también aspectos relacionados con la implementación en dispositivos programables y cómo diseñar con ese objetivo. El proceso de síntesis y ajuste es muy complejo y las herramientas informáticas no están lo suficientemente desarrolladas como para optimizar de forma eficaz todos los diseño. Por ello, la experiencia del diseñador resulta fundamental en estos casos.

Las   descripciones   han   sido   compilados   utilizando   la   herramienta   MaxPlusII,   de   Altera (http://www.altera.com). Es un programa de libre distribución y muy potente. No obstante, el paso a otras herramientas debería ser sencillo.

INDICE.

Parte A: Elementos básicos del lenguaje VHDL.1. Introducción....................................................................................................................... 12. Elementos sintácticos en VHDL....................................................................................... 23. Formas de descripción en VHDL...................................................................................... 74. Ordenando los programas en VHDL: subprogramas, paquetes y librerías....................... 195. Ejemplos en VHDL.......................................................................................................... 23Parte B: Consideraciones de diseño en VHDL.1. Introducción: flujo de diseño ......................................................................................... 282. Requerimientos y limitaciones.......................................................................................... 303. Las etapas de síntesis y ajuste............................................................................................ 304. Errores habituales y efectos no deseados........................................................................... 315. Precauciones relativas a la señal de reloj........................................................................... 336. Las salidas y la codificación de las máquinas de estado................................................... 347.   Influencia  de  las  opciones  de compilación y   la   forma de escribir  el   código  en  el resultado de la síntesis y el ajuste.......................................................................................... 408. Otras técnicas de optimización.......................................................................................... 47Bibliografía ...........................................................................................................................     48Licencia................................................................................................................................. 50

Page 4: apuntes_vhdl
Page 5: apuntes_vhdl

PARTE A: ELEMENTOS BÁSICOS DEL LENGUAJE VHDL.

1. Introducción.

Con   la   creciente   complejidad   de   los   diseños   digitales   ha   aparecido   una   necesidad   de describir un circuito de la forma más eficiente y práctica posible. Un lenguaje de programación ofrece la posibilidad de un alto nivel de abstracción y es la solución adecuada para dicha tarea. Entre los lenguajes para la descripción de circuitos digitales, el VHDL es el que está alcanzando mayor popularidad, por ser un estándar y por su amplio campo de aplicación, desde el modelado para la simulación de circuitos, hasta la síntesis automática de circuitos.

El significado de las siglas VHDL es Very high speed integrated circuit (VHSIC) Hardware Description Language. Fue impulsado por el Departamento de Defensa de los Estados Unidos, cuando la complejidad de los sistemas digitales hizo necesaria herramientas adecuadas para su manejo:

­Permite diseñar y modelar un sistema en varios estilos y niveles de abstracción: flujo de datos, estructural, algorítmico.

­Una   descripción   en   VHDL   es   independiente   de   la   implementación   hardware   final   del proyecto. Puede ser sintetizado sobre una PLD o un ASIC. Incluso puede servir para simulación exclusivamente.

­Permite   el   diseño   Top­Down   y   modular,   es   decir,   dividir   un   sistema   complicado   en subsistemas más sencillos, tantas veces como sea necesario hasta poder resolver cada módulo (subsistema)   por   separado,   aplicando   el   lema   "divide   y   vencerás".   Idealmente,   el   diseño comenzaría por una descripción muy abstracta de un sistema, pero, y esto es importante, ya simulable. Posteriormente, se refinaría y se llegaría a un mayor nivel de detalle, partición en bloques etc. Ello facilita la prueba de cada módulo independientemente y da más seguridad al correcto   funcionamiento   del   sistema   final.   VHDL   ofrece   sus   propias   maneras   de   definir "subprogramas".

­Es un estándar (IEEE Std 1076­1987, IEEE Std 1076­1993). No obstante, hay que decir que cada fabricante ofrece sus propias librerías con funciones útiles no definidas en el estándar. Por ello,  el  paso de un entorno de programación a  otro no es  trivial.  Nosotros  suponemos que trabajamos con el estándar del año 93.

Inicialmente, VHDL fue diseñado para el modelado de circuitos digitales. Su utilización en síntesis  (implementación hardware)  no es  inmediata,  aunque  la  sofisticación de  las  actuales herramientas es tal que permite implementar diseños en un alto nivel de abstracción.

En este curso, se explicarán los fundamentos del VHDL pensando en su utilización para programar dispositivos de tipo PLD o FPGA. No conviene olvidar que el VHDL en sí mismo no está asociado a dispositivos programables, sino que es una descripción de un circuito en alto nivel. De cualquier modo, una descripción que sea sintetizable es casi siempre válida también para simulación, mientras que una descripción para simulación puede tener más problemas a la hora de compilarla sobre un dispositivo hardware. 

En este capítulo se introducen mediante ejemplos, la sintaxis del lenguaje, de forma que se cubran los bloques más habituales en diseño digital, intentando que rápidamente sea posible escribir   descripciones   útiles,   por   comparación   con   los   ejemplos   dados   en   los   apuntes.  No pretende ser una guía extensa del VHDL. Para ello, y para resolver problemas concretos, es necesario acudir a la bibliografía o a los manuales que cada fabricante ofrece.

­ 1 ­

Page 6: apuntes_vhdl

La herramienta que usaremos se denomina MaxPlusII, de la marca Altera. Es un programa que admite entradas en VHDL, síntesis en PLD de dicha marca, así como simulación de los ficheros fuente para programar las PLD (ficheros  jedec, .jed o .pof). Es una herramienta muy potente, que tiene además entrada con captura esquemática, analizador de tiempos y posibilidad de   interface   con  herramientas  de  otras  marcas.  Se  puede   conseguir   una  versión  gratis   (de estudiante) en www.altera.com, que aunque obviamente no tiene todas las posibilidades de la herramienta completa, permite el contacto con un entorno profesional.

2. Elementos sintácticos en VHDL.

Daremos   unas   breves   definiciones   de   los   elementos   que   se   usan   en   VHDL.   La   norma completa se encuentra en el estándar. Aquí, adaptamos la estructura y explicaciones del libro de F. Pardo y J.A. Boluda (ver bibliografía).

Comentarios: empiezan por dos guiones "­­" seguidos, hasta el final de línea.

Símbolos especiales: de un sólo carácter +  ­  /  *  ( )  .  ,  :  ;  &  '  "  <  >  |  =  #de dos caracteres **  =>  :=  /=  >=  <=  <>  ­­

Identificadores:   Dan   nombre   a   los   objetos   del   lenguaje   (señales,   variables,   constantes, funciones,   entidades   etc).  Están   formados   por   letras   y  números,   incluyendo   el   símbolo   de subrayado "_". No debe contener uno de los símbolos especiales, ni empezar por un número ni contener   dos   subrayados   seguidos.   Las   mayúsculas   y   minúsculas   se   consideran   iguales. Tampoco   puede   coincidir   con   una   de   las   palabras   reservadas   del   lenguaje   (que   tienen   un significado predefinido). En definitiva, no intentes usar nombres "extraños".

Números: Los números son mucho menos utilizados en VHDL que en otros lenguajes de programación, puesto que es un lenguaje orientado a diseños digitales, donde los valores que se manejan son bits o cadenas de bits. No obstante, se pueden utilizar y por defecto se considera que están en base 10. Se admite la notación científica para números en coma flotante. Es posible escribir números en otras bases utilizando el símbolo #. Así,  2#11010101#  es un número en base 2, 16#F3# en hexadecimal. 

Caracteres: Cualquier letra o número entre comillas simples: '2', 't'.

Cadenas: Conjunto de caracteres en comillas dobles: "hola".

Cadenas de bits:   los  bits  son en  realidad caracteres   ('0'  y  '1').  Es posible  agruparlos formando   cadenas   que   se   encierran   entre   comillas   dobles;   pueden   verse   también   como representaciones de números: "1110100",  O"126",  X"D2"; el primero es binario, el segundo octal,   indicado por  la O delante de  la cadena, el  último es hexadecimal,   indicado por  la X delante de la cadena.

Palabras reservadas. Son aquellas que tienen un significado especial en VHDL definido por el estándar. Existen dos estándares uno de 1987, y otro que lo amplia, de 1993. A lo largo de este tema iremos viendo las palabras clave del lenguaje.

Operadores.

­ 2 ­

Page 7: apuntes_vhdl

& concatenación. Concatena cadenas: así "110" & "001" representa "110001".

** exponencial 5**2 representa 5 al cuadrado (no admitido por MaxPlusII).<=  :=  de asignación, el primero para señales, el segundo para constantes y variables.ABS() valor absoluto*, / multiplicación, división+, ­ suma, restaMOD (módulo)REM (resto)SLL, SRL  Desplaza un vector de bits un número de bits a  la  izquierda o a  la derecha, 

rellenando con ceros los huecos libres (no admitido por el MaxPlusII).SLA, SRA Como el anterior pero el desplazamiento conserva el signo, el valor del bit más 

significativo (no admitido por el MaxPlusII).ROL, ROR rotación a izquierda o a derecha. Como un desplazamiento, pero los huecos que 

se forman son ocupados por los bits que van saliendo (no admitido por el MaxPlusII).

=, /= igualdad o desigualdad<, <=, >, >= menor, menor o igual, mayor, mayor o igualNot, and, nand, or, nor, xor, xnor.

Tipos de datos.

El VHDL es un lenguaje fuertemente "tipado", es decir, es estricto con respecto a los tipos. Toda señal,  variable o  constante declarada debe  indicar  su  tipo.  En  realidad,  en VHDL no existen tipos propios, pero el lenguaje incluye los mecanismos para poder definir cualquier tipo. Las librerías incluidas en las herramientas informáticas contienen los tipos más habituales, que denominamos predefinidos.

Tipos escalares:  Son tipos simples.  Tienen un orden que permite usar  operadores relacionales con ellos. Pueden ser enteros, flotantes, físicos o enumerados.

Enteros: Se definen incluyendo el rango.type bcd is range 9 downto 0;type integer is range –2147483648 to 2147483647; ­­ tipo predefinido

Reales (coma flotante): Se deben definir también en un rango, pero con límites reales.Físicos: Datos que trabajan con magnitudes físicas, es decir, tienen un valor y una unidad 

asociada. Hay un tipo predefinido en VHDL que es el tiempo, time. 

Enumerados: Pueden tomar cualquier valor en una lista.type bit is ('0','1'); ­­Predefinidotype boolean is (FALSE, TRUE);

El estándar IEEE 1164 define un tipo enumerado adicional, std_ulogic, y varios subtipos. El tipo std_ulogic se define con una lista de 9 posibilidades:

type std_ulogic is ('U',  ­­ Sin inicializar'X', ­­ Fuerza a desconocido'0', ­­ fuerza a 0'1', ­­ fuerza a 1'Z', ­­ Alta impedancia'W', ­­ Desconocido débil'L', ­­  0 débil'H', ­­ 1 débil'­', ­­ no importa);

­ 3 ­

Page 8: apuntes_vhdl

El subtipo std_logic proviene del std_ulogic y la lista de valores es la misma, pero este subtipo tiene una función de resolución (concepto en el que no entraremos). En la práctica, nosotros usaremos muy a menudo el tipo std_logic para síntesis. Es más amplio que el tipo bit, al incluir los estados de alta impedancia y de no importa. Para usar el subtipo std_logic hay que incluir el paquete std_logic_1164 de la librería ieee.

Tipos compuestos. Están compuestos por tipos escalares.

Matrices:  Como en  otros   lenguajes,  es  una colección de  elementos  a   los  que  se  accede mediante un índice.

type word is array(31 downto 0) of bit;signal b: word;

A los elementos de una matriz se accede mediante los índices. Si dato es una señal de tipo word, dato(3) es el elemento 3 de dato, dato(29 downto 25) es una parte del array.

Una matriz doble se definiría como:

type memoria is array(0 to 7, 0 to 63) of bit;

Existen algunos tipos predefinidos.

type bit_vector is array (natural range <>) of bit;type std_logic_vector is array (natural range <>) of std_logic;

que nos permiten definir señales como:

signal a: std_logic_vector(3 downto 0);

Son también de gran importancia los tipos  unsigned  y  signed. Representan vectores de std_logic  pero considerándolos con signo. El tipo  unsigned  representa valores numéricos positivos   o   cero.   El   tipo  signed  representa   valores   tanto   negativos   como   positivos   en complemento   a   2.   Esto   tiene   una   implicación   a   la   hora   de   hacer   comparaciones   y   otras operaciones.

signal a: unsigned(3 downto 0);

Registros: Equivalente al tipo record de otros lenguajes.

Subtipos.

Es posible la definición de subtipos como subconjuntos de tipos existentes.

subtype primeros is integer range 1 to 7;subtype X01 is  std_ulogic range 'X' to '1'; ­­ admite tres valores de std_ulogic: 'X', '0' y '1'subtype id is bit_vector(5 downto 0); ­­ restringe el rango de una matriz

Atributos.

Los   atributos  permiten   obtener   cierta   información  de   algunos   elementos   en  VHDL.   Se indican mediante una comilla simple al lado del identificador correspondiente. Estos atributos 

­ 4 ­

Page 9: apuntes_vhdl

están asociados a ciertos elementos del lenguaje y se manejan mediante la comilla simple '. Veamos unos ejemplos para clarificar el concepto:

Si dato es una señal de un tipo enumerado, entero, flotante o físico, se tienen los siguientes atributos:

dato'left límite izquierdo del tipo asociado a dato.dato'rightdato'low menor de los valores en el tipo asociado a dato.dato'highdato'length da el número de elementos de dato.

Un atributo importante es 'event. Da un valor booleano verdadero si acaba de ocurrir un cambio en la señal. Se usa especialmente con señales que sean de reloj:

clk'event

Otro atributo que aparece con frecuencia es 'range. Da el rango de un objeto limitado. Por ejemplo, si definimos:

signal word: std_logic_vector(15 downto 0);

entonces word'range es 15 downto 0.

En ciertos programas informáticos, hay también atributos que permiten agregar información adicional   a   los  objetos  que  se  están definiendo en  VHDL.  Estas   informaciones  adicionales sirven para pasar información a las herramientas de diseño que se estén utilizando en VHDL, por  ejemplo  si  queremos que ciertas  señales  estén en  determinados pines  de una PLD.  En MaxPlusII esto se realiza de forma más cómoda en el edito Floorplan.

Señales, constantes y variables.

Constantes.Como en otros lenguajes, una constante es un elemento cuyo valor no puede ser cambiado:

constant indice: integer:=5;constant maximo: integer;

En el segundo caso maximo no tiene ningún valor asociado. Esto se permite siempre y cuando el valor sea declarado en algún otro sitio.

Variables.Su valor puede ser alterado en cualquier instante. Es posible asignarle un valor inicial:

variable indice: integer:=0;variable auxiliar: bit_vector(31 downto 0);

Las variables sólo tiene sentido en bloques donde la ejecución es en serie: subprogramas y procesos (process). 

Señales.Son distintas de las variables. Desde un punto de vista formal, las señales no guardan un 

valor inmediatamente. Podríamos decir que las señales tienen dos partes, una donde se escribe (almacena un valor) y otra donde se lee. Lo que se lee no tiene por qué coincidir con lo que se acaba de escribir. En un momento dado, ambos valores serán iguales.

­ 5 ­

Page 10: apuntes_vhdl

Desde  un  punto  de  vista  más  cercano al  mundo de   los  circuitos  digitales,  una  señal   se entendería como un nodo en el circuito. Las entradas y salidas de un bloque digital deben ser definidas  como señales.  Asimismo,  cualquier  posible  conexión   real   en  el   circuito  debe   ser definida como señal.

Las asignaciones de señales se realizan con el operador "<=", mientras que las de constantes y variables utilizan el operador ":=".

Más adelante, veremos un ejemplo que aclara la diferencia entre variables y señales.

Entidades y arquitecturas.

La descripción de un circuito en VHDL consta al menos de dos elementos: la entidad y la arquitectura. En la entidad se definen las señales de entrada y salida. En la arquitectura, se define  lo que hace el  circuito.  Previamente a  la  definición de ambas,  se pueden incluir   las librerías y los paquetes necesarios en la descripción. Veamos un ejemplo.

library ieee;use ieee.std_logic_1164.all;

entity MUX2to1_a is port( A, B: in std_logic; Sel: in std_logic; Y:  out std_logic);end MUX2to1_a;

architecture behavior of MUX2to1_a isbegin Y<= ( B and Sel ) or ( A and not(Sel) );end behavior;

Las   dos   primeras   líneas   del   programa   contienen   las   librerías   (ieee)   y   los   paquetes (std_logic_1164.all) que serán utilizados en el programa. Los tipos más habituales están declarados en esta librería, por lo que su uso es casi imprescindible. Como en el programa se usa el tipo std_logic, es necesario incluir este paquete.

En la entidad llamada MUX2to1_a, definimos las salidas (Y) y las entradas del sistema (A, B y Sel). Estas señales son puertos (port). Todas ellas están definidas como de tipo std_logic. En este caso, se trata de un multiplexor de dos canales. En la arquitectura, se define lo que realiza la entidad. En este caso, la descripción son unas simples ecuaciones booleanas.

Los puertos de las entidades se definen con un modo. Los  modos  pueden ser de 4 clases: in, out, buffer, inout:

­ in. Entrada a la entidad. ­ out. Salidas de la entidad. Este tipo de puertos no se considera legible dentro de la entidad 

(por ejemplo, no puede aparecer a la derecha en una asignación)­  buffer. Es como el modo  out, pero es legible dentro de la arquitectura. Dicho de otro 

modo, permite la realimentación de la señal en la arquitectura.­  inout.  Para señales bidireccionales,  se   tiene que declarar  el  puerto como  inout,  que 

permite que se pueda escribir o leer desde el exterior. 

­ 6 ­

Page 11: apuntes_vhdl

3. Formas de descripción en VHDL.

En VHDL existen varias formas de describir un circuito:

­ Por un lado, se pueden especificar las relaciones entre diferentes objetos en VHDL. Esta posibilidad de descripción de un circuito se llama descripción de flujo de datos.

­   Otra   forma   de   describir   circuitos   se   conoce   como   descripción   comportamental   o algorítmica.   Pertenece   a   un   nivel   de   abstracción   más   elevado   y   describe   la   funcionalidad mediante un algoritmo.

­  Finalmente,  un estilo estructural es más cercano a una  lista de nudos que  interconecta componentes.

En un estilo de descripción mediante flujo de datos las sentencias son concurrentes, es decir, determinan relaciones que se cumplen siempre. En una descripción algorítmica, la ejecución de las sentencias es en serie,  una tras otra,  como en Pascal o C (con las peculiaridades de las señales que veremos a continuación). 

En un código en VHDL, pueden mezclarse los estilos, pero siempre teniendo en cuenta que la descripción en conjunto es concurrente.

Lenguajes como VHDL, pensado para describir circuitos, deben ser ante todo concurrentes. Un circuito no se ejecuta en serie, sino que las conexiones entre componentes siempre actúan. No obstante,  el   lenguaje VHDL también permite  descripciones con ejecución en serie,  que hacen más fácil la programación en abstracto.

3.1. Descripción mediante flujo de datos.

when  ... else: asignación con condición.

Se trata de una estructura concurrente. Veamos el ejemplo de un decodificador BCD a 10 líneas:

library ieee;use ieee.std_logic_1164.all;

entity BCD_9 is port(A: in std_logic_vector(3 downto 0);Y:  out std_logic_vector(9 downto 0));

end BCD_9;

architecture archBCD_9 of BCD_9 isbegin

Y<="0000000001" when A="0000" else   "0000000010" when A="0001" else   "0000000100" when A="0010" else   "0000001000" when A="0011" else   "0000010000" when A="0100" else   "0000100000" when A="0101" else   "0001000000" when A="0110" else   "0010000000" when A="0111" else   "0100000000" when A="1000" else   "1000000000" when A="1001" else   "0000000000";

end archBCD_9;

­ 7 ­

Page 12: apuntes_vhdl

En este  caso,  usamos varios  when ... else  anidados.  Cuando  la  entrada  A  toma un número en BCD, la línea correspondiente se activa. 

¿Qué  significa en este  caso que  la  estructura es  concurrente? Por ejemplo,  es  imposible asignar otros valores a la salida Y en el mismo programa. Así, este otro código daría un error de compilación:

­­ Incorrectolibrary ieee;use ieee.std_logic_1164.all;

entity BCD_9 is port(A: in std_logic_vector(3 downto 0);Y:  out std_logic_vector(9 downto 0));

end BCD_9;

architecture archBCD_9 of BCD_9 isbegin

    Y<="0000000001" when A="0000" else    "0000000010" when A="0001" else    "0000000100" when A="0010" else    "0000001000" when A="0011" else    "0000010000" when A="0100" else    "0000100000" when A="0101" else    "0001000000" when A="0110" else    "0010000000" when A="0111" else    "0100000000" when A="1000" else    "1000000000" when A="1001" else    "0000000000";

Y<="1111111110" when A="0000" else   "1111111101" when A="0001" else   "1111111011" when A="0010" else   "1111110111" when A="0011" else   "1111101111" when A="0100" else   "1111011111" when A="0101" else   "1110111111" when A="0110" else   "1101111111" when A="0111" else   "1011111111" when A="1000" else   "0111111111" when A="1001" else   "1111111111";end archBCD_9;

Aquí hemos escrito dos decodificadores distintos, uno escrito después del otro. Es un error pensar que, puesto que el segundo bloque está después del primero, es la activación en baja de las líneas la que se va a implementar. Esta descripción no puede compilarse en MaxPlusII. En Pascal   por   ejemplo,   no   hay   ningún   problema   en  dar   dos  valores  distintos   a   las   variables, quedándose con el  último.  En realidad,   lo  que estoy haciendo es,  pensando en un circuito, conectar las salidas de dos decodificadores distintos al mismo punto (Y(9),  Y(8)  etc.). Por tanto, ¿qué ocurre cuando los dos decodificadores manden señales distintas a las salidas? Esta incompatibilidad es la impide la compilación. (Siendo estrictos, el VHDL permite este tipo de situaciones si se incluye un mecanismo de resolución de las señales, que indica qué ocurre en caso de incompatibilidad; éste es un concepto de VHDL avanzado, que no veremos).  Esto es válido para cualquier estructura concurrente.

Conviene también fijarse es la definición de un vector de tipo std_logic. Por ejemplo, A se define como un  std_logic_vector(3 downto 0).  Hay que tener en cuenta que en este caso, el bit más a la izquierda corresponde al de índice 3, mientras que si se hubiese definido como std_logic_vector(0 to 3), sería el de índice 0. Esto es importante, puesto que en operaciones aritméticas con cadenas de bits, el bit más a la izquierda es el más significativo.

­ 8 ­

Page 13: apuntes_vhdl

with ... select ... when: asignación con selección.

Es también una estructura concurrente. El codificador BCD a 10 líneas queda:

library ieee;use ieee.std_logic_1164.all;

entity BCD_9 is port(A: in std_logic_vector(3 downto 0);Y:  out std_logic_vector(9 downto 0));

end BCD_9;

architecture archBCD_9 of BCD_9 isbegin

with A select  Y<="0000000001" when "0000",    "0000000010" when "0001",    "0000000100" when "0010",    "0000001000" when "0011",    "0000010000" when "0100",    "0000100000" when "0101",    "0001000000" when "0110",    "0010000000" when "0111",    "0100000000" when "1000",    "1000000000" when "1001",    "0000000000" when others;end archBCD_9;

Según el valor de la señal A, se produce una asignación u otra. Es importante la última línea, when others. Si no hemos agotado todas las posibilidades de la entrada A, es necesaria esta última línea para indicar qué debe hacerse en los casos que no se pongan de forma explícita anteriormente. En la práctica, es casi obligatorio ponerlo. No conviene olvidar que al definir A como un tipo std_logic_vector(3 downto 0), no sólo hay 16 posibilidades para A, sino que el resto de valores posibles para A  también cuentan, por ejemplo "ZZZZ", o "­­­­". Por ello, incluso un decodificador de 4 a 16 líneas necesitaría when others al final, puesto que se ha definido la entrada A como de tipo std_logic_vector.

3.2. Descripción comportamental algorítmica.

Se trata de partes de la descripción con una ejecución en serie, definidos dentro de unidades que comienzan con la palabra clave process. En un mismo programa puede haber múltiples bloques process. Cada uno de ellos equivale a una instrucción concurrente. Es decir, aunque internamente el proceso describe el comportamiento de un circuito mediante una ejecución de instrucciones en serie, el compilador para síntesis deduce un circuito a partir de un proceso, y por tanto es concurrente con el resto de las instrucciones (no puedo asignar un valor a una señal dentro y fuera de un proceso).

process: if ... then

library ieee;use ieee.std_logic_1164.all;

entity and3 is port(a,b,c: in std_logic;y:  out std_logic);

­ 9 ­

Page 14: apuntes_vhdl

end and3;

architecture archand3 of and3 isbegin

p1: process (a,b,c)begin if (a='1' and b='1' and c='1') then y<='1';  else y<='0'; end if;end process;end archand3;

El proceso   contiene  un   identificador,  p1  (el  nombre  que  queramos).  Posee   además  una "sensitivity list" o lista sensible, (a,b,c), que indica que el proceso se ejecutará cuando haya un cambio en una de estas variables.  Como en cualquier  ejecución en serie,  hay que  tener cuidado al anidar los  if­then, de forma que el resultado sea el esperado. La utilización de else es opcional, pero si no se agotan todas las opciones, puede dar lugar a latches. Cuando se utilizan varios if then anidados, puede usarse una contracción de else if, elsif. En caso de usar la primera forma, es necesario cerrar el nuevo if que se crea. En caso de usar la forma contraída, no hay que cerrar ningún if adicional. Si la condición que se ha de cumplir se refiere a la detección de un flanco de reloj y va precedida de otro if previo, es necesario usar elsif en la detección del flanco de reloj, por ejemplo, en un contador con reset asíncrono, la condición de detección del reset va antes de la detección de flanco de reloj.

Es conveniente dejar claro en el código qué es lo que tiene que asignarse a las señales de salida en el proceso para todas las posibilidades de las señales en la lista sensible. En caso de que haya casos no especificados al anidar los  if, el compilador guarda el valor anterior de la señal en cuestión, es decir se trata de un latch.

La forma en que un proceso se evalúa puede ser entendida del siguiente modo. Cuando una señal que se encuentra en la "sensitivity list" del proceso cambia, este es ejecutado de forma secuencial. Una vez que se ha llegado a la última instrucción, el proceso se detiene y las señales se actualizan. Es decir,  se puede imaginar como los valores antes y después de un paso de simulación.

Desde el punto de vista de la síntesis, en el proceso se determinará el valor de una o varias señales, en función de otras que deben estar todas en la lista sensible. El compilador deducirá el  circuito lógico que dé el mismo comportamiento que el conjunto de las sentencias secuenciales  del proceso (con la distinción entre variables y señales que veremos luego). Una vez obtenido el circuito   lógico,   éste   es  concurrente  con   respecto   al   resto   de   construcciones   dentro   de   la arquitectura. El siguiente programa por ejemplo, es un error.

library ieee;use ieee.std_logic_1164.all;

entity and3 is port(a,b,c: in std_logic;y:  out std_logic);

end and3;

architecture archand3 of and3 isbegin

p1: process (a,b,c)begin

­ 10 ­

Page 15: apuntes_vhdl

 if (a='1' and b='1' and c='1') then y<='1';  else y<='0'; end if;end process;

y<= a or b or c;end archand3;

Diferencia entre variables y señales.

Uno de los aspectos que puede causar confusión es la diferencia entre variables y señales. Lenguajes como C o Pascal sólo tienen variables. Las variables en VHDL sólo tienen sentido dentro de bloques de ejecución en serie (procesos, funciones o procedimientos). Fuera de ellos, las variables no existen y por  tanto, no puedo hacer referencias a ellas ni  leer  su valor.  La asignación de las variables es inmediata (como en Pascal). Por contra, podemos pensar en las señales como si fuesen las líneas de un esquemático, que transportan valores de voltaje, por explicarlos   de   una   forma   llana.   Las   señales   son   puertos   de   la   entidad,   o   señales   internas definidas dentro de la arquitectura. Dentro de un proceso, las señales no actualizan su valor hasta el final de la ejecución. Es decir, el valor que leemos en la señal dentro del proceso, es el que tenía antes de ejecutar el proceso. Entendemos que leemos una señal cuando aparece a la derecha en una asignación, o definimos otra señal o variable en función de ella, cualquiera que sea la estructura sintáctica.

Veamos el siguiente programa de ejemplo:

library ieee;use ieee.std_logic_1164.all;

entity aver is port( clk,serin: in std_logic; q: buffer std_logic_vector(3 downto 0));end entity;

architecture arch of aver is

begin p: process(clk) begin if clk'event and clk='1' then   q(0)<=serin;  q(1)<=q(0);  q(2)<=q(1);  q(3)<=q(2); end if; end process;

end;

Debemos aclarar  que  la sentencia  if  clk'event and clk='1'  detecta un flanco de subida   de  clk.   Por   ello,   se   trata   de   un   sistema   síncrono.   Corresponde   a   un   registro   de desplazamiento.   Observamos   que,   aunque   la   sentencia    q(0)<=serin  aparece   antes   que q(1)<=q(0), el valor de q(1) no toma serin también, sino que toma el valor de q(0) antes de entrar en el proceso. En realidad, mientras asignemos valores a señales distintas el orden no influye.  Sí  que  influye si   intentamos dar  valores a   la  misma señal  dos  veces  dentro de un proceso y de forma no excluyente:

p: process (a,b)begin

­ 11 ­

Page 16: apuntes_vhdl

 y<=a; y<=b;end process;

En este caso, la segunda asignación es la que prevalece, la primera no tiene efecto (y es una señal).

La simulación del registro de desplazamiento anterior confirma su correcto funcionamiento.

Analicemos ahora el siguiente programa, basado en el uso de variables:

library ieee;use ieee.std_logic_1164.all;

entity aver2 is port( clk, serin: in std_logic; q: buffer std_logic_vector(3 downto 0));end entity;

architecture arch of aver2 is

begin p: process(clk) variable d: std_logic_vector(3 downto 0); begin if clk'event and clk='1' then   d(0):=serin;  d(1):=d(0);  d(2):=d(1);  d(3):=d(2);  q<=d; end if; end  process;

end;

¿Describe  este  programa un   registro  de  desplazamiento?  No,  al  utilizar  una  variable,   la asignación   es   inmediata   ("a   lo   Pascal").   Si   hacemos  d(0):=   serin  y   a   continuación d(1):=d(0),   entonces  estamos  haciendo  d(1)  igual   a  serin  también.  De  esta   forma,  el circuito digital que estamos describiendo no consiste más que en 4 biestables D en paralelo, con la misma entrada. La simulación da lo siguiente:

­ 12 ­

Page 17: apuntes_vhdl

Podemos fijarnos también en la declaración de las variables, que se hace dentro del proceso, así como en el símbolo de asignación para variables ":=".

En  la práctica,   las  variables  pueden ser  útiles  para  hacer algunas construcciones,  y  son inevitables en los índices que recorren los vectores, pero se puede prescindir de su uso en la mayoría de las ocasiones. Un proceso con variables y señales puede ser bastante complicado de comprender. Hay que evitar el uso de variables, especialmente si se tiene poca experiencia y no se está seguro de su significado. Además, el uso de variables está asociado a descripciones muy algorítmicas, cuya síntesis es más compleja para los programas informáticos.

Conviene recordar también que las señales sólo se actualizan al final del proceso. No hay que poner sentencias en el proceso que dependan de una asignación previa a una señal. 

process: case .. when

Se trata de una estructura de ejecución en serie. Es parecido al  with ... select, pero más general, puesto que en cada caso no sólo se puede hacer una asignación, sino que después de cada "=>" puede escribirse una sentencia o un conjunto de ellas.

library ieee;use ieee.std_logic_1164.all;

entity mux4to1 is port(a: in std_logic_vector(3 downto 0);sel: in  std_logic_vector(1 downto 0);y:  out std_logic);

end mux4to1;

architecture archmux4to1 of mux4to1 isbegin

   p1: process (a,sel)begin

   case sel is  when "00" => y<=a(0);  when "01" => y<=a(1);  when "10" => y<=a(2);  when "11" => y<=a(3);  when others => y<=a(0); end case;

end process;

­ 13 ­

Page 18: apuntes_vhdl

end archmux4to1;

Como en el caso del with ... select es necesario completar con when others, cuando no se han revisado todos los casos posibles.

bucles: process:  for loop

Permiten realizar bucles dentro de procesos. También se ejecutan en serie.

­­ cuatro multiplexores 2 a 1 controlados por la misma entrada­­ de seleccionlibrary ieee;use ieee.std_logic_1164.all;

entity cuatromux2to1 isport(

a: in std_logic_vector(7 downto 0);sel:  in   std_logic;y:  out std_logic_vector(1 to 4));

end cuatromux2to1;

architecture archcuatromux2to1 of cuatromux2to1 is

type lineas is array (1 to 4) of std_logic_vector(1 downto 0);signal dum: lineas;

begin

p1: process (dum,sel)variable i: integer range 1 to 4;­­ Al ser un indice de un loop

                                   ­­ esta definicion es optativabeginfor i in dum'range loop y(i)<= (sel and dum(i)(1)) or (not(sel) and dum(i)(0));end loop;end process;

dum(1)<=a(1 downto 0);dum(2)<=a(3 downto 2);dum(3)<=a(5 downto 4);dum(4)<=a(7 downto 6);

end archcuatromux2to1;

Este programa es poco práctico, sólo se escribe para mostrar, además de los bucles, el uso de los arrays. Hemos definido un array de vectores de std_logic. Es en definitiva una matriz:

type lineas is array (1 to 4) of std_logic_vector(1 downto 0);

Hay que hacer notar también el uso del  for loop,  así como del atributo  'range.  Este atributo da el rango de un objeto. En nuestro caso es de 1 a 4, puesto que hemos definido el tipo lineas como un array (1 to 4). 

La señal dum está definida como de tipo lineas. Entonces dum(1) es el primer elemento del   array.   Como   cada   elemento   es   un  std_logic_vector,  dum(1)  es   un std_logic_vector, y  dum(1)(1)  es su primer elemento. Una definición alternativa de un array con el mismo número de elementos es:

type lineas is array (1 to 4, 1 downto 0) of std_logic;

­ 14 ­

Page 19: apuntes_vhdl

En este  caso,   si  dum  fuese  de   tipo  lineas,   un  elemento  de  dum  necesita  dos   índices, dum(1,0) por ejemplo, y cada elemento es un std_logic. 

Es interesante también la manera de referirse a una parte de una cadena, en la forma  a(7 downto  6),   que   selecciona   dos   elementos   de   la   cadena   total,   definida   como   un std_logic_vector(7 downto 0). Hacemos hincapié asimismo en la definición de señales y de tipos dentro de la arquitectura:

type lineas is array (1 to 4) of std_logic_vector(1 downto 0);signal dum: lineas;

La señal  dum  no es de salida ni de entrada. Se podría entender como un nodo interno al circuito.

De forma análoga al  for ... loop  se define el  while ... loop.  Existe también la posibilidad   de   salir   de   los   bucles   con  next  y  exit.   Pero   estas   construcciones   no   están totalmente soportadas por MaxPlusII

3.3. Descripción estructural.

Este tipo de descripción es cercano a una net­list de otras herramientas CAD. La descripción estructural  es  especialmente   interesante  para   la  incorporación de elementos  de biblioteca al diseño  y   la   realización  de  diseños   jerárquicos  a  partir   de  componentes.  Consideremos  dos entidades, and_de_3 y or_de_n:

library ieee;use ieee.std_logic_1164.all;entity and_de_3 is port( a,b,c: in std_logic; y: out std_logic);end and_de_3;architecture archand_de_3 of and_de_3 isbegin y<='1' when a='1' and b='1' and c='1' else    '0';end archand_de_3;

­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­library ieee;use ieee.std_logic_1164.all;entity or_de_n isgeneric(n: integer:=2);port( a: in std_logic_vector(n­1 downto 0); y: out std_logic);end or_de_n;architecture archor_de_n of or_de_n isbeginp1: process(a) variable i: integer range 0 to n­1; variable res_parcial: std_logic;begin res_parcial:='0'; bucle:for i in 0 to n­1 loop  res_parcial:=res_parcial or a(i); end loop bucle; y<=res_parcial;end process;end archor_de_n;

­ 15 ­

Page 20: apuntes_vhdl

Obsérvese que en el segundo caso existe una definición de una entidad con un parámetro variable, por ello se usa entity ... generic. De esta forma se puede definir una puerta or de n entradas, siendo por defecto de dos entradas.

Cada una de estas entidades se compilaría por separado. Estas entidades se pueden definir en una librería, con el paquete (package)correspondiente. 

Esto   permite   a   un   programa   principal   copiar   esos  componentes  (es   decir,   otras   entidades definidas   en   librerías),   asociándoles   los   puertos   correspondientes.   Veremos   como   definir librerías en MaxPlusII. Suponiendo que la librería se llama "milibrería" y que el paquete se llama "puertas", la descripción principal quedaría:

­­ Descripción principal library ieee;library milibreria;use ieee.std_logic_1164.all;use milibreria.puertas.all;

entity variaspuertas is port( r,s,t,p: in std_logic; y: out std_logic);end variaspuertas;

architecture archvariaspuertas of variaspuertas is

 signal x,w,z: std_logic; signal dum1:std_logic_vector(1 downto 0); signal dum2: std_logic_vector(2 downto 0);begin u1: and_de_3 port map(r,s,t,x); dum1<=r & p; u2: or_de_n port map(a=>dum1,y=>z); dum2<=r & s & p; u3: or_de_n generic map(3) port map (dum2,w); y<=x or z or w;end;

La entidad and_de_3 es una puerta and de tres entradas y la segunda or_de_n una puerta or  de  n  entradas,  donde  n  es  un parámetro que por  defecto  es  2.  Es   interesante  el  uso de generic antes de port, para permitir que la entidad tenga uno o varios parámetros variables. 

En la arquitectura del programa principal se hace una asignación de los "nudos" de cada componente. La orden básica es port map:

u1: and_de_3 port map (r,s,t,x);

u1 es un identificador de la sentencia, puede ser cualquier nombre. and_de_3 es el nombre del componente. Como en él hemos definido los tres primeros puertos como una entrada, y el último puerto como una salida, el hecho de poner el orden (r,s,t,x)  implica que x  será el resultado de una operación AND de r, s y t. Conviene hacer notar que los identificadores r,s,t y  x  no tienen   por qué coincidir con los de la definición del componente (a,b,c,y). La orden port map no admite constantes.

La siguiente sentencia:

u2: or_de_n port map(a=>dum1,y=>z);

­ 16 ­

Page 21: apuntes_vhdl

es otra asignación usando el operador "=>", en este caso a una componente or_de_n. Como no hacemos referencia  al   tamaño (parámetro n),   se  toma por defecto el  definido en  la  entidad or_de_n, es decir, 2. En el paréntesis, podemos ver otra forma de realizar una asignación, en lugar de por posición como en el ejemplo anterior, mediante el operador =>. Asignamos la señal dum1 al puerto llamado a de or_de_n, y la señal z al puerto y.  a e y son los nombres de los puertos   en   el   componente,   mientras   que  dum1  y  z  son   los   nombres   de   las   señales   en   la descripción   principal.   La   señal  dum1  es   una   señal   intermedia,   que   no   es   más   que   la concatenación de  r  y  p.  Así  obtengo un  std_logic_vector,  que es  compatible  con   la definición del puerto  a  en la componente  or_de_n.   Si utilizo esta forma de asignación, no necesito dar las señales en el mismo orden que en la definición del componente.

La siguiente sentencia:

u3: or_de_n generic map(3) port map (dum2,w);

es   una   puerta   or   de   3   entradas.  Generic map (3)  sirve   para   pasar   el   parámetro   al componente, en este caso se le dice que el tamaño del vector de entrada es 3.

Finalmente, la salida final, y, no se define mediante componentes sino mediante operadores booleanos:

y<=x or z or w;

Se puede, por tanto, mezclar tipos de descripciones dentro de un mismo programa, siempre que una misma señal no sea escrita en distintos puntos del programa.

Las señales dum1 y dum2 sólo sirven para tener una compatibilidad con los tipos definidos en las componentes. Así,  or_de_n  admite como entrada un  std_logic_vector. Por tanto, debo pasarle un objeto del mismo tipo.

En resumen, lo que hemos construido es un circuito como el que aparece en la figura de la página siguiente.

Es posible definir también las componentes and_de_3 y or_de_n en un único programa, en lugar de en una librería. En el listado, se comenzaría por las entidades y arquitecturas de cada uno de ellos, repitiendo cada vez las librerías que se usen en cada entidad­arquitectura. En la arquitectura principal, se definen las componentes que se van a utilizar, mediante las sentencia component. 

component and_de_3 port( a,b,c: in std_logic; y:     out std_logic);end component;

Los nombres y los modos de los puertos en estos componentes deben coincidir con los de la entidad definida para cada componente, y deben también colocarse en el mismo orden.

Conviene   insistir   en   que   esto   es   un   ejemplo   para   demostrar   el   funcionamiento   de   la instrucción  port map. En este caso, sería mucho más sencillo hacer la descripción principal directamente   sin   necesidad   de   definir   componentes.   La   ventaja   de   los   componentes   es   la capacidad que proporciona al programador para hacer un diseño modular, probando cada bloque por separado. El circuito equivalente se muestra en la figura siguiente:

­ 17 ­

Page 22: apuntes_vhdl

Podemos ver un ejemplo de componente definido en el mismo programa en el siguiente código que representa un sumador en serie. Además nos sirve para ilustrar el comando for ... generate  usado para hacer varias asignaciones estructurales en un bucle que dependa de un índice:

library ieee;use ieee.std_logic_1164.all;

entity celdasumadora is port(  a,b,cin:in std_logic;  s, cout: out std_logic);end celdasumadora;

architecture archcelda of celdasumadora isbegin s<=(a xor b) xor cin; cout<= (a and b) or ((a or b) and cin);end archcelda;

­­ Descripción principal

library ieee;use ieee.std_logic_1164.all;

entity sumador2 is port( a: in std_logic_vector(7 downto 0); b: in std_logic_vector(7 downto 0); cin: in std_logic; sum: out std_logic_vector(7 downto 0); cout: out std_logic);end entity;

architecture archsumador2 of sumador2 issignal c: std_logic_vector(7 downto 0);­­ acarreos intermedios

component celdasumadora port( a,b,cin: in std_logic; s, cout: out std_logic);end component;

begin  u1: celdasumadora port map(a(0),b(0),cin,sum(0),c(0));  bucle:for i in 1 to 7 generate  begin    u2: celdasumadora port map(a(i),b(i),c(i­1),sum(i),c(i));  end generate;cout<=c(7);end archsumador2;

­ 18 ­

U2

OR2

12

3

U3

OR3

1234

U1

AND3

1234

r

t

s

p

U4

OR3

1234

x

z

w

y

Page 23: apuntes_vhdl

Es posible también definir varias arquitecturas para un mismo componente, y asignar a cada uno la arquitectura adecuada en cada caso (mecanismos de configuración).

ATENCIÓN: En MaxPlusII, existen bastantes problemas a la hora de la compatibilidad de los modos (in, out, buffer, inout) tal y como se han definido en un componente y el modo de la señal que se coloca en el programa principal.  Especialmente delicado resulta el modo buffer.  La forma más elegante de evitar estos problemas es definir señales de apoyo en la arquitectura.  Al  estar   definidas   en   la   arquitectura,  no   tienen  modo y  pueden  escribirse   sin problemas dentro de los port map. Las verdaderas señales pueden ser obtenidas a partir de las de apoyo por simples asignaciones.

MÁS   ATENCIÓN:   MaxPlusII   no   permite   el   uso   de   constantes   como   puertos   de componentes en el comando  port map. Es necesario usar siempre señales. Esto no es en la práctica un inconveniente. Una señal llamada uno a la que se le asigna un  '1'  (uno<='1') fuera de un proceso o función es en la práctica una constante puesto que al ser un lenguaje concurrente no le puedo asignar un valor en ningún otro lugar del programa.

4. Ordenando los programas en VHDL: subprogramas, paquetes y librerías.

Como   otros   lenguajes   de   programación,   VDHL   permite   el   uso   de   subprogramas,   que contienen una porción de código y a los cuales se les puede llamar. La instrucción component es,  de  algún modo,  un subprograma,  ya que permite   la   reutilización de código.  Veremos a continuación otras dos estructuras que permite el VHDL: funciones y procedimientos.

4.1. Funciones y prodecimientos.

Son similares a las estructuras de otros lenguajes. Son subprogramas a los que se les pasan unos parámetros. Las diferencias entre funciones y procedimientos son:

­  Las   funciones   devuelven  un  valor.  El   procedimiento   no   devuelve   valores,   pero   puede cambiar un parámetro que se la haya pasado.

­ El modo de los argumentos de una función es siempre de entrada (in). Por ello dentro de la función sólo se pueden leer. No es necesario especificar el modo. En el procedimiento pueden ser de cualquier modo (in, out, inout, buffer), por lo que pueden sufrir modificaciones. El modo por defecto es in.

­  En  una  función,  siempre debe  haber  al  menos una sentencia  return,   seguida de  una expresión que indique el valor devuelto; es necesario definir el tipo del valor devuelto. En un procedimiento   no   es   necesario.   Se   puede   usar  return  sin   más,   para   indicar   el   fin   de   la ejecución.

­ En una función jamás puede aparecer la instrucción wait, mientras que un procedimiento sí.

Las funciones pueden definirse en la parte de declaraciones de una arquitectura, en cuyo caso la definición de la función sirve como declaración de la función. Este es el ejemplo que se muestra más abajo. Otra manera de definirlas es declararlas en un paquete package, incluyendo la   definición   de   la   función   en   el   cuerpo   del   paquete  package  body.   De   esta   forma,   la definición de  la   función será  visible  a  cualquier  programa que utilice   la  orden  use  con el nombre del package correspondiente. Lo mismo se aplica para los procedimientos.

­ 19 ­

Page 24: apuntes_vhdl

En general, las funciones y procedimientos son construcciones de alto nivel, que computan valores o definen conversiones de tipo o sobrecarga de operadores (extensión de las operaciones a tipos de datos distintos de los que admiten de forma natural), o como una alternativa al uso de componentes. No obstante, gran parte de las funciones más útiles están definidas en paquetes de la herramienta informática. 

Ejemplos:

El   primer   ejemplo   es   una   célula   comparadora   de   un   bit,   preparada   para   construir compararadores de mayor número de bits en cascada. La salida que indica la igualdad se realiza mediante  una  función,   llamada  igualdad.  Esta   función necesita   tres  parámetros,  y  da como resultado un bit.

entity comparador is port( a,b,igual_in,mayor_in: in bit; igual_out, mayor_out: out bit);end entity;

architecture arch_comparador of comparador is function igualdad (a,b,igual_in: bit) return bit is begin  return ((not(a xor b)) and igual_in); end igualdad;

 begin  igual_out<=igualdad(a,b,igual_in);  mayor_out<=(a and not(b)) or (not(a xor b) and mayor_in); end architecture;

El segundo ejemplo es una puerta "or" por procedimiento. Un procedimiento define una puerta "or". Después, se llama a este procedimiento para realizar la puerta "or" en el programa principal.

entity or_con_procedimiento is port( a,b: in bit; z: out bit);end entity;

architecture arch of or_con_procedimiento is procedure dff (variable x1,x2:in  bit;                variable y:  out bit) is begin   y:=x1 or x2; end procedure;

 begin

  p: process(a,b)  variable va,vb,vz: bit;  begin   va:=a;vb:=b;   dff (va,vb,vz);   z<=vz; end process;

 end architecture;

En MaxPlusII   los  procedimientos  deben ser   llamados con variables,  por   lo  que  también deben tener como parámetros variables.  Por ello, el cálculo de la puerta "or" en el ejemplo anterior   se   realiza   a   través   de   variables.   En   otros   entornos   informáticos   esto   no   es necesariamente así.  Los procedimientos y funciones en VHDL para síntesis no son algo tan usado como en otros lenguajes (Pascal). La forma "natural" de reutilizar código en VHDL es 

­ 20 ­

Page 25: apuntes_vhdl

mediante el uso de componentes. Además, las funciones y procedimientos presentan una cierta variabilidad   dependiendo   de   la   herramienta   informática,   que   puede   o   no   incluir   todas   las posibilidades incluidas en el estándar. Por ello, un programa con funciones y procedimientos es más difícil de pasar de un entorno a otro. Esto es especialmente válido en síntesis.

4.2. Bibliotecas, paquetes y unidades.

Para  organizar  ciertos  diseños  conviene  definir  ciertos  elementos  en  una  biblioteca,  que luego se usará  en la descripción principal. En la biblioteca se pueden incluir los ficheros de algunos elementos, que incluyan las entidades y arquitecturas. Se incluyen también los paquetes (packages).  Los   paquetes  permiten  introducir  componentes  (cuya definición de entidad y arquitectura puede estar en otro fichero), tipos, funciones y procedimientos. Tienen una parte declarativa y otra descriptiva. Por ejemplo, las sentencias que están casi siempre a principio de toda descripción serán:

library ieee;use ieee.std_logic_1164.all;

Esto indica el uso de la librería  ieee; dentro de ella se usa el paquete  std_logic_1164 (sentencia use); y dentro del paquete se usan todos los elementos (.all). Si se necesitase sólo uno, bastaría poner el nombre del componente.

La   forma   concreta   de   organizar   los   directorios   y   ficheros   depende   de   la   herramienta informática concreta que usemos.

Por   ejemplo,   en  MaxPlus   II,   supongamos  que  queremos   construir   realmente   el   ejemplo descrito en el apartado 3.3 sobre la descripción estructural de programas. Allí se definieron dos componentes and_de_3 y or_de_n. Cada uno puede estar en su fichero .vhd, "and_de_3.vhd" y "or_de_n.vhd". Colocamos ambos ficheros en un directorio,  por ejemplo, en el  directorio "c:\ejemplo". Compilamos cada uno de ellos por separado, como cualquier otra descripción. Basta hacer una compilación funcional (con el compilador activado, Processing­Functional SNF extractor),   sin   especificar   una   PLD   concreta.   Después   habría   que   hacer   el   paquete correspondiente:

library ieee;use ieee.std_logic_1164.all;

package puertas is

component or_de_n generic(n: integer:=2);port( a: in std_logic_vector(n­1 downto 0); y: out std_logic);end component;

component and_de_3 port( a,b,c: in std_logic; y:     out std_logic);end component;end package;

 El texto del package se guardaría en un fichero puerta.vhd, y se compilaría (el compilador 

reconoce que es un paquete  y  actúa en consecuencia,  no se   llega a  las  últimas fases  de  la compilación puesto que es una estructura meramente declarativa).

Si ahora queremos compilar una descripción principal usando los componentes and_de_3 y or_de_n, una vez que está escrito deberíamos hacer lo siguiente antes de compilarlo:

­ 21 ­

Page 26: apuntes_vhdl

a) En el editor de texto, en el menú Options­User libraries, se debe dar el "path" al directorio donde están los bloques y el package. En nuestro caso sería "c:\ejemplo"

b) Con el compilador activo, en el menú Interfaces­VHDL Netlist reader settings  se le da el nombre a la librería y otra vez el "path" al directorio donde se encuentre.  El  nombre de la librería debe coincidir con el encabezamiento asociado a library en el programa principal. En el ejemplo del apartado 3.3. sería milibrería. Es independiente del nombre del directorio donde hayamos compilado el paquete, y del nombre del paquete en sí mismo.

c) Ya podemos compilar la descripción principal, que deberá reconocer la nueva librería.Las librerías existentes en MaxPlus II son las siguientes, según la ayuda on­line del propio 

programa:

File Package Library Contentsmaxplus2.vhd maxplus2 Altera MAX+PLUS   II   primitives,   macrofunctions,   and 

selected megafunctions supported by VHDL.megacore.vhd megacore Altera Pre­tested   megafunctions   consisting   of   several 

different design files.std1164.vhdstd1164b.vhd

std_logic_1164 Ieee Standard for  describing  interconnection data  types for   VHDL   modeling,   and   the   STD_LOGIC   and STD_LOGIC_VECTOR types.

lpm_pack.vhd lpm_components Lpm LPM megafunctions supported by VHDLarith.vhdarithb.vhd

std_logic_arith Ieee SIGNED   and   UNSIGNED   types,   arithmetic   and comparison   functions   for   use   with   SIGNED   and UNSIGNED   types,   and   the   conversion   functions CONV_INTEGER,   CONV_SIGNED,   and CONV_UNSIGNED. 

signed.vhdsignedb.vhd

std_logic_signed Ieee Functions   that   allow   MAX+PLUS   II   to   use STD_LOGIC_VECTOR   types   as   if   they   are SIGNED types.

unsigned.vhdunsignedb.vd

std_logic_unsigned Ieee Functions   that   allow   MAX+PLUS   II   to   use STD_LOGIC_VECTOR     types   as   if   they   are UNSIGNED types.

Algunas de estas librerías ofrecen funciones útiles. Destacamos por su especial interés:

a) operadores sobrecargados. Se llama así a la ampliación de una función u operador, para admitir otros tipos de entrada. Por ejemplo, el operador  +  sólo está definido para operar con enteros. Si queremos sumar una cadena de bits y un entero, podemos incluir  la librería que incluye la definición de la función + ampliada. En MaxPlusII, el paquete std_logic_arith de la librería  ieee  incluye la definición del tipo  unsigned  (cadena de bits entendida como un número positivo o cero), así como la función + suma de un unsigned y un entero. También se pueden sumar std_logic_vector y enteros, si se incluye el paquete std_logic_unsigned, que permite tratar a los std_logic_vector como si fuesen enteros sin signo.

Es interesante también la posibilidad de cambio de tipo: paso a entero, a unsigned, a signed o a   std_logic.   Para   más   información,   se   puede   leer   la   ayuda   on   line   acerca   de   "conversion functions".

b)   Módulos   parametrizados,   LPM   ("library   of   parametrized   modules).  Se   trata   de componentes "ya hechos" que se pueden llamar mediante la orden  port map. La ventaja que presentan es la gran optimización  a la que dan lugar durante la compilación y síntesis en una PLD. 

­ 22 ­

Page 27: apuntes_vhdl

MAX+PLUS   II   offers   a   variety   of   megafunctions,   including   LPM   functions   and   other parameterized functions. Megafunctions are listed here by function. Functions indicated by an asterisk (*) are provided for backward compatibility only. 

Gates

lpm_andlpm_invlpm_bustri lpm_muxlpm_clshift lpm_orlpm_constant lpm_xorlpm_decode muxbusmux

Arithmetic Components

divide* lpm_comparelpm_abs lpm_counterlpm_add_sub lpm_dividelpm_mult

Storage Components

altdpram* lpm_latchcsfifo lpm_shiftregdcfifo* lpm_ram_dpscfifo* lpm_ram_dqcsdpram lpm_ram_iolpm_ff lpm_romlpm_fifolpm_dff*lpm_fifo_dc lpm_tff*

Other Functions

clklock pll   ntsc

Altera also offers a variety of  MegaCore/OpenCore functions. These functions are available from Altera's world­wide web site at http://www.altera.com.

5. Ejemplos en VHDL.

Daremos   a   continuación   unos   ejemplos   de   VHDL   que   cubran   aspectos   no   tratados anteriormente, pero que se encuentran a menudo en la síntesis de circuitos.

5.1. Biestable D síncrono.

library ieee;use ieee.std_logic_1164.all;

entity biestD is port( clk, d:  in std_logic; q:  out std_logic);end entity;

architecture archbiestD of biestD is begin

   p: process(clk) begin  if clk'event and clk='1' then   q<=d;  end if; end process;end;

Es importante darse cuenta de la detección del flanco de reloj, característica de todos los sistemas síncronos. Dentro de un proceso, un  if  con  clk'event  (detecta cambios en clk) y clk='1'  (después del cambio vale uno) detecta un flanco de subida del reloj. Un flanco de 

­ 23 ­

Page 28: apuntes_vhdl

bajada sería if clk'event and clk='0'. Dado que no hay ninguna entrada asíncrona, el proceso sólo tiene a clk en la lista sensible (desde el punto de vista de la simulación, diríamos que sólo se ejecuta cuando hay un cambio en la señal de reloj, verificándose en su interior que se trata de un flanco de subida).

Todo lo que venga después de la detección del flanco de reloj corresponde a salidas que pasan a través de biestables.

5.2. Biestable D síncrono con puesta a cero y a uno asíncronas. 

library ieee;use ieee.std_logic_1164.all;

entity biestD is port( clk, d:      in std_logic; set, reset:  in std_logic; q:           out std_logic);end entity;

architecture archbiestD of biestD is begin

p: process(clk,set,reset)begin if reset='1' then q<='0';  elsif set='1' then q<='1';   elsif clk'event and clk='1' then    q<=d; end if;end process;end; Hay que hacer notar que las condiciones del reset y del set van antes que la detección de 

la señal de reloj. Por ello, son asíncronas, y aparecen también en la lista sensible.

5.3. Contadores.

Contador ascendente­descendente con carga paralelo síncrona y reset asíncrono:

library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;use ieee.std_logic_unsigned.all;

entity contador is port( clk, load, up, reset: in std_logic; d:                    in std_logic_vector(7 downto 0); q:                    buffer std_logic_vector(7 downto 0));end entity;

architecture archcontador of contador is begin

p: process(clk, reset)begin if reset='1' then q<=x"00";  elsif clk'event and clk='1' then    if load='1' then q<=d;     elsif up='1' then q<=q+1;        else q<=q­1;    end if;

­ 24 ­

Page 29: apuntes_vhdl

 end if;end process;

end architecture;

Es interesante la forma de definir un contador. Con el paquete  std_logic_unsigned  se puede tratar en ciertas situaciones un std_logic_vector como un entero sin signo (y sumarlo a  un   entero  por   ejemplo).  Dado  que   la   señal  q  es  un  std_logic_vector,   es   necesario declarar este paquete para realizar la suma q<=q+1.

También es interesante la definición del modo de q, como buffer. Ello es debido a que en la ecuación q<=q+1, q aparece a la derecha, y una señal definida como modo out no podría ser leída. Otra manera de evitar esto, sería la creación de una señal auxiliar en la arquitectura que utilizaríamos dentro del proceso. Una vez fuera del proceso, podemos hacer que la salida sea igual a esa señal. De este modo, la salida nunca aparece a la derecha en una asignación.

Recordamos también que si un vector es definido como (3 downto 0), el bit 0 es el menos significativo.

Finalmente, hacemos notar también el uso de vectores en hexadecimal indicado por una x delante del vector: x"00" es equivalente a 8 ceros binarios. 

Un contador es también una máquina de estados, por lo que se puede definir como haremos en el apartado siguiente. No obstante, esta definición es mucho más pesada.

5.4. Máquinas de estado.

La máquina de estado tiene una parte donde se describe la tabla de evolución de estados, y otra   parte   donde   se   describe   las   salidas.   Consideremos   un   ejemplo   (de   T.   Pollán,   ver Bibliografía): dos sensores en la vía del tren indican cuando pasa el tren por la vía (la vía es bidireccional). Los sensores se encuentran a ambos lados de un cruce con una carretera. Un semáforo debe ponerse en rojo cuando se detecte el paso del tren. 

library ieee;use ieee.std_logic_1164.all;

entity semaforo is port(rs, a, b, clk: in std_logic; ­­ a y b son los sensores.sem:   out std_logic);

end;

architecture archsemaforo of semaforo isconstant size: integer:=2; signal estado: std_logic_vector(0 to size­1); constant e0: std_logic_vector(0 to size­1):="00"; constant e2: std_logic_vector(0 to size­1):="10"; constant e1: std_logic_vector(0 to size­1):="01";

­ 25 ­

Carretera

Barreras

x1 x2

Page 30: apuntes_vhdl

 constant e3: std_logic_vector(0 to size­1):="11";begin p1: process (clk,rs)   begin    if rs='1' then estado<=e0;    elsif rising_edge(clk) then     case estado is       when e0 =>       if a='1' then estado<=e1;        elsif b='1' then estado<=e2;         else estado<=e0;       end if;      when e1 =>       if b='1' then estado<=e3;        else estado<=e1;       end if;      when e2 =>       if a='1' then estado<=e3;        else estado<=e2;       end if;      when e3 =>       if (a='0' and b='0') then estado<=e0;        else estado<=e3;       end if;      when others => estado<=(others=>'­');     end case;    end if;   end process;­­ decodificacion de las salidas;  with estado select  sem<='0' when e0,      '1' when others;  end archsemaforo;

Se ha definido el estado como un vector de std_logic. De esta forma, el programador elige totalmente la codificación del estado. La inclusión de las constantes es simplemente por motivos de   claridad,   pero   se   podría   prescindir   de   ellas   y   usar   las   cadenas   de   bits.   La   función rising_edge()  también detecta el flanco de subida mientras que falling_edge() detecta el de bajada. Se aplica al tipo std_logic, pero no es aplicable al tipo bit. 

Otra descripción alternativa podría ser definir un tipo con nombres de estado, más próximos al lenguaje humano. Esto se haría en la arquitectura, antes del inicio (begin):

type tipoestado is (reposo, entra_por_a, entra_por_b, alejandose);signal estado: tipoestado; 

De esta forma, he creado un nuevo tipo que puede ser uno de los estados (de igual modo que el  tipo bit  puede ser 1 o 0).  Defino mi señal  con ese nuevo tipo,  y  luego puedo usar esos nombres en el programa, en lugar de cadenas de bits o nombres de constantes. La codificación de los estados la elegirá el compilador.

5.5. Salidas triestado.

Consideremos ahora el caso de una salida triestado. 

library ieee;use ieee.std_logic_1164.all;

entity triestado is port( enable, sel:  in std_logic; linea:  in std_logic_vector(1 downto 0); y :  out std_logic);end entity;

­ 26 ­

Page 31: apuntes_vhdl

architecture archtriestado of triestado is begin

y<='Z' when enable='0' else   linea(1) when sel='1' else   linea(0) when sel='0';end architecture;

La salida en alta impedancia se indica con 'Z' (ojo, es Z, mayúscula). 

Una salida de un biestable no se puede definir directamente como 'Z'. Para hacer que una salida de un registro esté en alta impedancia, se puede definir una señal en la arquitectura como salida del registro. La salida real del sistema (definida, por ejemplo, en la entidad) será igual a la salida del registro, o bien a 'Z' cuando se cumplan las condiciones adecuadas.

5.6. Puertos bidireccionales.

Consideremos el caso de un contador con las salidas en alta impedancia y con capacidad de carga por los mismos pines de salida del contador.

library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_unsigned.all;

entity cont is port (clk, load, oe: in std_logic;count_salida:inout std_logic_vector(7 downto 0));

end cont;

architecture archcont of cont issignal count: std_logic_vector(7 downto 0);begin contador: process (clk,load,count_salida) begin

if (load='1') thencount <= count_salida;

elsif clk'event and clk='1' thencount <= count + 1;

end if; end process contador; count_salida <= count when (oe='1'and load='0') else "ZZZZZZZZ";end archcont;

Este contador se puede cargar por los mismos pines de salida (pines bidireccionales). Tales tipos  de puertos  son necesariamente  de  tipo  inout.  En este  ejemplo,   se  asegura que si   se produce una carga en paralelo (load='1') el contador deja sus salidas en alta impedancia, puesto que se supone que debe haber un sistema externo que actúe sobre esos pines dando el valor a cargar.

­ 27 ­

Page 32: apuntes_vhdl

PARTE B: CONSIDERACIONES DE DISEÑO EN VHDL.

1. Introducción: flujo de diseño.

El lenguaje VHDL es en principio independiente del dispositivo programable que debamos utilizar.   Puede   incluso   utilizarse   para   hacer   una   simulación   sin   hacer   referencia   al   CI programable.  Además,  el   lenguaje es suficientemente  flexible para que un mismo problema admita diferentes codificaciones.

Sin   embargo,   no   todas   estas   codificaciones   darán   los   mismos   resultados   en   cuanto   a prestaciones y uso de recursos a la hora de programar un circuito dado. Aunque los programas informáticos permiten optimizar el paso de un código VHDL al fichero de programación de un CI programable, no podemos esperar en la actualidad que la solución encontrada sea la mejor. Por otro lado, optimizar puede tener sentidos distintos. Un diseñador puede estar interesado en obtener la máxima frecuencia de trabajo, o bien en reducir la utilización de recursos del CI, de forma que quede espacio para programar otras lógicas. Es tarea del diseñador ayudar “a mano” a que el resultado final sea lo más eficiente posible. Es más, a veces la forma de escribir el código VHDL depende del dispositivo que se use (por ejemplo si es una FPGA o un CPLD). 

El   ciclo   de   diseño   se   puede   dividir,   en   general,   en   3   fases:   especificación,   validación, materialización. En la especificación se introduce el diseño bien mediante captura esquemática, bien   mediante   un   lenguaje   de   descripción   hardware   (HDL).   El   diseño   debe   verificarse sintácticamente. 

Con la generación del “net­list” tenemos una descripción de una serie de elementos lógicos conectados. La generación de net­list a partir de una descripción HDL se denomina síntesis. Con el   fichero   de   “net­list”,   podemos   pasar   a   realizar   la   validación   del   diseño,   mediante   la simulación. La simulación puede ser funcional, considerando los elementos como ideales (sin retrasos), o estructural, en el que la herramienta informática incorpora modelos más elaborados de los elementos lógicos que permiten realizar  la simulación incorporando retrasos. En este último  caso,   podemos   abordar   además   el  análisis   de   los   tiempos  de   set­up  y  de  hold.  La simulación funcional  es  más  rápida y se  aconseja  realizarla  primero hasta  que  la   lógica de nuestro diseño se compruebe. Es interesante observar que los propios modelos con retrasos de los elementos lógicos pueden estar descritos en un lenguaje HDL (uso de HDL para simulación, que no veremos en este curso).

Una vez que se ha realizado la simulación funcional podemos pasar a la implementación física. En el caso que nos ocupa de dispositivos programables, es necesario una etapa de síntesis  y ajuste (“fitting”) en el que el diseño se divide en elementos asimilables a los recurso físicos de nuestra PLC (sumas de productos, multiplexores, memoria embebida). Se puede observar en la figura que se pueden generar net­list  tomando como base diferentes tipos de elementos, por ejemplo,   puertas   elementales   al   inicio   del   diseño,   o   bien   los   elementos   que   realmente   se encuentran en un dispositivo programable, una vez que sabemos cuál vamos a utilizar.

Tras este paso de síntesis y ajuste, obtendremos un fichero que nos permitirá configurar o progrmar nuestro dispositivo.

­ 28 ­

Page 33: apuntes_vhdl

­ 29 ­

Edición de esquemáticos o de texto

Verificación sintáctica

OK

Generación del “net­list”inicial

No

Simulación funcional

 Simulación estructural y análisis de tiempos

OK

OK

º

No

No

Síntesis y ajusteGeneración de  net­list

Configuración o programación del dispositivo

Elección del dispositivo

Page 34: apuntes_vhdl

2. Requerimientos y limitaciones.

En un diseño existen una serie de requerimientos previos como pueden ser:

­ El diseño debe estar terminado en una cierta fecha.­ Debe funcionar correctamente, con el mínimo de material.­ Debe ser capaz de operar a una cierta frecuencia.­ No puede superar un cierto coste económico.­ Debe ajustarse a un circuito más complejo (limitaciones de espacio y compatibilidad).

El orden de los requerimientos puede ayudar a tomar decisiones. Por ejemplo, si la fecha limite es el requerimiento más importante, el diseñador no dedica más tiempo a mejorar un diseño que ya cumple el resto de requerimientos. Si la frecuencia de operación es un parámetro más crítico que el coste, no se pierde tiempo intentando reducir el área si las prestaciones ya son suficientes.

Es necesario tener en cuenta también las limitaciones de los CI programables. 

­ Número de salidas/entradas.­ Número de biestables.­ El número de términos producto (o capacidad booleana).­ Las posibles combinaciones de reset/preset que admita.­ Los posibles esquemas de distribución reloj.­ La capacidad de interconexión interna de señales.

A la hora de decidir entre una CPLD o una FPGA es importante tener en cuenta algunas consideraciones.   Las   CPLDs   proporcionan   habitualmente   las   mejores   prestaciones,   pero también contienen menos registros que las FPGAs. Las CPLDs pueden implementar funciones más complicadas en un sólo pase (sin que la salida sea realimentada al integrado) y en ellas el análisis de tiempos es más fácil. Las FPGAs son más flexibles en cuanto a la construcción de celdas lógicas en cascada debido a su granularidad más fina (celdas lógicas más sencillas, pero en mayor número que en una CPLD, y con mayor capacidad de conexión). La optimización y el análisis   de   tiempos   son   más   difíciles   en   una   FPGA.   Antes   de   elegir   un   dispositivo,   es conveniente entender qué recursos necesita un diseño, su funcionamiento, así como los objetivos de prestaciones, coste etc... Con todos los requerimientos en una lista, se pueden comparar las necesidades con lo que un dispositivo puede ofrecer. Finalmente, hay que contar por supuesto con un software adecuado y manejable.

3. Las etapas de síntesis y ajuste.

La síntesis (synthesis) es el proceso de crear las ecuaciones lógicas o las listas de nudos a partir del código VHDL. El proceso de ajuste (fitting) consiste en encontrar la manera de que esa   lógica   sea   realizada   por   un   dispositivo   programable.   Una   optimización   específica   del dispositivo se puede dar tanto en un proceso como en otro.

El término fitting se usa normalmente para referirse a CPLDs. El software de ajuste (fitter) se encarga de repartir la lógica entre las macroceldas y de conectar y enviar las señales  a través de la matriz de interconexiones. El análogo en FPGA es el término de colocación y conexionado (place and routing), que es el proceso de determinar qué celdas lógicas serán utilizadas y cómo las señales se transmitirán de una a otra.

­ 30 ­

Page 35: apuntes_vhdl

El   software   de   síntesis   puede   pasar   al   software   de   ajuste   unas   ecuaciones   lógicas   que indiquen qué recursos se utilizarán. O bien, puede pasar unas ecuaciones no optimizadas, siendo tarea del "fitter" dicha optimización. Lo importante es que los programas de síntesis y ajuste estén bien acoplados, es decir que el "fitter" reciba la información del algoritmo de síntesis de una forma que le permita producir la mejor implementación. Si el "fitter" no produce ninguna optimización,   entonces   el   proceso   de   síntesis   debe   pasar   una   ecuaciones   lógicas   y   una información de forma que el "fitter" no tenga más que situar la lógica. Sin embargo, si el "fitter" es capaz de realizar alguna optimización, entonces la información que se le debe pasar tras el proceso de síntesis no debe restringir las posibilidades del "fitter".

4. Errores habituales y efectos no deseados.

Antes de entrar en lo que sería propiamente los ejemplos de diseño, vamos a ver algunas situaciones que pueden provocar confusión. 

4.1. El uso de los símbolos no importa '­'.

El tipo std_logic (y también el std_logic_vector) admite valores diferentes de '1' y '0'. Uno de ellos es el no importa '­'. Su uso puede simplificar la lógica. Por ejemplo, si una señal  depende de una entrada codificada en BCD existirán vectores  de entrada que no son posibles (entradas en decimal del 10 al 15). Asignando a la salida un vector no importa en el caso de tener dichas entradas, se consigue una simplificación mayor de las ecuaciones finales.

Sin embargo hay que tener cuidado utilizar el no importa en operadores relacionales (=,>,<). Por ejemplo, si quiero que una señal (salida) valga 1 cuando el primero de los bits de entrada (entrada) sea 1, entonces parece natural utilizar el siguiente código:

p1: process (entrada)begin if entrada="1­­­" then salida<='1';  else salida<='0'; end if;end process;

En muchas de las herramientas informáticas, este código puede dar lugar a un efecto distinto al   previsto.   El   origen   del   mal   funcionamiento   es   el   siguiente.   Un   software   puramente   de simulación admite  '­'  como un valor posible igual que  '0'  o  '1'.  Por tanto, la condición entrada="1­­­" sólo se evalúa como verdadera si entrada es literalmente "1­­­". Caso de que este código pudiese sintetizarse, la salida sería entonces siempre '0'. En hardware, el valor '­'  no tiene sentido. La interpretación de este código depende del compilador que estemos utilizando.

MaxPlusII sustituye los valores no importa '­' por cero '0'.

4.2. Registros no deseados.

Otro aspecto es el de los registros no deseados. Una forma de entender lo que ocurre en la asignación de señales en un proceso (process) es asumir que todas las expresiones se basan en el valor actual de las señales a la derecha del símbolo <=, y que dichas señales sólo adquirirán un nuevo valor al final del proceso (process), cuando se acaba. Consideremos el siguiente código:

­ 31 ­

Page 36: apuntes_vhdl

seq: process (clk)begin if clk’event and clk=’1’ then   d<=e;   c<=d;   j<=k;   k<=l or m; end if;end process;

Dado   que   la   asignación   para  d,  k,  j  y  c  aparecen   después   de  if  clk’event  and clk=’1’, estas señales representan los estados de flip­flops síncronos. Como dicha asignación se produce sólo en los flancos de subida del reloj, este código no describe una lógica en la que d es equivalente a e y j es equivalente a k. Más bien, el proceso de síntesis deduce que cada señal está a la salida de un registro. En caso de que uno no quiera tener las señales c y k a través de registros, el código debería ser el siguiente:

Seq: process (clk)begin  if clk’event and clk=’1’ then   d<=e;   k<=l or m;  end if;end process;c<=d;j<=k;

Otra   tipo  de   código  que  puede  dar   lugar   a  memoria   implícita   (a   través  de  un   lazo  de realimentación) es el uso de if ... then ... else sin escribir el valor de asignación a las señales para todas las condiciones. En ese caso, se sobreentiende que la señal conserva su valor. Por ejemplo, consideremos dos señales de tipo std_logic_vector, de la misma dimensión. El siguiente código que iría dentro de un proceso (process) es sintácticamente correcto:

p: process(a,b)begin if a=b then  igualdad<='1'; end if;end process;

Este código sería inadecuado si lo que se pretende es que igualdad valga '1' sólo si a=b. En este caso, si a es distinto de b, la señal igualdad conserva su valor, puesto que no le hemos dicho explícitamente qué ocurre en caso de no igualdad. 

Para evitar un latch asíncrono en la señal igualdad, el código debería ser:

if a=b then igualdad<='1'; else igualdad<='0';end if;

Cuando   se   anidan   varios  if  o   estructuras   similares,   es   muy   fácil   dejar   de   escribir explícitamente todos los casos posibles. En ese caso  el compilador deducirá un latch. Hay que ser muy cuidadoso con las expresiones condicionales para que esto no ocurra.. La existencia de 

­ 32 ­

Page 37: apuntes_vhdl

latches y realimentaciones no controladas puede dar lugar a condiciones de carrera y a que el sistema no funciones correctamente.

5. Precauciones relativas a las señales de reloj.

El  reloj   es  una   señal  que   lleva  con   frecuencia  a   situaciones  erróneas,   especialmente  en VHDL para síntesis. No debemos olvidar nunca que el código debe ser sintetizable, es decir, expresable  con  puertas   lógicas  y  biestables.  Si  nosotros  mismos  no  estamos   seguros  cómo expresar nuestro programa en la forma de un circuito digital, es muy probable que el compilador tampoco lo haga.

Veamos varios códigos erróneos. El primero es:

if clk'event and clk='1' then salida<=entrada; else salida<=not(entrada);end if;

Es decir, si hay un flanco de reloj se ejecuta una orden, si no otra. Pero ¿qué significa que no haya flanco de reloj? Esto carece de sentido. El flanco de reloj es sólo un instante en el eje de tiempos, no se puede comparar con un intervalo (el resto del eje de tiempos menos los puntos de flancos de subida). Por un motivo similar, algunas herramientas no suelen admitir los procesos donde se encuentran dos if ... end if independendientes, siendo uno de ellos el de reloj:

if clk'event and clk='1' then ...end if;if entrada='1' then ...end if;

Otro código erróneo es:

if clk'event and clk='1' and habilitación='1' then salida<=entrada;end if;

Los compiladores no entienden el código anterior, hay que escribir la condición de detección de flanco de reloj por separado, y después, en otro if anidado, añadir las condiciones que sean necesarios.

if clk'event and clk='1' then  if habilitación='1' then salida<=entrada;  end if;end if;

En el  caso de necesitar   reset  o preset  asíncronos,   la condición de reloj  debe  ir  después, recomendándose usar la forma elsif:

if reset='1' then q<='0'; elsif set='1' then q<='1';  elsif clk'event and clk='1' then     q<=d; end if;

6. Las salidas y la codificación de las máquinas de estado.

­ 33 ­

Page 38: apuntes_vhdl

6.1. Salidas decodificadas de las variables de estado.

En una  máquina  de  estados   (consideramos  el   caso  de  máquinas  de  Moore),   las   salidas pueden obtenerse combinatorialmente a partir de las salidas de los biestables que representan el estado del sistema. Esta forma de escribir el código, supone un mayor retraso en las salidas.

Como ejemplo, tomaremos un máquina de test de memorias. La máquina escribe unos en todas las posiciones de memoria, y después las recorre para comprobar que efectivamente se han escrito todo unos (la versión completa de la máquina, escribe también ceros y patrones de ceros y unos; aquí veremos sólo la primer fase). El test comienza con una señal de INIT. Una señal de RESET permite poner la máquina en su estado inicial.

La máquina de estados interacciona con una memoria, cuyos pines son CS, chip select, WR, (=1 si escribe, =0 si lee), y los 8 bits de datos. Además, las direcciones de la memoria son generadas   por   un   contador   externo,   que   tiene   como   entradas   CLR   (puesta   cero   síncrona), ENABLE (habilitación de contaje), y como salidas TC (final de cuenta).

La máquina de estados utiliza 3 estados para escribir, de forma que la dirección y los datos seas estables mientras se habilitan para escritura. Si el periodo de reloj es suficientemente lento, se cumplirán las restricciones temporales de la memoria.

library ieee;use ieee.std_logic_1164.all;

entity test_mem is port( reset,init, tc, clk: in std_logic; data:inout std_logic_vector(7 downto 0); nwr, ncs: out std_logic; redlight: out std_logic; en,clr:out std_logic);end entity;

architecture archtest_mem of test_mem istype StateType is (idle,write,enablemem,disablemem,nowrite,changedir,  readones, error);signal present_state, next_state : StateType;

begin state_comb:process(reset,init,tc,data,present_state) begin  case present_state is         when idle => if init='1' then next_state<=write;                       else next_state<=idle;                      end if;

­ 34 ­

Memoria

REDLIGHTTEN CLR TC

D

nWR

nCSMáquina de estadosCLK

DireccionesContador

NOE = 0

Page 39: apuntes_vhdl

                      clr<='1';en<='0';nwr<='1';ncs<='1';                      redlight<='0';                      data<=(others=>'Z');         when write => next_state<=enablemem;                            clr<='0';en<='0';nwr<='0';ncs<='1';                            redlight<='0';                            data<=(others => '1');         when enablemem => next_state<=disablemem;                            clr<='0';en<='0';nwr<='0';ncs<='0';                            redlight<='0';                            data<=(others => '1');         when disablemem =>  next_state<=nowrite;                                                       clr<='0';en<='0';nwr<='0';ncs<='1';                             redlight<='0';                             data<=(others => '1');         when nowrite => if tc='1' then next_state<=readones;                           else next_state<=write;                         end if;                         clr<='0';en<='1';nwr<='1';ncs<='1';                         redlight<='0';

     data<=(others=> 'Z');         when readones => clr<='0';en<='1';nwr<='1';ncs<='0';redlight<='0';                          data<=(others=>'Z');                          if data/="11111111" then next_state<=error;                           elsif tc='1' then next_state<=idle;                           else next_state<=readones;                          end if;         when error => next_state<=error;data<=(others=>'Z');                       redlight<='1';                       clr<='0';nwr<='0';ncs<='1';en<='0';         when others => next_state<=idle;                        clr<='0';nwr<='0';ncs<='1';en<='0';                        redlight<='0';                 end case;

 end process; state_clk: process(clk) begin  if reset='1' then present_state<=idle;   elsif clk'event and clk='1' then present_state<=next_state;  end if; end process;end;

El   código   anterior   contiene   dos   procesos  process.   Un   proceso   describe   la   lógica combinacional, y otro describe la sincronización de las transiciones de estado con el reloj. La parte combinacional produce el valor de next_state. Este valor es la entrada del conjunto de biestables cuya salida es present_state.

Para   codificar   los   estados   hemos   creado   un   tipo   especial:  StateType,   que   tiene   las siguientes posibilidades: idle, write, enablemem, disablemem, nowrite, changedir, readones y error.  Otra opción sería definir  el  estado como un  std_logic_vector  y  asociar  nosotros mismos los bits que nos interesen a cada estado.

Supongamos que se ha sintetizado sobre una EPM7032SLC44­5, de Altera. El resultado del report file indica lo siguiente:

­­ Node name is 'clr' ­­ Equation name is 'clr', location is LC029, type is output. clr     = LCELL( _EQ001 $  GND);  _EQ001 = !present_state0 & !present_state1 & !present_state2;

­ 35 ­

Page 40: apuntes_vhdl

­­ Node name is 'data0' ­­ Equation name is 'data0', location is LC028, type is bidir.data0    = TRI(_LC028,  _LC017);_LC028   = LCELL( _EQ002 $ !present_state2);  _EQ002 = !present_state0 & !present_state1 & !present_state2;

­­ Node name is 'data7~1' ­­ Equation name is 'data7~1', location is LC017, type is buried.­­ synthesized logic cell _LC017   = LCELL( _EQ009 $  GND);  _EQ009 = !present_state2 &  _X001 &  _X002;  _X001  = EXP(!present_state0 & !present_state1 & !present_state2);  _X002  = EXP(!present_state0 & !present_state1);

present_state1 = DFFE( _EQ015 $  VCC, GLOBAL( clk), !reset,  VCC,  VCC);  _EQ015 =  data0 &  data1 &  data2 &  data3 &  data4 &  data5 &  data6 &               data7 & !present_state1 &  present_state2         #  present_state0 &  present_state1 & !present_state2         # !present_state0 & !present_state1;

Para  una   interpretación  detallada  de   las   ecuaciones   anteriores,   se   remite   a   la   ayuda  de MaxPlusII ($=XOR, &=AND, !=NOT, #=OR, _EXP=expansor, o sea Nand). Podemos observar que la salida clr, por ejemplo, corresponde a la salida de una celda lógica (LCELL), con la xor de la expresión _EQ001 y reset. En la línea de debajo tenemos la ecuación de ­EQ001. Clr es una   salida   combinacional.   Otra   de   las   salidas,   datax   (x=0,1,...,7)   corresponde   a   una   celda triestado.   LA   ecuación   que   controla   la   habilitación   están   en   la   celda   lógica   17   (LC17). Finalmente, present_state0 es uno de los bits de estado. Por ello, aparece en la salida de un biestable (DFFE).

Los tiempos de propagación que aparecen, medido con respecto al flanco de reloj, son:

Clr NCs Datax(x=0­7)

En Redlight Nwr

7.5  ns 7.5 ns 16.1 ns 10.6 ns 12.1 ns 7.5 ns

En este caso, las salidas se decodifican a partir del estado, con lo que se añade un retraso adicional.

El código anterior es fácil de comprender y mantener, pero las prestaciones a las que da lugar pueden mejorarse. Para ello es necesario cambiar el código ya que el compilador no es capaz por sí mismo de detectar otras posibilidades.

­ 36 ­

Page 41: apuntes_vhdl

6.2. Salidas codificadas en los bits de estado.

Una posibilidad es usar los bits de estado como salidas. Para ello, hacemos la siguiente tabla:

State Clr En nwr ncs Redlight St0Idle 1 0 1 1 0 0Write 0 0 0 1 0 0Enablemem 0 0 0 0 0 0Disablemem 0 0 0 1 0 1Nowrite 0 1 1 1 0 0Readones 0 1 1 0 0 0Error 0 0 0 1 1 0

En este caso,  podemos distinguir   todos  los estados por sus bits  de  salida,  salvo Write y Disablemem. Por ello, debemos añadir un bit de estado St0 para distinguir estos estados.

En este caso, la descripción VHDL puede realizarse de la siguiente forma:

library ieee;use ieee.std_logic_1164.all;

entity test_mem2 is port( reset,init, tc, clk: in std_logic; data:inout std_logic_vector(7 downto 0); nwr, ncs: out std_logic; redlight: out std_logic; en,clr:out std_logic);end entity;

architecture archtest_mem of test_mem2 issignal state : std_logic_vector(5 downto 0);constant idle: std_logic_vector(5 downto 0):="101100";constant write: std_logic_vector(5 downto 0):="000100";constant enablemem: std_logic_vector(5 downto 0):="000000";constant disablemem: std_logic_vector(5 downto 0):="000101";constant nowrite: std_logic_vector(5 downto 0):="011100";constant readones: std_logic_vector(5 downto 0):="011000";constant errors: std_logic_vector(5 downto 0):="000110";

begin machine:process(reset,clk) begin  if reset='1' then state<=idle;  elsif clk'event and clk='1' then   case state is         when idle => if init='1' then state<=write;                       else state<=idle;                      end if;         when write => state<=enablemem;         when enablemem => state<=disablemem;         when disablemem =>  state<=nowrite;         when nowrite => if tc='1' then state<=readones;                           else state<=write;                         end if;         when readones => if data/="11111111" then state<=errors;                           elsif tc='1' then state<=idle;                           else state<=readones;                          end if;         when errors => state<=errors;                         when others => state<=idle;                end case; end if;

 end process;

­ 37 ­

Page 42: apuntes_vhdl

 state_comb: process(state) begin     case state is         when idle => data<=(others=>'Z');         when write =>  data<=(others => '1');         when enablemem => data<=(others => '1');         when disablemem =>  data<=(others => '1');         when nowrite => data<=(others=> 'Z');         when readones => data<=(others=>'Z');         when errors => data<=(others=>'Z');                         when others => data<=(others=>'Z');                 end case; end process; clr<=state(5); en<=state(4);nwr<=state(3);ncs<=state(2);redlight<=state(1);end;

Las ecuaciones obtenidas son:

­­ Node name is 'clr' = 'state5' ­­ Equation name is 'clr', location is LC030, type is output. clr     = DFFE( _EQ001 $  VCC, GLOBAL( clk),  VCC, !reset,  VCC);  _EQ001 =  clr & !en &  init &  ncs &  nwr & !redlight & !state0         # !clr &  en &  nwr & !redlight & !state0 &  _X001         # !clr & !en &  ncs & !nwr & !redlight         # !clr & !en & !ncs & !nwr & !state0;  _X001  = EXP( data0 &  data1 &  data2 &  data3 &  data4 &  data5 &  data6 &               data7 & !ncs &  tc);

­­ Node name is 'data0' ­­ Equation name is 'data0', location is LC028, type is bidir.data0    = TRI(_LC028,  _LC017);_LC028   = LCELL( _EQ002 $  GND);  _EQ002 = !clr & !en & !ncs & !nwr & !redlight & !state0         # !clr & !en &  ncs & !nwr & !redlight;

En este caso, clr es ya un bit de estado, por lo que aparece en un biestable.

Y los tiempos de propagación obtenidos:

Clr NCs Datax(x=0­7)

En Redlight Nwr

2.8  ns 2.8 ns 16.1 ns 2.8 ns 2.8 ns 2.8 ns

Los tiempos de propagación disminuyen para el caso de las variables que pasan a través de registros, no así para datax que todavía se decodifica combinacionalmente.

6.3. Codificación con un solo uno.

La codificación con un solo uno es  una  técnica que usa n biestables para  codificar  una máquina de estados con n estados. Cada estado tiene sus propio biestable, y solo un biestable está a 1 en cada instante. Decodificar el estado actual es simplemente encontrar qué biestable se encuentra a 1. De forma análoga se puede definir una codificación con un solo cero.

La ventaja que presenta la codificación con un solo uno es que el número de puertas que serían necesarias para obtener las salidas y la lógica del siguiente estado es generalmente mucho 

­ 38 ­

Page 43: apuntes_vhdl

menor. Por el contrario el número de biestables aumenta considerablemente (para problemas complejos esta técnica estaría más indicada en una FPGA).

Para realizar la codificación en un solo uno, MaxPlusII ofrece una opción en el compilador, Assign/Global Project Logic Synthesis.

En el caso de una CPLD, este tipo de codificación puede que no suponga una mejora de los tiempos de propagación.

6.4. Salidas codificadas en registros paralelos.

Una forma de intentar que las salidas de la máquina de estados llegue antes a los pines de salida es decodificar la salida de los bits de estado antes de que estos pasen por los biestables, y luego almacenar la información decodificada en biestables. La asignación de las salidas debe realizarse fuera del proceso en el que las transiciones de estado se definen. En nuestro ejemplo del controlador de memoria, en lugar de utilizar present_state para determinar el valor de las salidas, se debería usar next_state para determinar lo que deberían valer las salidas en el siguiente ciclo de reloj. Las salidas deben producirse a través de biestables. El esquema general sería el siguiente:

Nota   sobre   los   estados   ilegales:  En   la   codificación   de   máquinas   de   estado,   existen combinaciones de los bits de estado que no   corresponden a ningún estado. Por ejemplo, una máquina de estados con 7 estados necesita al menos tres bits, pero existirá una codificación que no se corresponde a ningún estado. 

En el caso de que se defina un nuevo tipo (enumeration type) en el código, el proceso de síntesis  que para aquellos vectores   ilegales,   tanto en  las  salidas como en  las ecuaciones de transición se asumen valores no importa, lo cual permite simplificar la lógica. En el controlador de memoria:

Type StateType is (idle, decision, read1, read2, read3, read4, write);Signal present_state, next_state: StateType;

Uno de los vectores de estado no corresponde a ningún estado legal. Caso de que la máquina de estados cayera en dicho estado, tanto las transiciones como las salidas no serían predecibles por el diseñador. 

­ 39 ­

Output

Lógica de estado siguiente

Biestablesde estado

Lógica de salida

Biestables de salida

Input

Page 44: apuntes_vhdl

El número de estados ilegales aumenta considerablemente con el número de biestables. Es particularmente alto en el caso de codificación con un solo uno, ya que se aprovechan sólo n de 2n posibilidades. 

En el diseño de una máquina de estados se pueden tener en cuenta aquellas situaciones en los que el sistema cae en un estado ilegal, indicando las transiciones que se deben realizar en tal caso. De esta forma el diseño final será seguro frente a fallos. Sin embargo, hay que tener en cuenta que realizar un diseño que prevea todas las situaciones posibles implica una lógica más complicada y algunas de las ventajas de un determinado tipo de codificación puede desaparecer si queremos hacer un diseño que contemple todos los casos posibles. Es muy importante dotar al sistema de alguna entrada de reset que lleve al sistema a un estado conocido.

7. Influencia de las opciones de compilación y la forma de escribir el código en el resultado de la síntesis y del ajuste.

Los   programas   de   síntesis   de   VHDL   ofrecen   varias   posibilidades   en   sus  opciones   de compilación. Es posible obtener resultados distintos según se use unas u otras. Generalmente las opciones de compilación se refieren a:

1) Optimización en área o en velocidad (area­speed): En el primer caso, el compilador tiende a minimizar los recursos de la PLD. De esta forma podemos incluir más lógica en el mismo dispositivo. Una optimización en velocidad tiende a reducir los tiempos de propagación, muchas veces a costa de consumir más celdas lógicas.

2) Opciones de ajuste al código del usuario. Se puede intentar que el compilador tienda a conservar la estructura que el programador ha escrito en su programa. Es decir, es probable que aparezcan en ese caso las señales intermedias definidas en la arquitectura en nudos internos del dispositivo, aunque no necesariamente sean señales externas a la PLD, mientras que en una compilación normal   el   compilador   tiende   a   simplificar   al  máximo eliminando  esas   señales intermedias. 

3)  Optimización exhaustiva,  normal o  rápida.  Si  el  diseño es complicado,  el  compilador puede tardar un tiempo considerable en realizar su tarea. Si nos interesa ver un resultado previo, aunque no sea el óptimo, se puede indicar que haga una optimización rápida.

4) Elección del tipo de biestable. Algunas PLD permiten configurar sus biestables como tipo T  o   tipo  D   síncronos.  El   programador  puede   elegir   un   tipo   si   lo  desea,   aunque   suele   ser aconsejable dejar que el compilador elija aquel que sus algoritmos calculen como el mejor en cada caso.

5) Asignación de señales a determinados nudos del sistema. Forzamos al compilador a que una señal se obtenga a la salida de una macrocelda o celda lógica, no necesariamente un pin de salida. Evidentemente, eso es una restricción más a la hora de compilar.

MaxPlusII  ofrece varias  opciones  de compilación (en el  menú   assign/global  projet   logic Synthesis o Assign­Logic Options). Algunas de ellas son dependientes del dispositivo escogido. Para más información, se puede acudir a la ayuda on­line. Aquí comentamos las más sencillas:

­ Optimización en área y en velocidad­ Estilo de síntesis: NORMAL, FAST y WYSIWYG. Un estilo de síntesis define una serie de 

opciones   de   compilación   avanzadas.   El   propio   usuario   puede   definir   sus   estilos   como   un conjunto de opciones. MaxPlusII ofrece tres estilos predefinidos:

* NORMAL: pensado para optimizar recursos* FAST: pensado para optimizar en velocidad*   WYSIWYG:   What   You   See   Is   What   You   Get:   El   compilador   tiende   a 

conservar la estructura descrita por el programador en su código VHDL.

­ 40 ­

Page 45: apuntes_vhdl

  ­   Multi­level   Synthesis:   Tiene   o   no   en   cuenta   algunas   de   las   opciones   avanzadas   de compilación.

Otro segundo aspecto que hay que tener en cuenta en la implementación final es la forma de escribir el código. Un mismo problema se puede describir de varias maneras. Cada una da un resultado distinto. En general es difícil saber cuál será la mejor. La literatura nos da una serie de recomendaciones:

­  Escribir  ecuaciones  lógicas sencillas cuando sea posible,  en  lugar de usar otro  tipo de descripción (z<=a  and  b and  c  en lugar de un  if    a='1' and  b='1' and  c='1' then z<='1';else z<='0';end if;)

­ Utilizar funciones o componentes cuando haya que realizar lógica reutilizable.­ Evitar asignar varias veces un valor a una señal dentro de un proceso. Aunque dentro del 

proceso es válido, puede dar lugar a confusión.­ Utilizar sentencias  case  en lugar de anidar  if.  En ocasiones se pueden definir señales 

intermedias que representen el conjunto de entradas a un proceso, usándola como señal en el case.

­ Evitar implementaciones de muy alto nivel. Más bien hay que intentar descripciones más cercanas al hardware.

Otra opción que se debe considerar es la de usar módulo incluidos en las propias librerías del programa. Estos módulos están optimizados y son reconocidos por la herramienta, por lo que en principio son los más adecuados. Por contra, hacen que el programa sea menos intercambiable con otras herramientas, al usar estructuras no estándar.

Como ejemplo de  la  influencia  de  las  opciones  de compilación y de diferentes códigos, consideremos cuatro programas distintos para describir un sumador de 16 bits.

El primer ejemplo considera las entradas como vectores unsigned. Además usa la librería std_logic_arith de MaxPlusII para poder sumar + dos vectores unsigned (y definir el tipo unsigned). En realidad no estamos introduciendo ninguna lógica, dejamos todo en manos de la función suma +.

­­ Suma de vectoreslibrary ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;

entity sumador1 is port(  a:  in unsigned(15 downto 0);  b:  in unsigned(15 downto 0);  cin:   in std_logic;  sum:  out unsigned(15 downto 0);  cout:  out std_logic);end sumador1;

architecture archsumador1 of sumador1 is

signal a1,b1,sum1,cin1: unsigned(16 downto 0);

begina1(15 downto 0)<=a; b1(15 downto 0)<=b; a1(16)<='0'; b1(16)<='0'; cin1<=("0000000000000000" & cin);

sum1<=a1+b1+cin1;cout<=sum1(16);

­ 41 ­

Page 46: apuntes_vhdl

sum<=sum1(15 downto 0);end archsumador1;

En el segundo caso, se hace un sumador con acarreo en serie, definiendo una célula sumadora de 1 bit como componente.

­­ acarreo en serielibrary ieee;use ieee.std_logic_1164.all;

entity celdasumadora is port(  a,b,cin: in std_logic;  s, cout: out std_logic);end celdasumadora;

architecture archcelda of celdasumadora isbegin s<=(a xor b) xor cin; cout<= (a and b) or ((a or b) and cin);end archcelda;

­­  Programa Principal

library ieee;use ieee.std_logic_1164.all;

entity sumador2 is port( a:  in std_logic_vector(15 downto 0); b:  in std_logic_vector(15 downto 0); cin:  in std_logic; sum:  out std_logic_vector(15 downto 0); cout:  out std_logic);end entity;

architecture archsumador2 of sumador2 is signal c: std_logic_vector(15 downto 0);­­ acarreos intermedios

component celdasumadora port( a,b,cin: in std_logic; s, cout: out std_logic);end component;

begin  u1: celdasumadora port map(a(0),b(0),cin,sum(0),c(0));  bucle:for i in 1 to 15 generate  begin    u2: celdasumadora port map(a(i),b(i),c(i­1),sum(i),c(i));  end generate;  cout<=c(7);end archsumador2;

El  tercer  caso se usa un componente propio de  la  librería de MaxPlusII   lpm (library of parametrized modules):

­­ Módulo de librería

library ieee;use ieee.std_logic_1164.all;library lpm;use lpm.lpm_components.all;

entity sumador3 is port( a:  in std_logic_vector(15 downto 0); b:  in std_logic_vector(15 downto 0); cin:  in std_logic;

­ 42 ­

Page 47: apuntes_vhdl

 sum:  out std_logic_vector(15 downto 0); cout:  out std_logic);end entity;

architecture archsumador3 of sumador3 is

begin

 lpm_add_sub_component : lpm_add_subGENERIC MAP (

LPM_WIDTH => 16,LPM_DIRECTION => "ADD"­­ONE_INPUT_IS_CONSTANT => "NO"

)PORT MAP (

dataa => a,datab => b,cin => cin,cout => cout,result => sum);

end archsumador3;

Finalmente,   podemos   hacer   un   sumador   siguiendo   el   esquema   del   acarreo   anticipado, definiendo una célula sumadora de 4 bits y un bloque calculador de acarreos.

­­ Acarreo anticipado

library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;

entity celula is port( a:  in unsigned(3 downto 0); b:  in unsigned(3 downto 0); cin:  in std_logic; sum:  out unsigned(3 downto 0); g_de_4,p_de_4:  out std_logic); ­­ generación y propagación del acarreoend entity;

architecture archcelula of celula issignal g,p: unsigned(3 downto 0);begin sum<= a + b + cin; gyp: process(a,b) begin  for i in 0 to 3 loop   g(i)<=a(i) and b(i);   p(i)<=a(i) or b(i);  end loop; end process;  p_de_4<=p(3) and p(2) and p(1) and p(0); g_de_4<=g(3) or (p(3) and (g(2) or (p(2) and (g(1) or (p(1) and g(0)))))); end archcelula;

­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;

entity calculo_acarreos is port( cin:  in std_logic;

­ 43 ­

Page 48: apuntes_vhdl

 g_de_4_vector,p_de_4_vector: in unsigned(3 downto 0); g_de_16, p_de_16:  out std_logic; c:  out unsigned(3 downto 1));end;

architecture archcalculo_acarreos of calculo_acarreos isbegin c(1)<=g_de_4_vector(0) or (p_de_4_vector(0) and cin); c(2)<=g_de_4_vector(1) or (p_de_4_vector(1) and (g_de_4_vector(0) or        (p_de_4_vector(0) and cin))); c(3)<=g_de_4_vector(2) or (p_de_4_vector(2) and (g_de_4_vector(1) or              (p_de_4_vector(1)  and  (g_de_4_vector(0)  or  (p_de_4_vector(0)  and 

cin))))); p_de_16<=p_de_4_vector(3) and p_de_4_vector(2) and p_de_4_vector(1) and           p_de_4_vector(0); g_de_16<=g_de_4_vector(3) or (p_de_4_vector(3) and (g_de_4_vector(2) or           (p_de_4_vector(2) and (g_de_4_vector(1) or           (p_de_4_vector(1) and g_de_4_vector(0))))));end;

­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ CÓDIGO PRINCIPAL­­­­­­­­­­­­­­­­­­­­­­­

library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;

entity sumador4 is port( a:  in unsigned(15 downto 0); b:  in unsigned(15 downto 0); cin:  in std_logic; sum:  out unsigned(15 downto 0); cout:  out std_logic);end;

architecture archsum4 of sumador4 is signal c: unsigned(3 downto 0); ­­ acarreos de cada bloque signal g_de_4_vector,p_de_4_vector: unsigned(3 downto 0);  signal g_de_16, p_de_16: std_logic;

 component celula port(  a:  in unsigned(3 downto 0);  b:  in unsigned(3 downto 0);  cin:  in std_logic;  sum:  out unsigned(3 downto 0);  g_de_4,p_de_4: out std_logic); ­­ generación y propagación del acarreo end component;

 component calculo_acarreos port(  cin:  in std_logic;  g_de_4_vector,p_de_4_vector:  in unsigned(3 downto 0);  g_de_16, p_de_16:  out std_logic;  c:  out unsigned(3 downto 1)); end component;

begin ­­ architecture del programa principal c(0)<=cin; bucle:for i in 0 to 3 generate  la_celula: celula port map(a(4*i+3 downto 4*i),b(4*i+3 downto 4*i),             c(i),sum(4*i+3 downto 4*i),g_de_4_vector(i),p_de_4_vector(i));

­ 44 ­

Page 49: apuntes_vhdl

 end generate; look_ahead: calculo_acarreos port map (cin,g_de_4_vector, p_de_4_vector,             g_de_16,p_de_16,c(3 downto 1)); cout<=g_de_16 or (p_de_16 and cin);end;

En la tabla siguientes resumimos los resultados de la compilación en cuanto a uso de recursos y peor tiempo de propagación. Se ha sintetizado sobre una MAX5128LC1 de Altera. Se trata de una CPLD (complex PLD) organizada en celdas lógicas (Logic Cell, LC), donde cada celda tiene una suma de términos producto y un registro, con posibilidad de salida combinacional o registrada.  En total   tiene 128 celdas lógicas. Existen además otros bloques, expansores, que contienen también una suma de términos producto que puede añadirse a la de una de las celdas lógicas, pero no está predefinida a cuál, sino que es programable. Tiene hasta 128 celdas lógicas y 256 expansores. En la tabla se comparan varias opciones de compilación y varios programas. Las opciones que no aparecen tienen el valor por defecto.

opciones ­­­>

programa ↓

Multilevel Synthesis

Estilo Ocupación(celdas lógicas,expansores)

Peor tiempo depropagación.

"suma de vectores" ON NORMAL 70/128, 36/256 196 ns"suma de vectores" OFF NORMAL 42/128, 34/256 128 ns"suma de vectores" ON FAST 90/128, 54/256 202 ns"suma de vectores" OFF FAST 57/128, 43/256 130 ns"suma de vectores" ON  WYSIWYG 99/128, 53/256 246 ns"suma de vectores" OFF WYSIWYG 57/128, 43/256 130 ns"suma en serie" ON NORMAL 25/128, 31/256 186 ns"suma en serie" OFF NORMAL 55/128, 99/256 260 ns"suma en serie" ON FAST 25/128, 31/256 186 ns"suma en serie" OFF FAST 51/128, 99/256 260 ns"suma en serie" ON WYSIWYG 24/128, 26/256 196 ns"suma en serie" OFF WYSIWYG error: muy complejo"módulo de librería" ON NORMAL 43/128, 34/256 202 ns"módulo de librería" OFF NORMAL 26/128, 30/256 98 ns"módulo de librería" ON FAST 50/128, 36/256 168 ns"módulo de librería" OFF FAST 48/128, 89/256 94 ns"módulo de librería" ON WYSIWYG 76/128, 46/256 202 ns"módulo de librería" OFF WYSIWYG 48/128,89/256 94 ns"acarreo anticipado" ON NORMAL 56/128, 28/256 182 ns"acarreo anticipado" OFF NORMAL error: muy complejo"acarreo anticipado" ON FAST 56/128,28/256 182 ns"acarreo anticipado" OFF FAST error: muy complejo"acarreo anticipado" ON WYSIWYG 72/128, 43/256 170 ns"acarreo anticipado" OFF WYSIWYG error: muy complejo

Del cuadro se puede destacar lo siguiente:

1.­  Existe  una  gran  diferencia  de   resultados  para  un   sumador  de  16  bits.  El   tiempo  de propagación puede ir desde 94 ns a 260 ns, casi un factor 3. El mejor tiempo de propagación se obtiene usando el módulo de librería del fabricante. En cuanto a la ocupación de celdas lógicas van desde 24 en el acarreo en serie hasta 99 como "suma de vectores". 

2.­ La diferencia de ocupación en este caso no es crítica pues nunca nos acercamos al 100%. Sin   embargo,   si   estuviésemos   al   límite   de   la   capacidad   de   la   PLD   este   sería   un   factor determinante.

3.­ Es difícil obtener conclusiones generales. Por ejemplo, la opción Multilevel Synthesis (que determina como se simplifican las ecuaciones) en off sirve para reducir los tiempos de 

­ 45 ­

Page 50: apuntes_vhdl

propagación e incluso el uso de recursos, excepto en el "acarreo en serie", en el que el efecto es el contrario. En otros casos, ni siquiera nos permite finalizar la compilación.

4.­ El uso de la estructura de acarreo anticipado no es el mejor tiempo de propagación, está en la zona media, al contrario de lo que ocurre si queremos hacer un ASIC. No obstante, hay que decir que el tiempo de propagación depende también de la granularidad de la estructura, es decir, en lugar de tener módulos de 4 bits, hacerlos de otro número de bits. En todo caso sí que es ligeramente mejor que el de acarreo en serie. Por tanto, es necesario saber previamente qué esquema de bloques queremos implementar en VHDL, es decir el diseñador  debe conocer  las diferentes técnicas para implementar bloques típicos.

El proceso de síntesis y ajuste es muy complejo. Es difícil hacer predicciones a priori sobre la   mejor   forma   de   implementar   en   código   VHDL   un   circuito.   Además   esto   depende   del problema   en   concreto.   La   tabla   anterior   sería   probablemente   distinta   si   analizásemos   un contador o un comparador (¡Inténtalo!).  Pero esto no quiere decir que ante un problema (el diseño no se ajusta en la PLD o los tiempos de propagación son muy elevados) no se pueda hacer  nada.  Si  no  encontramos   la  opción óptima,   al  menos   se  deben  explorar  una  cuantas posibilidades para no caer en la peor opción:

­ Hay que intentar cambiar las opciones de compilación, puesto que esto no cuesta mucho tiempo.

­ Si lo anterior no funciona, se puede cambiar el código del programa. Hay que evitar hacer código complicado o de muy alto nivel. Más bien se debe intentar acercarse al hardware, y que el compilador detecte que se está describiendo un bloque dado (un sumador, contador, etc)

­ Se debe recordar finalmente la existencia de librerías de los fabricantes.

Nota: El peor tiempo de propagación se ha obtenido observando la matriz de tiempos de propagación  en   MaxPlusII.  Esta   se   puede   obtener   en   con   el   analizador   de   tiempos   activo (Tyming analyzer), y con el análisis de retrasos (analysis/delay matrix).

Respecto al uso de recursos se puede encontrar en el "report file". Se trata de un fichero con información referente a la PLD. Podemos encontrar allí información útil sobre la asignación de pines al dispositivo, las ecuaciones implementadas en cada celda lógica, el "ruteado" de las señales o el uso de recursos. Sobre el uso de recursos, existe información bastante exhaustiva sobre cada LAB (Logic Array Block, bloque de celdas lógicas), pero se puede encontrar un resumen del total que puede ser como:

Total dedicated input pins used:                  8/8      (100%)Total I/O pins used:                           42/52     ( 80%)Total logic cells used:                48/128    ( 37%)Total shareable expanders used:                89/256    ( 34%)

Por  ejemplo,   con   las  ecuaciones   lógicas  es   relativamente   sencillo   seguirle   la  pista  a  un acarreo en serie (o similar) que pasa de una celda a otra.

También es destacable que, aunque sólo se haya usado el módulo del fabricante en uno de los programas, el compilador detecta en otros casos el sumador y lo asocia a un módulo conocido. Es el caso de la compilación del sumador con acarreo anticipado o del "suma de vectores". Así mismo, en el caso del componente de librería se puede observar en el report file que tiene un subcomponente look_add:look_aheader. Es decir, en realidad sí que es útil este esquema, pero dependiendo del código el compilador puede o no detectarlo.

­ 46 ­

Page 51: apuntes_vhdl

8. Otras técnicas de optimización.

Existen otras técnicas de optimización en las que no entraremos en detalle.  Simplemente indicaremos brevemente el principio de alguna de ellas.

La   técnica   de   pipelining   (segmentación)   consiste   en   tomar   un   largo   camino   de   lógica combinacional que se ejecuta en un solo ciclo de reloj , y dividirlo mediante la inclusión de registros en partes más pequeñas que se ejecutan en períodos más cortos. La figura ilustra este concepto (R bloque de registros, C bloque combinacional).

Si el gran bloque combinacional tiene un tiempo de propagación tpd, la frecuencia máxima de   operación   del   circuito   es   1/tpd.   Si   dividimos   el   largo   camino   a   través   del   bloque combinacional en tres partes, cada una tendrá un tiempo de propagación de, imaginemos tpd/3, es decir  el  sistema puede operar   (recibir  datos)  a  una  frecuencia   tres  veces  superior  a  a   la original.   Hemos   despreciado   en   esta   estimación   los   tiempos   de   set­up   de   los   biestables introducidos en el camino combinacional. Esto no significa que la salida este disponible antes, ya que desde que se produce un cambio a la entrada es necesario esperar tres ciclos de reloj (latencia) para obtener la salida. Sin embargo, si el sistema está  recibiendo operandos a una determinada   frecuencia,   ésta   es   mucho   más   alta   en   el   caso   segmentado.   Así,   cuando   una operación combinacional se ha realizado completamente, la siguiente ya se encuentra a 2/3 del camino total,  mientras que en el caso no segmentado se encontraría al principio. En FPGAs suelen ser   importantes  también las  consideraciones sobre el   fan­out   (número de puertas a las que una señal está conectada). Las técnicas de buffering tratan de reducir este fan­out cuando es crítico en el sistema (bien sea a través de opciones del compilador o en el código fuente).

Esquema de la técnica de pipelining.

­ 47 ­

R                                    CR

R C R C R C R

Page 52: apuntes_vhdl

Bibliografía.

­ K. Skahill, "VHDL for Programmable Logic", Ed. Addison­Wesley, 1996. Incluye en un CD­ROM el programa Warp2.­ F. Pardo, J.A. Boluda. "VHDL: Lenguaje para síntesis y diseño de circuitos digitales". Ed. Rama, 1999. Incluye un CD­ROM con un simulador (Veribest) de VHDL (máximo 2000 líneas de código), y una herramienta de Altera, MaxII­Plus, aunque la versión ya está desfasada.­ S. Olloz, E. Villar, Y. Torroja, L. Teres: "VHDL, lenguaje estándar de diseño electrónico", Ed. McGraw­Hill.­ E. Mandado, L. Jacobo Álvarez, M. Dolores Valdés, "Dispositivos Lógicos Programables y sus Aplicaciones", Ed. Thomson ­Paraninfo, 2002.­ M.A. Larrea, R. Gadea, R. Colom, "Diseño práctico con FPGAs", Ed. Universidad Politécnica de Valencia, 2000.­ T. Pollán, "Electrónica Digital, Tomos I, II y III", Ed. Prensas Universitarias de Zaragoza, 2004.­ M.A. Freire Rubio, C. Sanz Álvaro, “Diseño con dispositivos lógicos programables”, Dpto. de publicacines de la EUI de Telecomunicación, Universidad Politcnica de Madrid, 2002.­ www.altera.com    Distribuye gratis un software (Max­Plus II) que admite entrada en VHDL y en AHDL, captura esquemática, simulación funcional y temporal. Cada cierto tiempo actualiza las versiones libres. Es una herramienta mucho más completa que Warp2.Otras páginas Web:­ http://tech­www.informatik.uni­hamburg.de/vhdl/­  www.altera.com­ www.xilinx.com­ www.actel.com

­ 48 ­

Page 53: apuntes_vhdl

Licencia

Permission is granted to copy, distribute and/or modify this document under the terms of the OpenContent License.

OpenContent License (OPL)Version 1.0, July 14, 1998.This document outlines the principles underlying the OpenContent (OC) movement and may be redistributed provided it   remains unaltered. For  legal  purposes,   this  document is  the  license under which OpenContent is made available for use.The original version of this document may be found at http://opencontent.org/opl.shtmlLICENSETerms and Conditions for Copying, Distributing, and ModifyingItems other than copying, distributing, and modifying the Content with which this license was distributed (such as using, etc.) are outside the scope of this license.1. You may copy and distribute exact replicas of the OpenContent (OC) as you receive it, in any medium,   provided   that   you   conspicuously   and   appropriately   publish   on   each   copy   an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the OC a copy of this License along with the OC. You may at your option charge a fee for the media and/or handling involved in creating a unique copy of the OC for use offline, you may at your option offer instructional support for the OC in exchange for a fee, or you may at your option offer warranty in exchange for a fee. You may not charge a fee for the OC itself. You may not charge a fee for the sole service of providing access to and/or use of the OC via a network (e.g. the Internet), whether it be via the world wide web, FTP, or any other method.2. You may modify your copy or copies of the OpenContent or any portion of it, thus forming works based on  the Content,  and distribute  such modifications  or  work under   the   terms of Section 1 above, provided that you also meet all of these conditions:a) You must cause the modified content to carry prominent notices stating that you changed it, the exact nature and content of the changes, and the date of any change.b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the OC or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License, unless otherwise permitted under applicable Fair Use law.These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the OC, and can be reasonably considered independent and separate works in   themselves,   then   this  License,   and   its   terms,   do  not   apply   to   those   sections  when  you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the OC, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every  part   regardless  of  who  wrote   it.  Exceptions   are  made   to   this   requirement   to   release modified works free of charge under this license only in compliance with Fair Use law where applicable.3. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to copy, distribute or modify the OC. These actions are prohibited by law if you do not accept this License. Therefore, by distributing or translating the OC, or by deriving works herefrom, you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or translating the OC.NO WARRANTY4. BECAUSE THE OPENCONTENT (OC) IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE OC, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT   WHEN   OTHERWISE   STATED   IN   WRITING   THE   COPYRIGHT   HOLDERS 

­ 49 ­

Page 54: apuntes_vhdl

AND/OR OTHER PARTIES PROVIDE THE OC "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK OF USE OF THE OC IS WITH YOU. SHOULD THE OC PROVE FAULTY, INACCURATE, OR OTHERWISE UNACCEPTABLE YOU ASSUME THE COST OF ALL NECESSARY REPAIR OR CORRECTION.5.   IN  NO EVENT   UNLESS  REQUIRED BY  APPLICABLE   LAW  OR  AGREED TO  IN WRITING WILL ANY COPYRIGHT  HOLDER,  OR ANY OTHER PARTY WHO MAY MIRROR AND/OR REDISTRIBUTE THE OC AS PERMITTED ABOVE, BE LIABLE TO YOU   FOR   DAMAGES,   INCLUDING   ANY   GENERAL,   SPECIAL,   INCIDENTAL   OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE OC, EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

­ 50 ­