apuntes_vhdl
TRANSCRIPT
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
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
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 TopDown 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 10761987, IEEE Std 10761993). 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
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
& 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
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
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
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
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
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
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
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 ifthen, 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
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
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
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
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
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 netlist 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(n1 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 n1; variable res_parcial: std_logic;begin res_parcial:='0'; bucle:for i in 0 to n1 loop res_parcial:=res_parcial or a(i); end loop bucle; y<=res_parcial;end process;end archor_de_n;
15
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
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 entidadarquitectura. 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
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(i1),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
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
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
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, ProcessingFunctional 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(n1 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
a) En el editor de texto, en el menú OptionsUser 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ú InterfacesVHDL 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 online 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 Pretested 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
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 worldwide 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
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 ascendentedescendente 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<=q1; end if;
24
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 size1); constant e0: std_logic_vector(0 to size1):="00"; constant e2: std_logic_vector(0 to size1):="10"; constant e1: std_logic_vector(0 to size1):="01";
25
Carretera
Barreras
x1 x2
constant e3: std_logic_vector(0 to size1):="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
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
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 “netlist” tenemos una descripción de una serie de elementos lógicos conectados. La generación de netlist a partir de una descripción HDL se denomina síntesis. Con el fichero de “netlist”, 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 setup 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 netlist 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
29
Edición de esquemáticos o de texto
Verificación sintáctica
OK
Generación del “netlist”inicial
Sí
No
Simulación funcional
Simulación estructural y análisis de tiempos
OK
OK
º
No
Sí
No
Sí
Síntesis y ajusteGeneración de netlist
Configuración o programación del dispositivo
Elección del dispositivo
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
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
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 flipflops 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
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
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
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 EPM7032SLC445, 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
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=07)
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
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
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=07)
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
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
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 (areaspeed): 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 AssignLogic Options). Algunas de ellas son dependientes del dispositivo escogido. Para más información, se puede acudir a la ayuda online. 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
Multilevel 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
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(i1),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
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
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
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
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
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 setup 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 fanout (número de puertas a las que una señal está conectada). Las técnicas de buffering tratan de reducir este fanout 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
Bibliografía.
K. Skahill, "VHDL for Programmable Logic", Ed. AddisonWesley, 1996. Incluye en un CDROM el programa Warp2. F. Pardo, J.A. Boluda. "VHDL: Lenguaje para síntesis y diseño de circuitos digitales". Ed. Rama, 1999. Incluye un CDROM con un simulador (Veribest) de VHDL (máximo 2000 líneas de código), y una herramienta de Altera, MaxIIPlus, 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. McGrawHill. 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 (MaxPlus 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://techwww.informatik.unihamburg.de/vhdl/ www.altera.com www.xilinx.com www.actel.com
48
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
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