Lenguajes de Alto Nivel para Diseño de
Circuitos Integrados Digitales
Miguel Ángel Aguirre Echánove
Jonathan Noel Tombs
Fernando Muñoz Chavero
Departamento de Ingeniería Electrónica
Escuela Superior de Ingenieros
Universidad de Sevilla
CAPÍTULO I. LENGUAJES DE DESCRIPCIÓN DE HARDWARE ........................ 3
1. Introducción histórica .......................................................................................... 3
2. Los HDL’s en la metodología de diseño. ............................................................ 5
3. Jerarquía de un diseño. ........................................................................................ 7
4. Niveles de abstracción de un HDL ...................................................................... 8
5. HDL: Programa o diseño..................................................................................... 8
6. Lenguajes HDL.................................................................................................. 10
7. Síntesis de Circuitos. ......................................................................................... 11
CAPÍTULO II. ESTRUCTURA DE UN BLOQUE FUNCIONAL EN VHDL........ 13
1. Cuestiones previas ............................................................................................. 13
2. Estructura de un diseño VHDL ......................................................................... 13
3. La sección ENTITY........................................................................................... 14
4. La sección ARCHITECTURE........................................................................... 16
5. La sección CONFIGURATION ........................................................................ 19
6. La sección LIBRARY ....................................................................................... 20
CAPÍTULO III. DESCRIPCIÓN DE LA FUNCIONALIDAD ................................. 23
1. Concepto de concurrencia. ................................................................................... 23
2. Estructura de un proceso.................................................................................... 23
3. Bloques de sentencias........................................................................................ 29
4. Asignaciones concurrentes ................................................................................ 29
5. La sentencia generate......................................................................................... 31
CAPÍTULO IV. TIPOS DE DATOS Y SEÑALES ................................................... 33
1. Definición de tipos de datos. ................................................................................ 33
2. Tipo entero ........................................................................................................... 33
2. Tipo matriz ........................................................................................................... 34
3. Definición de tipos compuestos ........................................................................... 36
4. Tipos simples ....................................................................................................... 37
5. La librería IEEE ................................................................................................... 39
CAPÍTULO V. SUBSISTEMAS DIGITALES. EJEMPLOS DE DISEÑO............... 42
1. Codificación de Sistemas Síncronos .................................................................... 42
2. Codificación de sistemas aritméticos. .................................................................. 49
3. Codificación de módulos de memoria.................................................................. 51
6. VHDL NO SINTETIZABLE. SIMULACIÓN Y MODELADO ........................... 54
1. Mecanismo de simulación. ................................................................................ 54
2. Sentencias para modelado. ................................................................................... 55
3. Construir un “Test Bench”. .................................................................................. 57
4. La librería TextIO................................................................................................. 59
2
CAPÍTULO I. LENGUAJES DE DESCRIPCIÓN DE HARDWARE
Este tema tiene por objeto dar una introducción general a los Lenguajes de Descripción
de Hardware. El objetivo es aportar una visión de conjunto acerca del significado de en
qué consiste diseñar grandes sistemas electrónicos digitales utilizando HDL, y acerca de
sus herramientas. Tras una breve introducción histórica se procede a analizar el salto
cualitativo de diseñar en esquemáticos a diseñar en HDL. Posteriormente
introduciremos el concepto de jerarquía de un diseño y de nivel de abstracción.
Finalmente se describe el comportamiento de una herramienta de síntesis de circuitos
mediante HDL.
1. Introducción histórica
La necesidad de construir circuitos digitales cada vez más complejos es patente día a
día. Ya en el siglo XXI somos capaces de construir microprocesadores de muy altas
prestaciones que están compuestos por millones de unidades funcionales (transistores)
que realizan tareas de gran responsabilidad en la sociedad. Por ejemplo, un sistema de
control de transacciones económicas de una bolsa de valores ha de ser un sistema
informático extraordinariamente rápido y robusto, ya que un fallo en la transferencia de
información acarrearía un sinfín de problemas con los inversores. Otro ejemplo, la
electrónica de control de un avión supersónico tiene igualmente una responsabilidad
extrema, tanto en aportar la información necesaria al piloto para determinar su rumbo
como para asistirle en sus tareas de pilotaje y combate.
En la práctica, el 100% de la electrónica de control y supervisión de los
sistemas, elaboración de datos y transferencia de los mismos se realiza mediante
circuitos integrados digitales, constituidos por una gran cantidad de transistores: son los
llamados circuitos integrados de muy alta escala de integración, o VLSI.
Si en los años cincuenta y sesenta, en los albores de la electrónica integrada los
circuitos eran esencialmente analógicos, en los que el número de elementos
constituyentes de los circuitos no pasaba de la centena, en la actualidad el hombre
dispone de tecnologías de integración capaces de producir circuitos integrados con
millones de transistores a un coste no muy elevado, al alcance de una PYME. A
mediados de los años sesenta Gordon E. Moore ya vaticinaba un desarrollo de la
tecnología planar en el que cada año la escala de integración se doblaría, y de la misma
manera aumentaría la capacidad de integrar funciones más complejas y la velocidad de
procesamiento de esas funciones. Las predicciones de Moore se han cumplido con gran
exactitud durante los siguientes 30 años, y que la tendencia continuará durante los
próximos 20. En el año 2012 Intel espera integrar 1000 millones de transistores
funcionando a 10GHz.
Si bien construir estos circuitos parece una cuestión superada, diseñarlos supone
un serio problema. Los primeros circuitos integrados eran diseñados a partir del trazado
directo de las máscaras sobre un editor de layout, y prácticamente no eran comprobados
antes de fabricarse. Se confiaba en la pericia del diseñador a la hora de elaborar los
dibujos. Con la aparición de los primeros ordenadores de entonces gran potencia,
llamados estaciones de trabajo (concepto que hoy en día no difiere sustancialmente del
de ordenador personal) se incorporaron complejos programas de resolución de
3
ecuaciones diferenciales que permitían, alimentados con un modelo matemático del
circuito, verificar su funcionalidad antes de la fabricación. Este esquema funcionaba con
circuitos digitales, y aún funciona, con circuitos analógicos, con escaso número de
elementos muy bien dimensionados para determinar fielmente su comportamiento. La
microelectrónica digital continúa por otro camino su desarrollo con herramientas
específicas, como son los simuladores digitales o los generadores automáticos de layout,
que resuelven el problema de la construcción del circuito y la verificación del mismo.
Nace así la ingeniería de computación o CAE en las que se delegan en herramientas
software las tareas de manejo de grandes cantidades de información, bases de datos que,
de forma óptima contienen la información acerca de la funcionalidad del circuito, de su
geometría y de su conexiones así como de su comportamiento eléctrico. Si bien, por un
lado la electrónica digital supone una simplificación funcional de un comportamiento
analógico, el tamaño de los circuitos digitales es una complicación que requiere una
visión del problema muy diferente.
Una vez comentada la incidencia de la tecnología nos centramos en la
especificación de la funcionalidad en si, lo que se ha dado en llamar la interrelación
hombre-máquina. Tradicionalmente se han utilizado los editores de esquemas, como
parte del flujo de CAE o secuencia de programas que se han de utilizar para construir
nuestro circuito integrado. Estos editores son un magnífico mecanismo de proyección
de un diagrama de símbolos (tradicionalmente es el lenguaje utilizado por la
electrónica) para expresar la funcionalidad deseada. Estos editores tienen un nivel de
desarrollo espectacular. Son capaces de dar una visión muy precisa y completa del
diseño rápidamente. A esto ha contribuido en gran manera el auge de los entornos
gráficos de los sistemas operativos al estilo del conocido Windows, que a finales de lo
ochenta tenía ya unos predecesores de gran potencia y prestaciones.
Sin embargo la complejidad de los circuitos digitales aumentaba y las prestaciones de
los editores de esquemas no eran suficientes para responder a una capacidad de diseño
tan elevada. Editar un esquema requiere un esfuerzo de desarrollo muy alto. Para una
determinada función:
• La función ha de presentarse sin errores en ninguna conexión y función lógica.
Se precisa una verificación, por simple que sea el módulo.
• La presentación del esquema ha de ser limpia y permitir una lectura rápida.
• Las señales han de tener asociadas un nombre significativo que permita su
posterior identificación.
• Se construye a partir de unos elementos funcionales contenidos en una librería
que proporciona un fabricante y por tanto ligada al mismo.
• La edición se hace con una interacción ratón, teclado, paleta de dibujo... etc que
ralentiza mucho el proceso de inserción del esquema.
• Como se puede observar la técnica de los esquemas es suficiente, pero requiere
para cada unidad un gran esfuerzo y tiempo. ¿Que hacer ante este panorama?
A principios de los años 90 Cadence Design Systems, líder mundial en sistemas de CAE
para microelectrónica, propone el Verilog, un lenguaje alfanumérico para describir los
circuitos de forma sencilla y precisa: es el primer lenguaje de descripción de hardware
en sentido amplio como veremos en epígrafes posteriores. Otros fabricantes de
hardware habían propuesto un lenguaje más centrado en la resolución de un problema
concreto: generación de una función para un dispositivo programable, resolución del
circuito de una máquina de estados finitos a partir de su descripción de la evolución de
4
los estados, ... etc. Nacen los conceptos de descripción de alto nivel y de síntesis lógica,
que posteriormente formalizaremos.
En el año 1982 el Departamento de Defensa de los Estados Unidos promueve un
proyecto para desarrollar un lenguaje de descripción (conocido como MIL-STD-454L)
de hardware que:
• Describiera los circuitos digitales de forma amplia: Funcionalidad, tecnología y
conexionado
• Permitiera describir y verificar los circuitos a todos los niveles: funcional,
arquitectural y tecnológico (posteriormente matizaremos estas tres categorías).
• Describiera la tecnología misma, para poder diseñar circuitos que sean
independientes de la propia tecnología o bien durante la puesta a punto del
proceso de fabricación.
• Describiera modelos del entorno en el que se va a insertar el circuito de forma
que hubiese unas posibilidades de verificación más amplias del propio circuito.
El lenguaje resultante es el VHDL, que responde a las siglas VHSIC HDL (Very
High Speed Integrated Circuits, Hardware Description Language), y es ratificado por el
Instituto para la Ingeniería Eléctrica y
Electrónica (IEEE, en 1987) en la norma
IEEE-1076. Aunque en este sentido el
Verilog
cumple
las
propuestas
anteriormente anunciadas, el VHDL se
impone como lenguaje estándar de diseño.
Posteriormente
veremos
diferencias
generales entre uno y otro. Por el momento
nos referiremos a los HDL’s como
lenguajes alfanuméricos comprensibles para
describir circuitos electrónicos en sentido
amplio. En primer lugar veremos cuál ha
sido la aportación de los HDL’s en la
metodología clásica de diseño.
DESCOMPOSICIÓN
ARRIBA-ABAJO DEL
DISEÑO
INTRODUCCIÓN DEL
DISEÑO MEDIANTE
ESQUEMAS.
REALIZACIÓN
ABAJO-ARRIBA
BASE DE DATOS
DEL DISEÑO
SIMULACIÓN FUNCIONAL Y
VERIFICACIÓN
BASE DE DATOS
CON LAS PRIMITIVAS
DEL FABRICANTE
¿CONFORME
ESPECIFICACIONES?
NO
SI
2. Los HDL’s en la metodología de
diseño.
DISEÑO NIVEL FÍSICO
2.1. Metodología clásica.
La secuencia de herramientas informáticas
que procesan un diseño desde su
descripción en un lenguaje fácilmente
comprensible por el hombre hasta la
información final, útil para la fabricación
del dispositivo se llama flujo de diseño. La
figura representa un flujo típico en el que se
han introducido las derivaciones debidas a
las comprobaciones necesarias para
verificar la correcta introducción del diseño.
¿CONFORME
ESPECIFICACIONES?
NO
SI
FABRICACIÓN
Figura 1. Flujo de diseño con
herramientas CAD
Una vez que se decide la funcionalidad
5
del circuito que queremos integrar el procedimiento a seguir suele ser una
descomposición jerárquica de las funciones. Suelen ser diagramas de bloques y las
señales que los conectan estableciendo las relaciones entre ellos. A su vez estos bloques
son descompuestos en otros más simples, de menor rango en la jerarquía. Este proceso
continúa hasta alcanzar funciones basadas en los elementos de menor entidad. A este
proceso se le denomina descomposición Arriba-Abajo (Up-Bottom), usualmente
realizado sin la ayuda de ninguna herramienta informática.
Terminada la etapa de decisiones, se procede a la introducción del diseño. Si
utilizamos librerías de elementos básicos proporcionadas por los fabricantes (también
llamados primitivas) construimos los primeros elementos de la jerarquía a partir de estas
primitivas creando y verificando estas funciones del primer estadio jerárquico. Estos
primeros bloques se incorporan a la librería de elementos que constituirán nuestro
diseño.
Seguidamente construimos y verificamos el segundo nivel, tercero, cuarto, ...
hasta llegar al nivel más elevado, que es nuestro diseño y las interconexiones con el
sistema exterior. Esta es la fase de la metodología Abajo-Arriba (Bottom-Up). Cada
unidad funcional es incorporada a la librería de diseño de forma que varias funciones
podrían hacer uso de una misma función de inferior rango jerárquico. Este es el
concepto denominado reusabilidad de
una función.
DESCOMPOSICIÓN
ARRIBA-ABAJO DEL
DISEÑO
Ilustraremos lo expuesto mediante
un ejemplo sencillo: Imaginemos el
proceso de diseño y construcción de un
puente. El ingeniero ha de estudiar a
partir de las especificaciones más
generales: ubicación, formas posibles y
materiales, aspectos económicos y
medioambientales, hasta decidir la mejor
de las soluciones posibles. Una vez
realizados los cálculos de estructuras,
dimensiones y esfuerzos para cada
elemento del mismo, procede a realizar
los planos. Cada pilar es descrito y
dimensionado a partir de las unidades de
los materiales elegidos, y en función de
los cálculos de la estructura. Cada unidad
de material será una primitiva.
Obviamente intentará que en la medida
de lo posible los planos de cada pilar se
repitan y reutilizar los cálculos de esta
unidad jerárquica. La descripción general
de todos los elementos del puente y su
ubicación geográfica es el elemento de
mayor rango en la jerarquía.
INTRODUCCIÓN DEL
DISEÑO MEDIANTE LENG.
ALFANUMÉRICO (HDL).
REALIZACIÓN
ABAJO-ARRIBA
BASE DE DATOS
DE MÓDULOS
DE DISEÑO
SIMULACIÓN ALTO NIVEL Y
VERIFICACIÓN
¿CONFORME
ESPECIFICACIONES?
NO
SI
BASE DE DATOS
CON LAS PRIMITIVAS
DEL FABRICANTE
SINTESIS LÓGICA
CONDICIONES DE
DISEÑO.
RESTRICCIONES
DISEÑO NIVEL FÍSICO
¿CONFORME
ESPECIFICACIONES?
NO
SI
FABRICACIÓN
Figura 2. Flujo de diseño mediante HDL's
2.2. Metodología basada en HDL’s
6
La introducción de los HDL’s como métodos de descripción de circuitos han
enriquecido el proceso de creación de un diseño, lo han acelerado y asegurado,
abaratando los costes de desarrollo.
La figura 2 muestra la modificación del flujo clásico cuando se utiliza esta
técnica para la introducción del diseño. Existen varias mejoras sustanciales:
La librería de diseño es independiente de la tecnología para la que se diseña, por lo que
la reusabilidad de los módulos constituyentes es total. No solo se puede compartir entre
diseños sino que puede ser compartido por diferentes procesos de fabricación. Es
independiente de la tecnología. El código introducido es de Alto Nivel y la simulación
para su comprobación es asimismo de Alto Nivel. Solamente cuando se obtiene una
imagen física del diseño puede predecirse con cierta certeza si cumple o no cumple las
especificaciones. El programa de síntesis ha de alimentarse con el diseño, las
condiciones de contorno en que funcionará, y la tecnología disponible del fabricante. El
resultado será un código HDL de Bajo Nivel o lo que es lo mismo una representación
del circuito alfanumérica compuesta por primitivas y sus conexiones, lo que se conoce
como una netlist. Esta netlist se podría representar en un plano esquemático, pero no
encontraríamos una ordenación racional y comprensible entre las primitivas y sus
conexiones. Se utiliza para alimentar el flujo de síntesis física.
La penalización ha sido la pérdida de cierto control en la generación del circuito,
ya que en la nueva metodología hay una fase de síntesis automática en la que se cede a
una herramienta software la responsabilidad de la resolución del circuito final. Nos
acercamos a la situación ideal de la obtención totalmente automática del circuito a partir
de las especificaciones.
En el ejemplo del puente sería como el pedir a un programa informático que
realizara el propio puente proporcionándole los datos de situación geográfica, geológica,
resistencia del terreno, dimensiones, esfuerzos que tendría que soportar, materiales,
normativas, ... etc. La herramienta buscaría entre las múltiples y posibles soluciones, y
generaría los planos de un puente exento de estética, encontraría la solución final que
cumpliera todas las premisas, aunque seguramente no será la más económica.
Sin embargo también se podría realizar manualmente una primera aproximación
al puente final y dejar la responsabilidad al programa informático de hacer los cálculos
más pesados y concretos, por ejemplo de los pilares y la cubierta a sustentar, sería una
primera “ayuda” al programa allí donde el razonamiento humano es más potente: en la
creación. La solución final sería más cercana a la óptima, y que cumple otras premisas
más propias del razonamiento humano.
En la práctica esta aproximación es la más utilizada en la que el diseñador ha
cedido la responsabilidad de diseño de módulos regulares, sin la necesidad de verificar
su correcta implementación. Por ejemplo un multiplicador editado en un esquema a base
de puertas lógicas elementales (primitivas) había de ser verificado ante prácticamente
todas las posibles combinaciones de las entradas. Mediante lenguajes de alto nivel la
sentencia a*b representa todo el circuito multiplicador correctamente generado
automáticamente por un programa.
3. Jerarquía de un diseño.
7
En este momento del desarrollo del tema es fácil comprender la idea de
descomposición jerárquica de un diseño. Un diseño de gran dimensión, típico de un
circuito microelectrónico digital requiere una buena organización del diseño, una
descomposición razonada en módulos de inferior rango, que se suceden con una
descripción cada vez más detallada de sus funciones, los llamados bloques funcionales.
En realidad se sigue la conocida táctica de divide y vencerás.
4. Niveles de abstracción de un HDL
Como se ha comentado un HDL puede expresar un mismo diseño bajo diversos
puntos de vista, son los llamados niveles de abstracción. El más elevado es una
descripción en la que se define el comportamiento del circuito, es el llamado nivel
comportamental (behavioral). El más bajo es un modelo íntimamente relacionado con
la tecnología compuesto por primitivas y conexiones, representando la estructura del
circuito, es el llamado nivel estructural (structural). También llamaremos nivel
estructural al que expresa una interrelación entre bloques funcionales del mismo nivel
jerárquico. Posteriormente veremos que podemos definir niveles intermedios o mixtos
compuestos por mezclas de ambos, en el que las unidades de un nivel estructural no son
primitivas, sino que son módulos de rango jerárquico inferior.
5. HDL: Programa o diseño.
El lector habrá notado que hemos procurado eludir la palabra “programa” a la
hora de referirnos a la elaboración de un código HDL, nos referiremos a él siempre
como “diseño”. Como programa conocemos a una secuencia ordenada de comandos y
funciones, que realizan unas tareas definidas por el diseñador en una computadora.
Estos comandos y funciones están expresados en un lenguaje alfanumérico que es
fácilmente comprensible por el hombre, pero que tiene una proyección directa sobre el
lenguaje comprensible por la computadora, y por tanto, fiel a su arquitectura. La
traducción de uno a otro la realiza un programa compilador. Veamos de una forma
somera las tareas que se han de realizar para elaborar un programa informático:
1. Generación de un código basado en el lenguaje elegido (C, C++, PASCAL,
Fortran, ...)
2. Análisis sintáctico para detección de errores e inconsistencias del programa.
3. Elaboración de los códigos objeto reubicables o imagen binaria de los módulos
componentes.
4. Montaje del programa por parte del montador de enlaces y detección de errores
en la relación entre los módulos.
Durante la ejecución del programa se ejecutan una tras otra las instrucciones
siguiendo un esquema secuencial definido por la arquitectura de la CPU, que es la
llamada máquina de Turing. Existen típicamente dos soluciones: la arquitectura Harvard
y la arquitectura Von Newman. En ambos casos la estructura del programa es una
secuencia de instrucciones, accesos a memoria y saltos en la secuencia de ejecución. En
particular una subrutina puede ser accedida mediante un mecanismo de saltos tantas
veces como se precise, resultando un ahorro en términos de memoria de programa.
Los lenguajes de descripción de hardware, HDL’s, realizan aquello para lo que están
diseñados, describir un hardware: Describen un comportamiento propio de un circuito
en sentido amplio y por tanto no existen precedencia entre la activación de una parte del
8
mismo u otra. Representan un comportamiento inherentemente paralelo. Esto tiene un
conjunto de implicaciones que han de tenerse en cuenta a la hora de elaborar el diseño
que particularizaremos al caso del VHDL. La primera implicación es la relación entre el
lenguaje y la plataforma de desarrollo, que es una computadora y tiene el
comportamiento descrito en el párrafo anterior. Por tanto simulación y síntesis han de
estar provistas de mecanismos o artificios que emulen el paralelismo y que inciden
directamente en la formulación del código. La segunda implicación es en un cambio de
mentalidad en el diseñador a la hora de elaborar la descripción del diseño, estando
habituado a realizar programas software. El mencionado ahorro en este caso exige une
cuidadosa planificación del diseño previa a su elaboración. Existen usos típicos del
programador de software que están absolutamente prohibidos en la formulación de un
diseño hardware.
Un caso típico es utilizar variables auxiliares para almacenar temporalmente un
valor. Posteriormente es posible reutilizar de nuevo la misma variable en otro punto del
programa. Este uso es muy poco aconsejable si estamos diseñando circuitos, ya que una
variable de este estilo sería un registro, con diferentes accesos y salidas, implicaría una
compartición del mismo registro por varios módulos ... en esencia algo poco
recomendable. Sin embargo en un software es útil, ya que una variable así es una
posición de memoria que puede accederse cuantas veces sea preciso y si se reutiliza el
programa resultante es más compacto.
int aux;
....
aux=a+b;
...
c=función1(aux, ...);
...
aux=a*b; //reutilización de la variable
...
j=función2(aux, ...);
Otro caso muy corriente es la utilización de módulos aritméticos. En un diseño
HDL, una suma o un producto implica necesariamente la síntesis de un circuito digital
suma o producto. Cuando se declara la misma operación varias veces en software se
utilizan el sumador y el multiplicador de la CPU exhaustivamente, sin más. En HDL el
resultado sería un circuito con múltiples operadores que ocuparían gran cantidad de área
de silicio. Es importante, por tanto, “ayudar” al programa de síntesis a elaborar el diseño
definiendo una estructura que defina el circuito a nivel modular, lo que más adelante
llamaremos diseño rtl (register transfer level), como nivel de abstracción adecuado para
diseñar.
Otra situación muy típica es la evaluación secuencial de asignaciones:
a=a * b;
El procesador evalúa a * b y almacena el resultado en un registro propio, para
posteriormente transferir el valor a la posición de a. Esto es algo impensable en
hardware ya que se trata de describir un multiplicador cuya salida es la entrada, es decir,
una realimentación combinacional.
9
Queda, pues, comentar el proceso que realiza un programa de síntesis a la hora
de elaborar el circuito a partir de nuestra descripción del hardware:
1. Generación del código en un lenguaje paralelo, comprensible por el usuario
(VHDL, Verilog, ABEL, ...).
2. Análisis sintáctico para detección de errores en la generación del código.
3. Elaboración del módulo, identificación y asignación de una imagen hardware
del mismo (existen comandos y funciones de los HDL’s que no son traducibles
a hardware, y que se utilizan para la simulación), lo que se conoce como modelo
de referencia de una estructura HDL.
4. A partir del modelo de referencia, se genera un circuito basado en primitivas.
5. Optimización del circuito, teniendo en cuenta las condiciones de contorno del
diseño, es decir, generación automática del diseño final con las restricciones
definidas por el usuario.
6. Elaboración de una descripción en bajo nivel del circuito, una netlist en un
formato estándar para la síntesis física.
En los lenguajes HDL’s se da la doble implicación, la de describir un hardware
para implementación de un circuito (síntesis del circuito) y modelado del circuito
(simulación del circuito), de la tecnología y del sistema. Por tanto existen funciones
que no identifican bien un circuito. Por ejemplo, en VHDL se puede escribir el
comando:
wait for 47ns;
¿existe un circuito capaz de hacer la operación de espera de 47ns exactos? Obviamente
no. Sin embargo puede servir para expresar el retardo de una señal o un módulo en
simulación. Distinguiremos pues, HDL para síntesis y HDL para modelado y
simulación. El primero es un subconjunto del segundo.
6. Lenguajes HDL
Existen un buen número de lenguajes HDL’s la mayoría de ellos con un
propósito limitado. Los más conocidos son:
• ABEL: Lenguaje promovido por XILINX para la introducción de módulos
de carácter específico, como máquinas de estados finitos.
• AHDL: Lenguaje promovido por ALTERA para facilitar la introducción de
diseños en sus FPGA’s.
• EDIF, XNF, ...: No son lenguajes propiamente dichos, en el sentido de no
ser fácilmente comprensibles por el usuario, pero se utilizan cómo medios
para transferir netlist entre herramientas.
6.1 VHDL
El VHDL fue desarrollado por la Departamento de Defensa de los Estados Unidos
para modelar y simular sistemas. Emula el paralelismo mediante eventos y utiliza como
modelo de formulación el ADA. El Instituto para la Ingeniería Eléctrica y Electrónica
(IEEE) ha formulado una norma que regula el VHDL: la norma IEEE 1076-1987.
6.2 Verilog
Es un lenguaje HDL propiedad de CADENCE Design Systems, fabricante de
software para diseño de circuitos integrados, de la misma manera emula el paralelismo
mediante eventos.
10
6.3 VHDL vs Verilog
Llevamos casi un década debatiendo cuál de los dos lenguajes es el más
apropiado para diseñar. La comunidad científica ha llegado a la conclusión de que no
existe una razón de peso para claramente decantarse por uno o por otro. Por un lado,
Verilog nace ante la necesidad de mejorar un flujo de diseño y por tanto es un lenguaje
que cubre todas las necesidades de diseño y simulación. El resultado es un lenguaje
simple y flexible que es fácil de aprender aunque tiene ciertas limitaciones. Por otro,
VHDL nace como un lenguaje que resuelve las necesidades actuales de diseño, y otras
futuras, es decir con mayor amplitud de miras. El resultado es un lenguaje más rígido y
con una sistematización en el código que lo hace más legible, aunque las reglas de
diseño obligan a proceder de forma, a veces poco ágil. Ante la duda de cuál de ellos
enseñar en este curso la respuesta está en la propia comunidad científica: lo ideal es
conocer ambos. En este sentido se coincide que el conocedor de Verilog le cuesta
aprender VHDL, sin embargo el proceso contrario es en general, mas aceptado. El
programador de VHDL aprende Verilog en poco tiempo La experiencia de los autores
de estos capítulos es precisamente esa, y que conceptualmente es más didáctico el
VHDL.
6.4 Nuevos lenguajes de alto nivel
A partir de los años 2000 han nacido nuevos lenguajes de alto nivel cuya sintaxis y
método de codificación son idénticos a los lenguajes informáticos más usuales en
ingeniería como el C ó C++. Estos lenguajes tienen como objeto facilitar la traslación de
los lenguajes de diseño y pruebas de estudio a la descripción hardware. Los más
conocidos son Handel-C de Celóxica y System C.
7. Síntesis de Circuitos.
Hemos mencionado repetidamente la palabra síntesis como un proceso fundamental
en la generación del circuito. En el año 1987 A. Sangiovanni-Vincetelli, uno de los
mayores contribuidores al CAD/CAE para circuitos VLSI definía síntesis como
“generar el circuito desde una descripción algorítmica, funcional o de comportamiento
del circuito, una descripción de la tecnología y una descripción de las condiciones de
diseño en una función de costes. El resultado ha de obtenerse en un tiempo razonable de
computación y con la calidad similar o mejor a la que el razonamiento humano podría
obtener”. La síntesis completa de un circuito requiere tres niveles:
1. Síntesis modular o particionamiento razonado: es el análisis a nivel de diseño.
Esta labor se la hemos dejado al diseñador.
2. Síntesis lógica, combinacional, secuencial, algorítmica y de comportamiento.
3. Síntesis física. Es decir, generación automática del layout, de la tarjeta PCB, de
la programación de la FPGA,...
El tema que nos ocupa se centra fundamentalmente en el punto segundo. La
naturaleza del problema estriba en que no existe una única formulación para una misma
función lógica, es decir, una misma función puede adoptar muchas expresiones, y su
implementación y comportamiento físico es diferente en cada caso. En particular existen
dos casos especialmente interesantes: aquel en que el área ocupada por la función es
mínima y aquel en que el número de etapas o niveles de la función es mínimo, ya que
hay una relación directa entre el número de etapas y el retardo en la propagación de las
señales. Debido a que son dos factores normalmente contrapuestos, en cada caso ha de
evaluarse la formulación más adecuada o bien una situación intermedia, solución que
11
está definida por las condiciones de diseño. En adelante síntesis será un sinónimo de
síntesis lógica.
Durante los años 80 y 90 las investigaciones en materia de síntesis se han
centrado en desarrollar algoritmos que resuelven de una manera eficiente y óptima
diferentes estructuras típicas de los circuitos digitales. Podemos identificar:
• Síntesis a dos niveles y síntesis multinivel. Optimización de funciones
combinacionales.
• Síntesis de módulos aritméticos. Generación de estructuras regulares que
permitan generar circuitos digitales aritméticos de cualquier dimensión.
• Síntesis de circuitos secuenciales: FSM’s, Memorias, Contadores,
Registros,...
La realidad es que se han desarrollado algoritmos específicos para cada
estructura y por tanto estrategias diferentes para cada módulo a implementar por tanto
en el proceso de síntesis lógica ha de haber una etapa previa de identificación del
módulo en cuestión. Un programa de síntesis es una recopilación de programas
específicos de optimización, aplicados a cada estructura digital, sucedido de una
optimización combinacional global. De ahí que en el punto 5 de este capítulo descrito la
necesidad de obtener un modelo de referencia como paso previo a la síntesis del
circuito.
Comercialmente existen varios programas de síntesis en el mercado que merecen
mencionarse:
• SYNOPSYS, programa de carácter general que permite realizar síntesis ante
multitud de lenguajes y tecnologías. Funciona sobre plataforma UNIX.
• Leapfrog, de CADENCE para VHDL
• VerilogXL, de CADENCE para Verilog.
• Leonardo de MENTOR GRAPHIC’s, multilenguaje en plataforma PC
• FPGA-EXPRESS, de Synopsys para PC y centrado en FPGA’s.
12
CAPÍTULO II. ESTRUCTURA DE UN BLOQUE FUNCIONAL EN VHDL
1. Cuestiones previas
Una vez introducida la necesidad de diseñar utilizando lenguajes HDL y todo lo que
ello significa nos decantamos por presentar el VHDL como lenguaje de diseño.
Existen varias cuestiones previas que hay comentar antes de iniciar la exposición formal
del lenguaje.
Primeramente, el VHDL es un lenguaje en el que se define el sentido del flujo de las
señales, es decir, una señal es de entrada y/o salida definida en el código, no por la
evolución de la señal en si misma. La importancia de este comentario radica en que el
nivel de descripción más bajo que podemos alcanzar en VHDL es el nivel de puerta
lógica, y no de transistor. Por ejemplo, para representar un switch en VHDL se ha de
incluir una subrutina de cerca de 750 líneas de código, inefienciente a todas luces. En
Verilog esto esta bien resuelto por comando switch.
En segundo lugar, el VHDL no es sensible a mayúsculas y minúsculas, por tanto la
señal PEPE es igual a la señal PePe y a su vez a la señal pepe.
Los comentarios son solamente de línea y van marcados por dos signos menos:
--Esto es un comentario en VHDL
El lenguaje VHDL está dotado de muy pocos recursos inicialmente, de pocas
funciones y tipos de variables. Para darle mayor versatilidad es preciso utilizar librerías
estándar que le dotan de mucha mayor potencia para representar comportamientos
digitales. Dejaremos a capítulos posteriores la formalización de esta idea.
El mecanismo para realizar la síntesis de un diseño descrito en VHDL se realiza,
de modo estándar utilizando una librería de compilación donde se ubican los modelos
de referencia, que físicamente es un subdirectorio en el directorio de diseño. Esta
librería de modelos, por defecto se suele llamar WORK. Ampliaremos esta idea en el
apartado referido a librerías.
2. Estructura de un diseño VHDL
Una de las grandes aportaciones de los lenguajes HDL, como se expuso en el
capítulo 1, es la posibilidad de organizar jerárquicamente los diseños, de tal manera que
cada elemento, junto con los elementos de inferior nivel jerárquico, es en sí mismo un
diseño autocontenido. En consecuencia cada unidad en la jerarquía tiene entidad como
circuito, con entradas, salidas y funciones.
La unidad jerárquica en cuestión es designada por la palabra reservada entity o
entidad. La entidad tiene asociado un nombre identificador usualmente relativo a la
función que realiza. Cada vez que hagamos uso de este circuito utilizaremos el nombre
asociado. Asimismo está definida por señales de enlace con el exterior y una
arquitectura funcional.
13
El VHDL ha previsto la posibilidad de modelar diferentes arquitecturas para una
misma entidad de ahí que haya que asignar nombres tanto a la entidad como a la
arquitectura. Asimismo dispone de un mecanismo para poder acceder de forma
automática a las diferentes arquitecturas de una misma entidad desde una entidad de
orden jerárquico superior. Es el mecanismo de la configuración o configuration
2.1 Código general de una entidad VHDL
El código de una entidad se presenta en el siguiente esquema:
--Zona de declaración de librerías
LIBRARY nombre_librería;
USE paquete_funciones_librería.all;
--Cabecera de la entidad
ENTITY nombre_entity IS
GENERIC(..........);
PORT(................);
END nombre_entity;
--Cuerpo de la entidad
ARCHITECTURE nombre_architecture OF nombre_entity IS
--Declaración de sub-entidades (componentes) y señales internas
BEGIN
--Descripción de la funcionalidad
END nombre_architecture;
--Enlace con las arquitecturas de otras entidades
CONFIGURATION nombre_configuracion OF nombre_entidad IS
FOR nombre_arquitectura
--Cuerpo de la configuración
END nombre_configuracion;
El desarrollo de este tema consiste en explicar cada uno de los elementos expuestos en
el esquema anterior.
3. La sección ENTITY
Identifica la unidad jerárquica de forma unívoca. No pueden existir dos
entidades del mismo nombre en la jerarquía definidas de forma diferente. Junto con la
palabra ENTITY hay asociados dos campos:
• GENERIC: utilizado para pasar parámetros a la entity
• PORT: utilizado para definir las señales que relacionan la entity con el resto del
diseño.
Veamos un ejemplo concreto:
ENTITY contador IS
GENERIC(N:integer:=10);
PORT(
CLK: in BIT;
RST: in BIT;
ENABLE: in BIT;
COUNT: out BIT_VECTOR(N-1 DOWNTO 0);
SATUR: out BIT
);
END contador;
El código representa una descripción de las entradas y salidas de un contador de
tamaño 10. La gran ventaja es que es posible redefinir el parámetro N tantas veces como
queramos y extender el contador a la longitud que convenga en cada momento sin
14
modificar el código. Cada vez que se utiliza la entidad como parte del diseño se han de
determinar los parámetros que definitivamente definen el diseño. Sin embargo hemos
dado un valor por defecto, el valor 10 al tamaño del contador. El motivo es doble. Por
un lado el valor por defecto se utilizará en caso de no redefinición del parámetro, pero la
más importante es que este parámetro tiene la misión de dar coherencia al modelo del
circuito, es decir, un contador de tamaño N no tiene sentido como circuito, sin embargo,
al dar un valor sí queda bien definido y a la hora de generar su correspondiente modelo
hardware de referencia es posible generar la arquitectura. Pueden definirse tantos
parámetros como se desee. De los tipos posibles de parámetros hablaremos en el
capítulo siguiente. La sintaxis seguida es:
Nombre_parámetro : tipo_parámetro := valor_por_defecto;
La cláusula PORT indica las señales que interrelacionan la entity con el resto del
diseño. La sintaxis es:
Nombre_señal : dirección tipo_de_señal;
El nombre de la señal identifica unívocamente la señal en cuestión. No puede
haber otra señal dentro de la arquitectura con el nombre de la señal. El campo
“dirección” de la señal indica el sentido del flujo de la misma. Este campo puede tomar
los siguientes valores:
IN para indicar que la señal es una entrada
OUT para indicar que la señal es una salida
INOUT para indicar que la señal es una entrada o salida dependiendo de cada instante.
BUFFER tipo extendido de salida.
LINKAGE es un tipo restringido de salida que está en desuso.
Una señal IN recibe sus valores desde el exterior de la entidad. Por tanto, no puede ser
reasignada en el interior de la entidad, es decir no puede aparecer a la izquierda de una
asignación en la arquitectura:
Señal_in<=A; --Error
A<=Señal_in; --Correcto
Una señal OUT genera valores al exterior de la entidad. No puede ser asignada a
ninguna otra señal en la arquitectura:
A<=Señal_out; --Error
Señal_out<=A; --Correcto
Una señal INOUT puede ser asignada en ambos sentidos y es responsabilidad del
diseñador determinar en que condiciones de la función lógica descrita la señal puede ser
IN o OUT.
El lector podría pensar que para evitar errores lo más sencillo sería describir todas las
señales como INOUT. Sin embargo esto sería atentar contra la naturaleza de los HDL,
que es describir de la forma más explícita y precisa posible un circuito. De la otra
manera un mal estilo de diseño nos llevaría a una mala descripción del circuito y
seguramente a otro tipo de errores.
Una señal BUFFER es señal de salida asignable a otra señal en la arquitectura, pero no
puede ser forzada desde el exterior, dicho de otra manera, no es en ningún momento una
15
entrada. Se utilizan fundamentalmente para describir señales que realimentan dentro de
la entidad y simultáneamente salen al exterior. Un caso típico es un registro síncrono.
No entraremos en detalles acerca de los tipos de datos, cuestión que se abordará
en el capítulo correspondiente.
4. La sección ARCHITECTURE
La arquitectura de la entidad es la descripción de la funcionalidad. Consta de dos partes:
La parte declarativa, donde se especifican los elementos de tipo estructural que van a
componer la arquitectura, es decir:
• Señales internas. Son enlaces entre los diferentes elementos que definen la
arquitectura.
• Entidades de orden inferior en la jerarquía, llamadas componentes o
component.
La parte descriptiva, donde se define la funcionalidad que concretamente se le asigna a
la arquitectura. De las órdenes comandos y funciones que se pueden incluir en una
arquitectura hablaremos más adelante. Por ahora profundizaremos en la cuestión de la
jerarquía de un diseño y cómo se especifica en una arquitectura.
4.1 Diseño jerárquico (estructural) en una arquitectura.
En primer lugar especificamos en la parte declarativa que componentes
(component) se utilizarán en la arquitectura. Hacemos referencia a otras entidades, a
sus señales de enlace y a los parámetros.
Luego se especifican las señales mediante la palabra signal, seguida de uno o varios
nombres y de un tipo según la sintaxis:
SIGNAL nombre1, nombre2, ... : Tipo de señal:= valor inicial;
Todas las señales especificadas pertenecen al mismo tipo. Como se puede apreciar
este es el mismo esquema empleado en la declaración de las señales de enlace de la
entidad, con la salvedad de omitir la direccionalidad de la señal. Asimismo, y
opcionalmente se puede asignar un valor inicial de la señal. Este valor suele utilizarse
en simulación y es omitido por los sintetizadores.
También se pueden declarar valores constantes:
CONSTANT nombre: Tipo de señal:= valor inicial;
Veamos un ejemplo concreto. Utilizando la declaración de la entidad superior
construiremos una arquitectura que llama dos veces al contador anterior. Supongamos
un contador doble:
ENTITY doble_contador IS
PORT(
clk,rst,enable1,clr: IN BIT;
salida: OUT BIT;
cuenta: OUT BIT_VECTOR(7 DOWNTO 0)
);
16
END doble_contador;
ARCHITECTURE estructura_mod OF mod IS
COMPONENT contador
GENERIC(N:integer:=10);
PORT(
CLK: in BIT;
RST: in BIT;
CLEAR: in BIT;
ENABLE: in BIT;
COUNT: out BIT_VECTOR(N-1 DOWNTO 0);
SATUR: out BIT
);
END COMPONENT;
SIGNAL enlace1, vlogico0: BIT;
SIGNAL cuenta1: BIT_VECTOR(7 DOWNTO 0);
BEGIN
vlogic0<=’0’;
cont1: contador
GENERIC MAP(N=>8)
PORT MAP(CLK=>clk, RST=>rst, CLEAR=> CLR, ENABLE=>enable1,
COUNT=>cuenta1, SATUR=>enlace1);
cont2: contador
GENERIC MAP(N=>12)
PORT MAP(CLK=>clk,RST=>rst, CLEAR=>vlogic0, ENABLE=>enlace1,
COUNT=>OPEN,SATUR=>salida);
END estructura_mod;
El ejemplo muestra la utilización de un mismo componente varias veces. Lo primero
que hay que haces notar es la presencia de una etiqueta que identifica cada uno de los
componentes de una manera unívoca. cont1 y cont2 son dos instancias de un mismo
componente tipo contador, y cada uno de ellos tiene un valor diferente del parámetro.
De ahora en adelante esta etiqueta identificará al componente. La sintaxis es simple:
nombre_instancia : nombre_entidad (o componente)
GENERIC MAP(...............)
PORT MAP(...................);
En segundo lugar debemos destacar la manera de referenciar señales internas a
la entidad. Tras la declaración de los componentes se declaran señales que son interiores
al diseño. En el ejemplo aparece la señal enlace1 como señal interna, común a ambos
bloques: en un caso enlace1 es de salida y en el otro, de entrada. Obviamente en la
declaración de la misma no existe direccionalidad, a diferencia de la señales de los
puertos de la entidad.
En tercer lugar conviene repasar la sintaxis y la puntuación. La etiqueta de
instancia va seguida del nombre de la entidad, en este caso el componente que se trata.
Las secciones generic y port son ahora generic map y port map, indicando las señales
que van a ser asociadas a sus entradas y salidas. En el caso de que hubiese
direccionalidad en las señales, es decir, que vengan declaradas desde la entidad (en el
ejemplo clk,rst,enable1, salida,...) la señal ha de conservar el tipo. Las cláusulas map no
van separadas por ‘;’.
17
Finalmente la manera de asociar señales que se propone es especificando qué señal
se asocia a qué entrada, mediante los caracteres ‘=>’ y separados por comas. Este
método se denomina pasar parámetros por referencia. En este sentido conviene hacer
algún comentario adicional. Es correcto pasar los parámetros por orden, es decir con la
sintaxis:
cont2: contador
GENERIC MAP(12)
PORT MAP(clk,rst,enlace1,OPEN,salida);
Si bien es más conciso, es también poco práctico, ya que un error obliga a estar
constantemente comparando con el componente y verificando una correcta asociación,
es decir, muy poco legible. La experiencia demuestra que siendo más explícito, si bien
se escribe más, se tarda en tener éxito mucho menos tiempo. En este caso es obligado
que aparezcan todos los parámetros.
La palabra reservada open.
La palabra reservada open se utiliza para dejar señales sin conectar. La omisión
de un parámetro es equivalente a la asociación de open.
Es posible asociar open a una entrada, que es tanto como decir que se deja sin
conectar, lo cual en la práctica no es nada aconsejable, ya que se deja libertad al proceso
de síntesis para que realice las operaciones sin nuestro control. En la práctica se debe
asociar un valor lógico concreto a la entrada para evitar resultados impredecibles. En
VHDL no se admite que en la referencia a un componente se asocie un valor lógico
directamente, por lo que hay que declarar una señal auxiliar.
constant vlogic0: std_logic :=’0’; --Este valor no se puede asignar
signal vlogic0: std_logic; --Este es el modo de asignación
….
vlogic0<=’0’;
….
Port map (clr
La arquitectura presentada es un ejemplo de arquitectura estructural, ya que está
compuesta únicamente de componentes y sus enlaces. Si hacemos referencia al
paralelismo entre ellos, este caso es un ejemplo claro que no existe precedencia en el
comportamiento de un contador u otro, ya que ambos se activan de igual manera,
aunque el segundo dependa del primero.
4.2 Diseño de comportamiento en una arquitectura.
En el nivel más bajo del diseño ha de haber una información acerca de la
funcionalidad del propio diseño. No se hacen referencia a otros componentes, sino que
en la propia arquitectura se definen las operaciones que se realizan sobre las señales. Sin
entrar excesivamente en detalles, ya que serán motivos de capítulos posteriores.
18
4.3 Diseño con primitivas.
Las primitivas son las unidades funcionales elementales que describen el diseño
después de realizar la síntesis. Están asociadas a la tecnología elegida y están
representadas como componentes en una librería que el fabricante de circuitos
integrados proporciona. Es posible realizar una descripción de un diseño a partir de una
descripción mediante primitivas, lo que equivale a realizar un esquemático escrito.
5. La sección CONFIGURATION
La sección CONFIGURATION responde a un complejo mecanismo de selección
entre las diferentes arquitecturas posibles de una misma entidad utilizada en un nivel
jerárquico superior. Una configuración está asociada a una entidad y una arquitectura.
Por tanto, mediante la selección de la configuración determinaremos qué arquitectura es
seleccionada para esa entidad. Podemos definir tantas configuraciones como sean
necesarias, ya que a ella se le asocia un nombre.
La sintaxis es:
CONFIGURATION nombre_configuracion OF nombre_entidad IS
FOR nombre_arquitectura
FOR nombre_instancia :
nombre_entidad USE CONFIGURATION WORK.nombre_configuracion;
END FOR;
.....................
END FOR;
END nombre_configuracion;
Es en el nivel jerárquico superior cuando se particulariza qué configuración se
desea para determinada entidad. Veamos un ejemplo:
--Entidad inferior
ENTITY mi_contador IS
PORT(
clk,rst,enable: in std_logic;
cuenta: out std_logic_vector(7 downto 0)
);
END contador;
--definimos dos arquitecturas diferentes para la misma entidad
ARCHITECTURE comport OF contador IS
BEGIN
....................
END comport;
ARCHITECTURE estruct OF contador IS
BEGIN
....................
END estruct;
--asociamos una configuración a cada una de las arquitecturas
CONFIGURATION comport_conf OF contador IS
FOR comport
END FOR;
END comport_conf;
CONFIGURATION estruct_conf OF contador IS
FOR estruct
END FOR;
19
END estruct_conf;
-- configuración de la entidad superior que referencia el componente
CONFIGURATION estruct_top OF top IS
FOR estruct
FOR cont1: contador USE CONFIGURATION WORK.estruct_conf;
END FOR;
FOR cont2: contador USE CONFIGURATION WORK.comport_conf;
END FOR;
................
END FOR;
END estruct_conf;
En este ejemplo hemos utilizado dos arquitecturas diferentes del mismo componente.
En el caso de que no exista la necesidad de variar las configuraciones se puede resumir
en la cláusula ALL:
-- configuración de la entidad superior que referencia el componente
CONFIGURATION estruct_top OF top IS
FOR estruct
FOR ALL: contador USE CONFIGURATION WORK.estruct_conf;
END FOR;
.................
END FOR;
END estruct_conf;
5.1 Estilo par entidad-arquitectura
Existe una sintaxis alternativa que permite una asociación más explícita:
CONFIGURATION estruct_top OF top IS
FOR estruct
FOR cont1: contador USE ENTITY WORK.contador(estruct);
END FOR;
FOR OTHERS: contador USE ENTITY WORK.contador(comport);
END FOR;
.................
END FOR;
END estruct_conf;
En este caso hemos introducido también la cláusula OTHERS para identificar a
aquello que no se ha hecho referencia previamente. Este primer ejemplo de una palabra
que es muy utilizada en VHDL.
En la práctica este potente mecanismo es poco utilizado, dado que no se ofrecen
muchas situaciones de varias arquitecturas para una misma entidad. Si solamente existe
una única arquitectura en todos y cada uno de los componentes del diseño, la
configuración será única y por tanto se omite la sección de configuraciones.
6. La sección LIBRARY
Tiene un especial interés el dedicar un párrafo completo a las librerías en VHDL,
dado que en la práctica todas las extensiones del lenguaje se realizan utilizando este
potentísimo mecanismo.
Mediante bibliotecas podemos definir componentes, funciones y procedimientos, es
decir, unidades del lenguaje que pueden ser útiles en múltiples aplicaciones, siendo un
20
elemento de reutilización del lenguaje. De esta manera, un componente de una librería
ya analizada y sintetizada puede ser llamado en un código sin necesidad de tener que
volver a declararlo como componente. Las librería se estructuran en paquetes
(packages) que permiten una fácil clasificación de los elementos que las componen.
La declaración de una librería sigue la siguiente sintaxis:
library MiLibreria;
use MiLibreria.Paquete1.all;
use MiLibreria.Paquete2.all; --Enmascara las funciones de Paquete1
De esta manera quedan accesibles todos (all) los elementos del Paquete1 y
Paquete2 de la librería MiLibreria. En el capítulo seis nos dedicaremos en detalle a
definir, declarar y construir nuestras librerías. Sin embargo nos detendremos en explicar
algunos aspectos importantes de las librerías más utilizadas. Solo dos palabras para
comentar que las funciones VHDL disponen del mecanismo de sobrecarga en sus
llamadas, de forma que no solo se atiende a la coincidencia del nombre de la función en
la llamada, sino que además se verifica la coicidencia de los tipos de argumento de las
mismas.
Finalmente, la presencia de funciones con igual nombre y tipo de argumentos en
Paquete1 y Paquete2 hace que las funciones del primero queden enmascaradas por las
del segundo, de ahí que si se desea utilizar parcialmente una de ellas, bien debemos
cuidar el orden de declaración o bien sustituir la palabra all por las funciones que nos
interesen.
6.1 La necesidad de la librería std_logic_1164
El VHDL posee un limitado número de tipos y funciones que resultan insuficientes
a la hora de describir completamente el comportamiento de un circuito digital. Por tanto
se hace preciso que la extensión del lenguaje contemple un número amplio de
situaciones. En particular, los tipo de datos que maneja el VHDL se reflejan en la
siguiente tabla:
•
•
•
•
•
•
•
•
•
•
•
•
BIT , {0,1}
BOOLEAN, {false, true}
CHARACTER, {la tabla ASCII desde 0 a 127}
INTEGER, {-2147483647, 2147483647}
NATURAL, {0, 2147483647}
POSITIVE, {1, 2147483647}
STRING, array {POSITIVE range <>} de CHARACTER
BIT_VECTOR, array {NATURAL range <>}de BIT
Tipos físicos, como TIME
REAL, {-1E38, 1E28}
POINTER, para accesos indirectos a datos.
FILE, para accesos a ficheros y pilas de datos del disco duro.
Ni que decir tiene que muchos de ellos corresponden a estructuras del lenguaje
que no son sintetizables. De echo, durante la síntesis de bloques VHDL casi nunca
se utilizan de manera explícita y completa estos tipos de datos. Por tanto el VHDL
21
de forma sistemática ha de extenderse mediante tipo de datos que presentan un
campo de variabilidad mucho más cercano al funcionamiento normal de un circuito.
Se trata del paquete std_logic_1164 de la librería IEEE. Normalmente esta librería
aparece dando sentido a un nuevo tipo de datos, el std_logic, que es un tipo
enumerado. Los detalles acerca de este tipo de datos se exponen en el capítulo
cuarto de estos apuntes.
La cabecera de librerías estándar es típicamente:
library IEEE;
use IEEE.std_logic_1164.all;
En esta librería se introducen los nuevos operadores relacionales asociados al
nuevo tipo de datos.
6.2 Librerías aritméticas
El VHDL no tiene definidos de modo estándar operaciones aritméticas que
permitan el uso de operadores. Es preciso, pues, la inclusión de librerías que faciliten la
inclusión de operadores de tipo aritmético. La librería estándar para este caso es la
std_logic_arith. En el capítulo dedicado a comentar la utilización de las funciones
aritméticas desarrollaremos las referencias a esta librería. Se estudiará a fondo en los
capítulos cuatro y cinco.
22
CAPÍTULO III. DESCRIPCIÓN DE LA FUNCIONALIDAD
1. Concepto de concurrencia.
Un circuito electrónico es, por naturaleza paralelo, es decir, no existe un
mecanismo inherente a la tecnología soporte que establezca precedencias en la . Esto
significa que la descripción del mismo ha de preservar esta propiedad. Cuando se
introducen sentencias o bloques (que más adelante llamaremos procesos), todos ellos
son concurrentes, es decir, no existe precedencia entre unos u otros y no importa el
orden en que se escriban (en lo que afecta a su funcionalidad, que no es lo mismo que su
legibilidad e interpretación). Por ejemplo:
A<=B and C;
D<=A and F;
es equivalente a:
D<=A and F;
A<=B and C;
sin embargo existen gran cantidad de situaciones en las que es muy fácil
describir un comportamiento de un circuito de modo secuencial.
Nos obstante , y con el ánimo de ser formal existen en VHDL cinco tipos de
elementos concurrentes:
• Bloques (block). Grupos de sentencias concurrentes.
• Instancias de componentes. En diseños estructurales, cada vez que se
produce la utilización de un componente se realiza de modo concurrente.
• Sentencias generate. Generación de copias de bloques de hardware.
• Llamadas a procedimientos y funciones (procedure y function). Los
procedimientos son algoritmos combinacionales que computan y asignan
valores a señales. Suelen formar parte de librerías.
• Declaración y definición de procesos (process). Definen algoritmos
secuenciales que leen valores de señales y asignan valores a señales.
• Asignaciones de señales computadas a través de expresiones booleanas o
aritméticas.
Resulta, pues muy útil la presencia de mecanismos que permitan introducir
código secuencial. El más común es el proceso o process, que detallaremos en el
siguiente apartado. Un proceso es un bloque que contiene código secuencial pero
externamente se contempla como un bloque concurrente, y tiene el mismo tratamiento
que el ejemplo anterior. Las instancias de componentes se han tratado en el capítulo
anterior y los procedimientos y las funciones se explicarán en el capítulo seis cuando se
explican las librerías, ya que es en ellas donde más se utilizan.
2. Estructura de un proceso
Para comprender el funcionamiento de un proceso conviene introducir una doble
perspectiva del mismo: la simulación y la síntesis. Desde el punto de vista de
23
simulación el proceso contiene una secuencia de transformaciones sobre las señales que
se producen una vez activado el mismo. Desde el punto de vista de síntesis conviene no
olvidar que un proceso representa una porción de un circuito y que es preciso asociar
una imagen hardware al conjunto de sentencias que lo componen.
El mecanismo del proceso se entiende como una manera fácil de transferir
algoritmos ensayados en lenguajes de alto nivel secuenciales a VHDL. Esta idea
condiciona enormemente su sintaxis y sus posibilidades.
2.1 Elementos de un proceso.
El siguiente listado recoge todos los elementos de un proceso
etiqueta_proceso: PROCESS(lista de sensibilidad)
VARIABLE ...
BEGIN
--secuencia de ordenes
END PROCESS etiqueta_proceso;
Los procesos se introducen en la zona de arquitectura y no existe restricción alguna
acerca de su número y extensión.
2.2 Lista de sensibilidad.
La lista de sensibilidad es un artificio para realizar computacionalmente el
paralelismo, principalmente en simulación, aunque también es útil en síntesis. La lista
de sensibilidad es un conjunto de señales que activan el proceso, es decir, mediante un
cambio en un cualquiera de ellas, el proceso es evaluado completamente. Esto permite
resolver el problema de la concurrencia mencionado, ya que si una sentencia
concurrente es evaluada cuando los argumentos varían, igualmente le ocurre a un
proceso.
Existe varias cuestiones que conviene aclarar:
• La lista de sensibilidad debe contener todas las señales que afecten a la
evaluación del proceso, es decir, señales que usualmente se ubican a la
derecha en una sentencia de asignación.
• La omisión de una señal en la lista de sensibilidad afecta necesariamente a la
morfología del circuito que representa.
• Si no existe lista de sensibilidad el proceso se denomina infinito y se ejecuta
constantemente. Para su control se precisa el uso de sentencias tipo wait que
son típicas de construcciones no sintetizables y se estudiarán en el capítulo
correspondiente.
• Si una señal que se computa a la izquierda de una asignación dentro del
proceso aparece en la lista de sensibilidad, este se evaluará de nuevo, y se
producirá sucesivamente hasta que el valor de la señal se estabilice.
Si en la lista de sensibilidad se omite una señal, hemos dicho que afecta decididamente a
la imagen hardware que se representa. Imaginamos el siguiente ejemplo:
pr1: PROCESS( a ) –omitimos la señal b
BEGIN
c<=a and b;
24
END PROCESS pr1;
Hemos omitido la señal b. Cuando a varía el proceso se evaluará realizando la
operación and de a y b, con el valor que b tenga en ese instante. Sin embargo, cuando b
varía c no se actualizará, ya que no está en la lista de sensibilidad. De esta manera, el
circuito que produce el valor de c ha de almacenar el valor de b cuando se produzca una
variación en a.
Todos los programas de síntesis producen un aviso de ausencia de la señal de la lista de
sensibilidad, que es preciso eliminar.
2.3 Cláusula variable
El la implementación secuencial se suele hacer uso de variables temporales que
no trascienden fuera del proceso. Su uso está limitado a asignaciones en su interior. Este
recurso del lenguaje se le conoce como variable. El mecanismo de asignación es igual
que el de una señal, con la salvedad de que en la asignación se utilizan los símbolos ‘:=’
en lugar del ‘<=’. Por ejemplo:
asig: PROCESS(b,c)
VARIABLE a: std_logic;
BEGIN
a:= b and c;
d<= a xor b;
END PROCESS asig;
La variable a aparece en una situación transitoria, y tiene una misión aclaratoria, es
decir, un proceso se activa con señales y resuelve los valores de señales. Existen,
además, otras diferencias cualitativas que describiremos en el apartado siguiente.
2.4 Asignaciones dentro de un proceso
En el cuerpo de un proceso se admiten asignaciones simples. La principal diferencia en
el tratamiento estriba en que el proceso se evalúa en un instante de tiempo cero, es decir,
que todas las asignaciones y condiciones se producen sin retraso. De la misma manera
las asignaciones a señales (que no a variables) dentro de un proceso no se hacen
efectivas hasta que el proceso no se concluye. Por ejemplo:
ARCHITECTURE comport OF ejemplo
SIGNAL q : INTEGER RANGE 0 TO 3;
BEGIN
ej: PROCESS(A,B,..)
BEGIN
q<=0;
IF(A=’1’) THEN q<=q+1; END IF;
IF(B=’1’) THEN q<=q+2; END IF;
CASE q =>
WHEN 0 =>
....
END CASE;
END PROCESS ej;
END comport;
El valor de q no se actualiza hasta que el proceso ha concluido. Esto significa que en la
evaluación realizada en la estructura CASE el valor de q es el que tuviese antes de la
25
evaluación del proceso. Ni siquiera la asignación incondicional afectaría a dicha
evaluación. Para realizar una evaluación secuencial tal y como se realiza en un
programa desarrollado para una CPU es preciso utilizar variables en vez de señales, es
decir:
ARCHITECTURE behav OF ejemplo
BEGIN
ej: PROCESS(A,B,..)
VARIABLE q : INTEGER RANGE 0 TO 3;
BEGIN
q:=0;
IF(A=’1’) THEN q:=q+1; END IF;
IF(B=’1’) THEN q:=q+2; END IF;
CASE q =>
WHEN 0 =>
....
END CASE;
END PROCESS ej;
END behav;
Mediante esta modificación realizamos la asignación deseada y la funcionalidad que
resulta es la requerida.
2.5 Sentencias propias de un proceso
Dentro de un proceso existen un conjunto sentencias que son propias de su estructura.
Fuera de él carecen de significado. Son condiciones no concurrentes:
Sentencia IF THEN ELSIF ELSE
La resolución de una condición se resuelve mediante la sentencia IF. Su sintaxis es:
IF condición 1THEN
Consecuencia 1;
ELSIF condición 2 THEN
Consecuencia 2;
ELSIF ... THEN
...
ELSE
Consecuencia por defecto;
END IF;
La condición puede ser lógica o aritmética. Merece especial atención al concepto
inherente de jerarquía que introduce la sentencia IF. La condición 1 es previa a la
condición 2, que nunca será evaluada en caso de cumplimiento de la primera. Algo
parecido ocurre con la siguiente estructura, también válida:
IF condición 1THEN
IF condición 2 THEN
Consecuencia 2.1;
ELSE
26
Consecuencia 2.2;
END IF;
ELSE
Consecuencia por defecto;
END IF;
La diferencia entre ambas estructuras estriba en que en la segunda las
consecuencias 2.1 y 2.2 se validan mediante un circuito que comprueba Condición 1 y
Condición 2 encadenadas. En la primera de las estructuras la Consecuencia 1 tendría un
camino crítico menor.
Las condiciones en una sentencia IF se evalúan a través de expresiones
booleanas que devuelven los valores ‘verdadero’ o ‘falso’. En la siguiente tabla se
resumen los tipos de expresiones:
Tipo
condición
=
/=
<,<=,>,>=
de
Ejemplo
C
If (a = b) then
Igualdad.
If (a /= b) then
Desigualdad
If (a<b) then
Menor, menor o igual, mayor, mayor
o igual
Inferencia de elementos de memoria
Las sentencias condicionales tienen una particularidad que las hacen de especial
interés en la síntesis de hardware secuencial. Sea el ejemplo siguiente:
sec: PROCESS(ena,d)
BEGIN
IF (ena=’1’) THEN
q<=d;
qz<=NOT d;
END IF;
END PROCESS sec;
La estructura inferida hace que q tome el valor d cuando ena tiene el valor ‘1’.
Pero, ¿qué ocurre cuando ena vale ‘0’? Si cambia d, q permanece con el valor de d
cuando ena valía ‘1’. Existe un dispositivo hardware que permite realizar la
funcionalidad expuesta, consistente en un elemento de memoria activo por nivel o latch.
Del ejemplo se infiere un latch para la señal q y otro para la qz, que en un posterior
proceso de optimización sería agrupado en uno solo con la negación de q.
El modelo de inferencia un latch se reproduce continuamente durante el proceso
de diseño, en situaciones deseadas y en situaciones no deseadas. Un ejemplo típico de
situación deseada es el biestable, donde la señal ena es el reloj, y por tanto se ha de
utilizar un mecanismo para especificar el flanco del reloj, mecanismo que se expondrá
en el capítulo cuarto. El latch rara vez se utiliza en diseño síncrono y suele ser una
fuente de problemas. Normalmente cuando los programas de síntesis detectan una
estructura de un latch (no deseada, el biestable es un elemento siempre deseado) suelen
proporcionar un aviso de que se infiere. En general es una estructura condicional que no
27
tiene solución hardware para todas las posibles situaciones. Por tanto es recomendable
que en las estructuras que dependen de la evaluación de una condición antecedente
dispongan al menos de una valor por defecto para los consecuentes.
Sentencia CASE WHEN
Una selección en función del valor que toma una señal se construye mediante la
selección
CASE objeto IS
WHEN caso1 =>
Código1;
WHEN caso2 =>
Código2;
WHEN caso3 | caso4 =>
Código34;
WHEN OTHERS =>
Código para el resto de los casos;
END CASE;
En los casos en que las dos formas son posibles, es recomendable el uso de
CASE antes que IF/ELSIF dado que el IF introduce un elemento de prioridad que puede
resultar menos eficiente (más área).
WHEN OTHERS representa la condición por defecto. No puede haber dos
valores de selección iguales y la cláusula others debe aparecer si no se cubren todos los
posibles valores de selección.
Es importante que exista una asignación para todas las salidas en cada uno de los
casos ya que de esta forma evitamos la formación de latches indeseables.
Bucles dentro de procesos
VHDL permite la construcción de bucles para reducir el tamaño de código repetitivo.
Estas sentencias han de ir en dentro de un proceso.
Etiqueta: FOR parámetro IN lista_de_valores LOOP
Código;
END LOOP Etiqueta;
Etiqueta: WHILE condición LOOP
Código;
END LOOP Etiqueta;
Cuando el objeto a evaluar es un índice, no es precisa su declaración previamente. En
ambos casos es posible interrumpir la evaluación normal con las instrucciones NEXT y
EXIT, cuando la condición se satisface. NEXT salta el resto del código secuencial
dentro del bucle y obliga a la evaluación de un nuevo índice. EXIT salta el resto del
código secuencial y obliga a la terminación del mismo, continuando con la secuencia
del proceso.
28
NEXT etiqueta WHEN condición; --saltar el resto del bucle
EXIT etiqueta WHEN condición; --salir del bucle
por ejemplo:
FOR i IN 0 TO 7 LOOP
NEXT WHEN i=6; -- Dejamos el bit 6 como estaba
Bus(i) <= ’0’;
END LOOP;
Otra alternativa es:
i:=0;
WHILE i < 8 LOOP
Bus(i) <= ’0’;
i:=i+1;
END LOOP;
3. Bloques de sentencias
Es una agrupación de sentencias concurrentes, es decir, no importa el orden en
que han sido escritas. La sintaxis es:
etiqueta: BLOCK
BEGIN
Sentencia Concurrente 1;
Sentencia Concurrente 2;
Sentencia Concurrente 2;
......
END BLOCK etiqueta;
Normalmente un bloque no crea un nuevo nivel de jerarquía. Su función es
puramente aclaratoria y en la práctica es poco utilizado.
4. Asignaciones concurrentes
Es posible realizar operaciones de tipo asignación concurrente equivalentes a las
que se han definido dentro de las estructuras secuenciales, en las que las restricciones a
la hora de realizar una inferencia son exactamente las mismas que en las mencionadas
estructuras, con la ventaja de que no es preciso la realización de una estructura del tipo
proceso.
NOTA: Realmente todos los elementos concurrentes son tratados dentro del complejo
tratamiento informático del lenguaje como procesos concurrentes.
4.1 Asignación simple
Es la sentencia concurrente más elemental y se realiza mediante el asignador
‘<=’. La condición para una correcta asignación es la igualdad en los tipos de las
señales que se asignan. Su sintaxis es:
etiqueta : Señal Destino <= Señal Origen;
29
Donde la señal destino puede ser una señal declarada previamente mediante
signal o bien una señal que se encuentra en el puerto (port) de la entidad (entity) como
salida (out) , entrada-salida (inout) ó buffer.
La señal origen puede ser una señal declarada previamente mediante signal o
bien una señal que se encuentra en el puerto (port) de la entidad (entity) como entrada
(in), entrada-salida (inout) ó buffer. Además la asignación simple permite la
evaluación de expresiones siguiendo una sintaxis igual:
etiqueta : Señal Destino <= Expresión Funcional;
La expresión funcional representa una función booleana o aritmética que es
combinación de señales de entrada o señales. La siguiente tabla representa las funciones
booleanas que se admiten en una asignación:
Operador
Ejemplo
Tipo
NOT
a <= NOT b;
Operador lógico
AND, NAND
a <= b AND c; Operadores lógicos
OR,NOR,XOR a <= b OR c;
Operadores lógicos
Asimismo se admiten operaciones aritméticas. Estas operaciones han de estar
previamente definidas en librerías. En el capítulo cuarto se tratan más en profundidad la
manera de trabajar con estas funciones. Por ahora nos limitaremos a exponer una
relación de las mismas:
Operador
Uso
Descripción
Multiplicación
*
a <= b * c;
**
a <= b ** 2; Exponencial
/
a <= b/c;
+
a <=b+c;
División (c ha de ser potencia
de 2)
Suma
-
a <=b-c;
Resta
a <= b MOD Calcula b en modulo c
c;
a <= b REM Calcula la resta entera de b / c
c;
MOD
REM
Es posible una asociación y una inferencia de jerarquía en las funciones
jerarquía mediante la utilización de paréntesis. Existe una prioridad en la definición de
las funciones. Se resume en la siguiente tabla:
Máxima:
**
*
ABS
/
NOT
MOD REM
30
Mínima
+
=
AND
(negación)
&
/=
<
<=
>
>=
OR
NAND NOR XOR
4.2 Asignación condicional
Es una expresión concurrente que realiza las mismas operaciones que la cláusula
IF dentro de un proceso. Su sintaxis es:
etiqueta: Señal Destino <= Expresión1 WHEN Condición ELSE Expresión2;
No existen diferencias cualitativas entre ambos tipos de sentencias. La forma IF
admite, de modo mucho más legible, más de una consecuencia y consecuencias mucho
más complejas que la forma concurrente WHEN.
De la misma forma es posible encadenar la forma WHEN:
etiqueta: Señal Destino <= Expresión1 WHEN Condición1 ELSE
Expresión2 WHEN Condición2 ELSE
Expresión3 WHEN Condición3 ELSE
..............
ExpresiónN WHEN CondiciónN ELSE Expresión;
4.3 Selección en forma concurrente
También existe una sentencia equivalente a case en forma concurrente. Su
sintaxis es:
etiqueta: WITH expresión_selección SELECT
Señal Destino <= Expresión1 WHEN Valor_Selección1,
Expresión2 WHEN Valor_Selección2,
....
ExpresiónN WHEN OTHERS;
No puede haber dos valores de selección iguales y la cláusula others debe
aparecer si no se cubren todos los posibles valores de selección.
5. La sentencia generate
Uno de los mecanismos de generación de hardware más potentes del VHDL es
el basado en la sentencia generate. Los detractores del Verilog utilizan esta sentencia
como argumento de valor, ya que en no existe en Verilog una forma equivalente.
5.1 Bucles utilizando generate
Mediante la sentencia generate podemos realizar cero, una o más copias de un
conjunto de sentencias concurrentes en función de un parámetro que recorre un rango de
valores. La sintaxis es como sigue:
etiqueta: FOR parámetro IN rango_de_valores GENERATE
{
Sentencias concurrentes asociadas al parámetro
}
31
END GENERATE etiqueta;
En este caso la etiqueta es obligatoria, ya que se permite anidar bucles generate. El
parámetro no se declara externamente. Es local al bucle. No es posible asignar un valor
al parámetro ni este puede ser asignado.
El rango de valores se recorre en forma creciente o decreciente:
entero_menor TO entero_mayor
entero_mayor DOWNTO entero_menor
El uso más corriente de la sentencia generate es la generación de copias de
componentes, procesos o bloques. El ejemplo representa la llamada múltiple a un
componente, y genera 8 instancias del mismo con valores diferentes de los parámetros:
COMPONENT MiComp
GENERIC(N: integer:=8);
PORT(X: in std_logic;
Y: out std_logic );
END COMPONENT;
....
SIGNAL A,B: bit_vector (7 downto 0);
....
Bucle_generate: FOR i IN 0 TO 7 GENERATE
copia: MiComp
GENERIC MAP (N=>2*i)
PORT MAP(X=>A(i), Y=>B(7-i));
END GENERATE Bucle_generate;
....
En el ejemplo hemos jugado con diferentes posibilidades del mecanismo
generate, con la idea de poner de manifiesto sus posibilidades. Hemos asignado a cada
componente un valor diferente del parámetro, en función del mismo. Asimismo hemos
realizado operaciones con el índice del vector B.
5.2 Generate condicionado.
Mediante la evaluación de una expresión booleana es posible construir un
componente:
Etiqueta: IF expresión GENERATE
{
Sentencias concurrentes
}
END GENERATE Etiqueta;
Es preciso destacar que esta forma de IF se presenta como sentencia concurrente
y la ausencia de una condición alternativa tipo ELSE ni ELSIF. Esta estructura tiene
mejor uso anidada dentro de otro bucle generate.
32
CAPÍTULO IV. TIPOS DE DATOS Y SEÑALES
En este capítulo vamos a presentar los tipos de datos más corrientes en VHDL y
planteamos todos conceptos básicos necesarios para poder manejar el lenguaje, dejando
a capítulos siguientes las particularizaciones sobre elementos más comunes. Haremos
un recorrido por todos los elementos típicos y la librería std_logic_1164 de gran
importancia en la generación de códigos sintetizables. Partimos de lo reseñado en el
apartado 6.1 del capítulo segundo de estos apuntes.
1. Definición de tipos de datos.
El VHDL admite la definición de nuevos tipos de datos de una forma muy
simple. Dentro de la parte declarativa es posible escribir de manera explícita todos los
posibles valores que puede tomar una señal de un determinado tipo. Se le conoce como
tipo enumerado:
TYPE nombre_tipo IS (Enum1,....,EnumN);
Donde Enum1 a EnumN puede ser un identificador (un nombre cualquiera) o
bien un carácter. Por ejemplo:
TYPE equipo IS (Sevilla, Málaga, Huelva, Betis, Córdoba, Jaén, Jerez)
....
SIGNAL ligaandaluza: equipo;
Realmente la señal ligaandaluza en un tipo enumerado porque realmente es un subtipo
de INTEGER donde Sevilla representa el valor 0, Málaga el valor 1, hasta Jerez el valor
7. De esta forma se obtiene una evaluación positiva de la condición:
Betis > Sevilla
Este mecanismo permite realizar operaciones con valores de las señales que están
representados de una forma clara por un estado, nombre o identificador. En el tema
dedicado a las máquinas de estados finitos se comprenderá su potencia y utilidad.
2. Tipo entero
El VHDL admite en su forma estándar el uso del tipo entero. Una señal
declarada como entero es un registro de 32 bits que recorre el rango desde -(231-1) hasta
+(231-1). El registro trata los valores negativos en complemento a 2. Es posible definir
un intervalo de enteros mediante la definición de un nuevo tipo:
TYPE nombre_tipo IS RANGE intervalo;
El intervalo debe estar contenido entre los valores anteriormente expuesto. Por
ejemplo:
TYPE puntos_liga IS RANGE 0 TO 144;
Todas las señales que sean definidas como puntos_liga podrán tomar valores
dentro de este intervalo. Internamente estas señales serán tratadas mediante registros de
8 bits.
33
2. Tipo matriz
Una matriz es un conjunto ordenados de elementos del mismo tipo. El acceso a
uno de esos elementos se realiza a través de un índice. Un tipo matriz se define:
TYPE matriz_nombre_tipo IS ARRAY (intervalo de enteros) OF nombre_tipo;
Por ejemplo:
TYPE clasificacion IS ARRAY (1 TO 7) OF equipo;
VHDL soporta de matrices multidimensionales, es decir, cuyos elementos
dependen del valor de uno o más parámetros. La mayoría de programas de síntesis no
contemplan esta posibilidad y se restringen al tratamiento de matrices unidimensionales.
No obstante a través de varias declaraciones de tipo unidimensional se pueden construir.
El valor del intervalo de enteros permite la definición de tipos restringidos, como
el del ejemplo, y no restringidos que pueden tomar valores en todo el rango de
variabilidad de los enteros. Su sintaxis es:
TYPE matriz_nombre_tipo IS ARRAY(Tipo_predefinido RANGE <>) OF nombre_tipo;
La ventaja de este mecanismo consiste el que dejamos a las herramientas de
síntesis VHDL tomar las decisiones acerca del intervalo de variabilidad final. Por
ejemplo:
TYPE Bit_Vector IS ARRAY (INTEGER RANGE <>) OF bit;
...
VARIABLE MiVector: Bit_Vector (-5 TO 5);
Siendo la variable la que define finalmente el intervalo en el momento de su
declaración.
2.1 Atributos de las matrices.
Los atributos son valores asociados a una señal o variable que proporcionan
información adicional, independientemente del valor que la señal o variable tenga en un
instante dado. Son de gran utilidad en la elaboración de código que depende
intensivamente de parámetros. En este apartado se exponen los atributos relacionados
con el aspecto matricial. En apartados posteriores se exponen otros atributos asociados
al comportamiento de las señales. Estos son los atributos matriciales definidos en
VHDL estándar:
• LEFT. Identifica el valor definido a la izquierda del intervalo de índices.
• RIGTH. Identifica el valor definido a la derecha del intervalo de índices.
• HIGH. Identifica el valor máximo del intervalo de índices.
• LOW. Identifica el valor mínimo del intervalo de índices.
• LENGTH. Identifica el valor total del intervalo de índices.
• RANGE. Copia el intervalo de índices
• REVERSE_RANGE. Representa el mismo intervalo de índices pero en
sentido inverso.
• La manera de acceder a los valores de los atributos es la siguiente:
34
nombre_de_señal’NOMBRE_ATRIBUTO
Veamos un ejemplo que presenta todos los valores de los atributos anteriormente
expuestos:
....
signal MiSenal : std_logic_vector (7 downto –3);
....
MiSenal’left toma el valor 7
MiSenal’rigth toma el valor -3
MiSenal’high toma el valor 7
MiSenal’low toma el valor –3
MiSenal’length toma el valor 11 (El 0 también se cuenta!!!)
MiSenal’range toma el valor (7 downto –3)
MiSenal’reverse_range toma el valor (-3 to 7)
Podremos utilizar algunos de estos valores en el bucle en el código siguiente:
Bucle: FOR i IN MiSenal’reverse_range LOOP
A(MiSenal’high - i)<=MiSenal(i)
END LOOP Bucle;
Esta función trabaja con una matriz con independencia de su tamaño.
2.2 Tipo vector
Probablemente el tipo más utilizado es la matriz unidimensional de bits, tipo que
representa físicamente un BUS o conjunto ordenado de líneas eléctricas. Habitualmente
estos tipos se predefinen con un tipo infinito para posteriormente se particularizado.
Estos tipos predefinidos suelen caracterizarse por llevar el sufijo _VECTOR.
En la librería correspondiente está definido el tipo
TYPE Bit_Vector IS ARRAY (POSITIVE range <>) OF BIT;
En nuestro código podremos definir señales:
.....
SUBTYPE MiBus : Bit_Vector (15 downto 0);
SIGNAL x,y : MiBus;
SIGNAL a : BIT;
......
IF(x=y)THEN
.....
x(3)<=a; -- Asignación de un ídice particular
y<=”111000101000110”; -- Asignación de un valor constante
.....
VARIABLE MaxIndex : INTEGER;
.....
MaxIndex:=x’HIGH; --Vale 15
.....
En el ejemplo anterior se muestra un conjunto de asignaciones y manipulaciones
posibles de un VECTOR. hemos definido un subtipo (subconjunto de un tipo), aunque
35
no es preciso realizar tal operación, ya que podemos trabajar directamente con el tipo
Bit_Vector(15 downto 0).
La asignación de un valor constante se realiza utilizando las dobles comillas en lugar de
la comilla simple, utilizado en el tipo std_logic. La razón estriba en que se asocian
cadenas de caracteres.
Otra cuestión interesante es la forma que hemos dado al VECTOR: (15 downto 0).
Es norma del buen programador el ser sistemático a la hora de definir las formas de los
buses. Normalmente el índice que se asigna al bit más significativo debe ser el más alto
y al bit menos significativo el más bajo. Si se utiliza la forma (0 to 15), esta regla no se
respeta y llevará seguro a confusiones. Es por tanto muy recomendable utilizar la forma
DOWNTO a la hora de definir buses y olvidar la forma TO.
3. Definición de tipos compuestos
Un tipo compuesto (record) consiste en una agregación de tipos que forman uno
nuevo. La manera de definirlos es:
TYPE nombre_de_tipo IS
RECORD
nombre_de_subtipo1 : tipo;
nombre_de_subtipo2 : tipo;
.............
END RECORD;
La manera de acceder a los tipos individualmente es mediante una referencia al nombre
del subtipo. Por ejemplo:
TYPE Entero_Complejo IS
RECORD
EnteroR : INTEGER –100 TO 100;
EnteroI : INTEGER –100 TO 100;
END RECORD;
....
SIGNAL x,y,z : Entero_Complejo
SIGNAL real, imaginaria : INTEGER –100 TO 100;
.....
x.EnteroR <= real;
.....
imaginaria <=x.EnteroI;
.....
y<=z;
.....
En este ejemplo se han realizado una asignaciones parciales y completas del tipo
definido. También se pueden realizar agregaciones:
X<=( EnteroR => 20, EnteroI => 40);
Y<=(30,-10);
En el ejemplo se pasan los argumentos por referencia y por orden.
36
4. Tipos simples
Como se ha comentado el VHDL dispone de tipos de datos estándar que de
manera natural soportan todas las posibilidades del lenguaje. Estos tipos son:
•
•
•
•
•
•
•
•
CHARACTER, tipo enumerado que soporta toda la secuencia de caracteres
ASCII desde 0 a 128.
BOOLEAN, tipo enumerado que toma valores TRUE ó FALSE, donde
TRUE>FALSE.
BIT, tipo enumerado, que toma valores ‘0’ ó ‘1’. Las funciones lógicas
devuelven también un valor del mismo tipo.
INTEGER que toma valores en el intervalo –(231-1) a +(231-1).
NATURAL, un subtipo del INTEGER que toma valores entre 0 y +(231-1).
POSITIVE, un subtipo del INTEGER que toma valores entre 1 y +(231-1).
STRING, una matriz de intervalo no definido del tipo CHARACTER.
BIT_VECTOR, una matriz de intervalo no definido del tipo BIT.
En la práctica ninguno de estos tipos se utiliza para describir hardware. Todos
los programas de simulación y síntesis VHDL proporcionan una librería de tipos y
funciones que completan la librería estándar para describir de manera más precisa en
comportamiento de circuitos digitales. Se trata de la librería IEEE.
4.1 Los atributos de las señales.
Independientemente del valor que una señal tenga en un instante dado el VHDL
mantiene una serie de registros asociados a la señal llamados atributos. La diferencia
con los atributos asociados a una matriz estriba en que son dinámicos, es decir, están
asociados a la evolución de la señal en el tiempo y son extremadamente útiles a la hora
de describir situaciones reales. Se basan en el mecanismo comentado en el capítulo
primero en el que se define una base de tiempos incremental (el tiempo mínimo es un
paso de simulación) para emular el paralelismo inherente a un circuito, y describir
adecuadamente la evolución de los sistemas digitales. Los atributos predefinidos en
VHDL son:
• DELAYED(t). Valor de la señal retrasada t unidades de tiempo.
• STABLE(t), verdadero si la señal permanece invariable durante t unidades de
tiempo.
• QUIET(t), verdadero si la señal no ha recibido ninguna asignación en t unidades
de tiempo.
• TRANSACTION, tipo bit, a ‘1’ cuando hay una asignación a la señal.
• EVENT, verdadero si ocurre un cambio en la señal en el paso de simulación.
• ACTIVE, verdadero si ocurre una asignación a la señal en el paso de simulación.
• LAST_EVENT, unidades de tiempo desde el último evento.
• LAST_ ACTIVE, unidades de tiempo desde la última asignación.
• LAST_VALUE, valor anterior de la señal.
• DRIVING, verdadero si el proceso actual determina el valor de la señal.
• DRIVING_VALUE, valor que toma la señal tras el proceso.
Ni que decir tiene que la mayoría de estos atributos no se utilizan en el
subconjunto de síntesis de VHDL. Son válidos para modelado de dispositivos. Sin
embargo existe uno de ellos en particular extremadamente útil a la hora de describir
37
sistema síncronos. Se trata del atributo EVENT, que estudiamos en detalle en la sección
siguiente.
La existencia de atributos marcan claramente la diferencia entre un objeto tipo
signal, de los tipo variable y constant. Se recuerda que los elementos de port map
tiene un tratamiento equivalente al de signal.
4.2 El atributo EVENT
Es de una gran importancia en la elaboración de código VHDL. Veamos por
qué. X’EVENT significa que si una señal sufre una modificación se produce un valor
‘verdadero’ en el atributo EVENT durante el instante de evaluación de la señal, es decir
durante el paso de simulación. Después, si ya no se produce cambio alguno, el atributo
toma el valor ‘falso’.
Este atributo permite detectar como caso particular una transición de una señal
‘0 a 1’ y ‘1 a 0’. Si se combina con una condición adicional podemos describir la
transición de una señal a un valor concreto, es decir:
X’EVENT AND X=’1’
es una manera de detectar un flanco de subida, ya que la señal X a cambiado y su valor
de cambio es ‘1’. Si este mecanismo lo aprovechamos para describir un elemento de
memoria que se activa con el flanco:
sinc: PROCESS(CLK)
BEGIN
IF CLK’EVENT AND CLK=’1’ THEN
Q<=D;
END IF;
END PROCESS sinc;
Mediante este código se describe un elemento de memoria que registra a través de un
flanco de la señal clk. A este elemento se le conoce como biestable o flip-flop tipo D.
Lo más interesante es que este mecanismo nos permite describir de forma clara todos
los sistemas digitales síncronos, que en realidad son la práctica totalidad de los sistemas
digitales.
Aún así hay cuestiones que resultan interesantes de destacar. Veamos el
siguiente código, parecido al anterior:
sinc_mal: PROCESS(CLK)
BEGIN
IF CLK’EVENT THEN
Q<=D;
END IF;
END PROCESS sinc_mal;
El atributo EVENT evalúa los cambios en CLK cada vez que se produce una transición
valida la condición. La pregunta: ¿existe algún módulo hardware digital cuyo
comportamiento coincida con la descripción del código anterior? Evidentemente no. El
artificio EVENT tiene sentido a la hora de realizar una síntesis si va ligado al valor de la
señal.
38
Otra cuestión es que intencionadamente hemos omitido el valor de D en la lista
de sensibilidad. Esto es posible debido a que la evaluación tiene lugar en instantes muy
precisos de tiempo que dependen sólo de CLK por tanto no tiene importancia el valor de
D en el resto del tiempo.
Finalmente es posible escribir este código:
sinc: PROCESS(CLK)
BEGIN
IF CLK’EVENT AND CLK=’1’ THEN
CUENTA<=CUENTA + 1; -- ¿Realimentación combinacional?
END IF;
END PROCESS sinc;
En el apartado 6 del capítulo primero describimos como una realimentación
combinacional la línea del código anterior. Realmente el bucle existe pero está
seccionado con un registro, situación que es del todo válida. Hemos descrito un
contador. Aunque es válido no recomendamos la generación de códigos como el
anterior. En el capítulo cinco expondremos y analizaremos a fondo un el código de un
contador.
5. La librería IEEE
Como se ha comentado la definición BIT resulta insuficiente para describir
completamente el conjunto de valores que cualitativamente toma una señal digital. De
ahí que la librería IEEE proporciona un paquete llamado “std_logic_1164”. La librería
extiende, como se ha dicho, el paquete estándar de VHDL, contemplando las
situaciones a nivel de función lógica. No existe un tratamiento a nivel de interruptor o
switch (cosa que sí existe en Verilog). Prácticamente es obligatorio disponer de una
cabecera que declare el uso de esta librería en todos nuestros diseños:
LIBRARY IEEE;
USE IEEE.std_logic_1164.ALL;
En esta librería se define un nuevo tipo de señal enumerado, el std_logic. Este
nuevo tipo de contempla los siguientes valores:
• ‘U’, no inicializado, es el valor por defecto.
• ‘X’, desconocido. Es el valor que toma en casos de conflicto.
• ‘0’, el valor lógico bajo.
• ‘1’, el valor lógico alto.
• ‘Z’, representa el estado de alta impedancia.
• ‘W’, estado desconocido débil.
• ‘L’, valor lógico bajo débil.
• ‘H’, valor lógico alto débil.
• ‘-‘, valor que no importa
Las situaciones tipo débil suelen representar situaciones de tipo resistivo. Por
ejemplo, una señal con valor ‘L’ se conecta directamente a una con valor ‘1’ el
resultado será una señal de valor ‘1’.
Es importante recordar cuales son estos valores y qué representan. El valor ‘z’
(¡¡minúscula!!) no existe, ya que no está contemplado en este conjunto enumerado. En
39
la librería se contemplan las manipulaciones precisas que permiten el adecuado
tratamiento de las señales y sus valores.
De la misma manera están definidos los tipos VECTOR std_logic_vector. para
soportar conjuntos ordenados de del tipo std_logic. Dentro del paquete se encuentran
definidas las operaciones booleanas y los comparadores = y /=. Asimismo proporciona
mecanismos que permiten la conversión entre tipos. Por ejemplo:
SIGNAL x,y,z : std_logic_vector (8 DOWNTO 0);
SIGNAL b,c : BIT;
.....
x(5)<=Conv_StdLogic(b);
c<=Conv_bit(z(3));
x<=y xor z;
5.1 Los tipos SIGNED y UNSIGNED.
La utilización del tipo std_logic_vector en un diseño en el que se realizan
operaciones de cálculo tiene dos dificultades: La primera consiste en que la librería no
contiene los operadores aritméticos necesarios para poder realizar los cálculos y la
segunda es debida a que el valor que se representa plantea ciertas dificultades debido a
la naturaleza ambigua de la representación binaria. Por ejemplo si un vector tiene el
valor “1101” estamos representando ¿qué valor? ¡Depende! Si trabajamos en valores
positivos tenemos el 13, pero si trabajamos con representación en complemento a 2
tenemos el –3. No disponemos con la librería std_logic_1164 de una manera de tratar
esta situación, ya que se limita a las funciones booleanas.
La librería IEEE contiene un paquete de funciones, la std_logic_arith que
contiene las funciones aritméticas para resolver la primera parte del problema. Mediante
la inserción en la cabecera de
USE IEEE.std_logic_arith.ALL;
disponemos de todos los operadores aritméticos (+,-,*,ABS) y relacionales
(<,>,<=,>=,= y /=).
Para la segunda dificultad, se han definido nuevos tipos de datos: UNSIGNED y
SIGNED. Son tipos de datos std_logic_vector con la atribución de contemplar el valor
de su contenido como entero sin signo el primero y entero con signo y representación en
complemento a 2 el segundo. También se incluyen las funciones de conversión
correspondientes para realizar correctamente las asignaciones. Por ejemplo podremos
definir:
SIGNAL a,b: UNSIGNED(8 downto 0);
SIGNAL x,y: SIGNED(8 downto 0);
SIGNAL d,e: std_logic_vector(8 downto 0);
.....
Realizamos las asignaciones
a <= b; --Son del mismo tipo y funciona
x <= a; --Se obtiene un error!! Diferente tipo
x <= signed(a); --También funciona la función conv_signed.
40
b<= unsigned(x) + unsigned(d); --gracias al mecanismo de sobrecarga
d <= std_logic_vector(a) * std_logic_vector(y) --Se obtiene un error!! Operador
ambiguo
Mediante este procedimiento podemos definir y controlar la morfología del hardware de
los operadores.
Existen unas librerías aritméticas reducidas que permiten operar directamente
con tipos std_logic_vector dando un tratamiento global. Una cabecera del tipo:
LIBRARY IEEE;
USE IEEE.std_logic_1164.ALL;
USE IEEE.std_logic_arith.ALL;
USE IEEE.std_logic_unsigned.ALL;
Extiende, efectivamente al tipo std_logic_vector, pero dentro de la librería
std_logic_unsigned, todas las funciones aritméticas quedan definidas para el tipo
std_logic_vector y se tratan de modo unsigned. No se obtendría el error del ejemplo
anterior. El capítulo cinco se dedica al uso de los operadores aritméticos y sus
particularidades.
5.2 Las funciones de conversión
Como se ha indicado anteriormente, las asignaciones solamente se pueden
realizar entre tipos idénticos. En las librerías se incluyen funciones que permiten la
conversión de un tipo en otro. Cuando se trata de convertir entre tipos signed, unsigned
o std_logic_vector la función de conversión sigue la forma:
señal1 <= tipo_señal1(señal2);
La función tipo_señal1(·) se sobrecarga con el tipo de la señal2. Cuando se realizan
conversiones de tipo en las que hay otros de naturaleza diferente, como es el caso de los
tipos integer y positive las funciones de conversión son diferentes, ya que hay que
especificar el número de bits de la señal de destino:
señal1 <= conv_std_logic_vector(señal2,señal1’LENGTH);
Donde señal2 es un tipo integer ó positive y señal1 es un tipo std_logic_vector.
Obviamente el valor de señal2 ha de ser compatible con el número de bits de la señal1.
De la misma forma se pueden definir las funciones:
señal1 <= conv_unsigned(señal2,señal1’LENGTH);
señal1 <= conv_signed(señal2,señal1’LENGTH);
Las funciones que realizan la conversión inversa son:
señal1 <= conv_to_integer(señal2);
Donde el tipo de la señal2 sobrecarga la función.
41
CAPÍTULO V. SUBSISTEMAS DIGITALES. EJEMPLOS DE DISEÑO.
Este capítulo incide en aspectos prácticos relacionados con la codificación
utilizando el lenguaje VHDL y se basa fundamentalmente en los conceptos introducidos
en capítulos anteriores. Abordaremos dos aspectos esenciales en la codificación de
sistemas digitales, por un lado los sistema síncronos y por otro, en los sistemas
aritméticos. Finalmente proponemos un ejemplo de una memoria de tamaño variable.
1. Codificación de Sistemas Síncronos
La descripción sistemas síncronos es una parte fundamental del diseño de
sistemas digitales. Se basan en la utilización de una señal de reloj que coordina los
cambios en el circuito, sucediéndose en instantes muy precisos, concretamente en los
flancos activos (de subida o bajada).
Todo sistema síncrono se puede descomponer en dos procesos concurrentes,
uno que recoge la funcionalidad característica del sistema y otro, generador de un
registro o banco de flip-flops, también llamado proceso de sincronismo. Nuestra
experiencia nos aconseja mantener este esquema de codificación en dos procesos, ya
que si bien requiere la introducción de un número mayor de líneas de código la
descripción es más clara, más legible y más fácil de depurar. Es más, hemos detectado
algunos sintetizadores que no admiten otra manera de introducir la descripción. Todos
los ejemplos que propondremos seguirán esta estructura.
Ejemplo 1. Codificación de un registro simple
El código es el siguiente:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY registro_simple IS
GENERIC (N : INTEGER := 5);
PORT(
resetz : IN std_logic;
clk : IN std_logic;
entrada : IN std_logic_vector (N-1 DOWNTO 0);
salida : OUT std_logic_vector (N-1 DOWNTO 0)
);
END registro_simple;
ARCHITECTURE comport OF registro_simple IS
BEGIN
Sinc: PROCESS(rstz,clk)
BEGIN
IF (resetz=’0’) THEN
Salida<=(OTHERS=>’0’);
ELSIF (clk’EVENT) AND (clk=’1’) THEN
Salida <= entrada;
END IF;
END PROCESS sinc;
END comport;
En este ejemplo hemos introducido algunos elementos que merecen ser
destacados. En primer lugar la señal de reset asíncrono resetz es activa a nivel bajo. Es
42
buena práctica que aquellas señales definidas como activas a nivel bajo lleven un
distintivo, de ahí que optemos por la terminación z. En segundo lugar la utilización
siempre que sea posible de parámetros en generics ya que su impacto en la complejidad
del código en prácticamente nula y permite la reutilización del mismo. El análisis de la
estructura de sincronismo indica prioridad en la señal de resetz sobre el flanco de reloj
clk. Esto indica que el registro cargará un ‘0’ ante un valor ‘0’ de la señal resetz aunque
clk fluctúe. Finalmente hemos omitido la señal entrada de la lista de sensibilidad, ya
que solamente se actualiza en flancos de clk.
Ejemplo 2. Codificación de un registro con señal de carga
El código es el siguiente:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY registro_carga IS
GENERIC (N : INTEGER := 5);
PORT(
resetz : IN std_logic;
clk : IN std_logic;
carga : IN std_logic;
entrada : IN std_logic_vector (N-1 DOWNTO 0);
salida : OUT std_logic_vector (N-1 DOWNTO 0)
);
END registro_ carga;
ARCHITECTURE comport OF registro_ carga IS
SIGNAL interna, valor : std_logic_vector (N-1 DOWNTO 0);
BEGIN
Mux: interna <= entrada WHEN (carga =‘1’) ELSE valor; --realimentación
Sinc: PROCESS(rstz,clk)
BEGIN
IF (resetz=’0’) THEN
valor<=(OTHERS=>’0’);
ELSIF (clk’EVENT) AND (clk=’1’) THEN
valor <= interna;
END IF;
END PROCESS sinc;
Salida <= valor;
END comport;
En éste ejemplo hemos utilizado la técnica de dos procesos concurrentes. El
proceso sinc es una estructura idéntica a la del ejemplo1.1. Hemos hecho uso de dos
señales auxiliares, interna y valor. La señal interna es la salida del multiplexor que
realimenta el valor almacenado. La señal valor se puede quitar, ya que su función es
simplemente la de evitar que la realimentación se produzca sobre la señal salida, que al
ser de tipo out no puede aparecer a la derecha en una asignación.
Podría construirse caminando el tipo inout o al tipo buffer, que es más
adecuado. No se utiliza en tipo inout porque no describe bien el tipo de la señal.
Tampoco lo definimos tipo buffer porque existen algunos sintetizadores que tratan el
tipo buffer de una manera no estándar, reservando la palabra buffer para forzar la
inclusión de un circuito de amplificación de corriente.
43
La versión con un solo proceso y con salida en alta impedancia:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY registro_carga IS
GENERIC (N : INTEGER := 5);
PORT(
resetz : IN std_logic;
clk : IN std_logic;
carga : IN std_logic;
lectura: IN std_logic;
entrada : IN std_logic_vector (N-1 DOWNTO 0);
salida : INOUT std_logic_vector (N-1 DOWNTO 0)
);
END registro_ carga;
ARCHITECTURE comport OF registro_ carga IS
SIGNAL valor : std_logic_vector (N-1 DOWNTO 0);
BEGIN
salida<=valor WHEN lectura=’1’ ELSE (OTHERS=>’Z’);
Sinc: PROCESS(rstz,clk)
BEGIN
IF (resetz=’0’) THEN
valor<=(OTHERS=>’0’);
ELSIF (clk’EVENT) AND (clk=’1’) THEN
IF (carga =‘1’) THEN
valor <= entrada;
END IF;
END IF;
END PROCESS sinc;
END comport;
Este código realiza una función idéntica al anterior, pero una descripción más
compleja y menos legible. Hemos introducido una señal, lectura, que pone el valor a la
salida, y en caso contrario, la deja en alta impedancia. Esto es útil en el caso de la
construcción de memorias, como la del ejemplo 8.
Ejemplo 3. Codificación de un contador síncrono
El código propuesto es:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
USE IEEE.std_logic_arith.all;
USE IEEE.std_logic_unsignaed.all;
ENTITY contador IS
GENERIC (satur : : INTEGER := 13; N : INTEGER := 5);
PORT(
resetz : IN std_logic;
clk : IN std_logic;
habilita : IN std_logic;-- poner en marcha el contador
clr: IN std_logic;
-- puesta a ‘0’ síncrona
salida : OUT std_logic_vector (N-1 DOWNTO 0)
);
44
END contador;
ARCHITECTURE comport OF contador IS
SIGNAL interna, valor : std_logic_vector (N-1 DOWNTO 0);
BEGIN
Cont: PROCESS(clr,habilita,carga,valor)
BEGIN
IF (clr=’1’) THEN
interna<=(OTHERS=>’0’);
ELSIF (habilita=’1’) THEN
IF (valor=satur) THEN -- comparación de la saturación
interna<=(OTHERS=>’0’);
ELSE
interna<=valor + 1;
END IF;
ELSE
interna<=valor;
END IF;
END PROCESS cont;
Sinc: PROCESS(rstz,clk)
BEGIN
IF (resetz=’0’) THEN
valor<=(OTHERS=>’0’);
ELSIF (clk’EVENT) AND (clk=’1’) THEN
valor <= interna;
END IF;
END PROCESS sinc;
Salida <= valor;
END comport;
En este ejemplo hemos mantenido la estructura de dos procesos concurrentes y
el proceso síncrono es una copia del ejemplo 2. Además hemos introducido varios
elementos nuevos. El valor de saturación se ha introducido en el campo generic, como
parámetro, aunque podría incluirse en el campo port como señal. Hay que puntualizar
ciertos aspectos acerca de la comparación del valor corriente con el valor de saturación:
a) La comparación se satisface cuando el contador alcanza el valor satur, que
debe ser alcanzable por la señal valor. (p.e. satur = 35 genera un error).
b) La comparación se realiza entre un tipo unsigned y un entero. Esta función
está incluida en la librería.
c) El número de ciclos de reloj necesario para saturar el contador es satur +1.
En cuanto al operador suma hemos utilizado un esquema global de definición de
tipos, es decir, se ha utilizado la librería std_logic_unsigned. El operador ‘+’ representa
la función suma entre los tipos unsigned y entero, función que existe en la librería
también.
El contador dispone de una señal clr de ‘puesta a 0 síncrona’. Su
comportamiento difiere del de resetz, ya si clr toma el valor ‘1’, el contador
evolucionará al valor 0 en el siguiente flanco activo de reloj clk.
Ejemplo 4. Codificación de una máquina de estados finita tipo Moore.
Una máquina de estados finitos tipo Moore es una máquina de estados que
evoluciona síncronamente en función del estado y las entradas y cuyas salidas toman
valores en función del estado en que se encuentra.
45
El código correspondiente a este ejemplo es el siguiente:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY maqMoore IS
PORT(
resetz : IN std_logic;
clk : IN std_logic;
habilita : IN std_logic;-- poner en marcha el contador
clr: IN std_logic;
-- puesta a ‘0’ síncrona
salida : OUT std_logic_vector (N-1 DOWNTO 0)
);
END maqMoore;
ARCHITECTURE comport OF MaqMoore IS
TYPE estado IS (reposo, activo, espera);
SIGNAL actual, futuro: estado;
BEGIN
fsm: PROCESS ( actual, activacion, retorno)
BEGIN
--salida<=’0’; sería un valor por defecto,
CASE actual IS
WHEN reposo =>
salida<=’0’; -- valor salida
IF (activacion=’1’) THEN
futuro<=activo;
ELSE
futuro<=reposo;
END IF;
WHEN activo =>
salida<=’1’; --valor salida
futuro<=espera;
WHEN retorno =>
salida<=’0’; -- valor salida
IF (retorno=’0’) THEN
futuro<=activo;
ELSIF (activacion=’1’) THEN
futuro<=espera;
ELSE
futuro<=reposo;
END IF;
END CASE;
END PROCESS fsm;
Sinc: PROCESS(rstz,clk)
BEGIN
IF (resetz=’0’) THEN
valor<=(OTHERS=>’0’);
ELSIF (clk’EVENT) AND (clk=’1’) THEN
actual <= futuro;
END IF;
END PROCESS sinc;
END comport;
Este ejemplo muestra el método de resolución de una máquina de estados. En
primer lugar los estados se definen como un tipo enumerado. Esto significa que la
relación entre el código del estado y su nombre viene determinado por su ubicación en
la lista de definición del tipo estado. Así el código de reposo es “00” (0), el de activo es
46
“01” (1) y el de espera es “10” (2). El código “11” (3) no es alcanzable. El mecanismo
de construcción es una selección mediante un CASE que define un multiplexor que
selecciona el estado futuro en función del estado actual y de las entradas.
Finalmente la evolución se produce en flancos activos de reloj mediante el
proceso de sincronismo, que tiene una estructura idéntica al de los ejemplos anteriores.
ACTIVACION=0
RETORNO=?
REPOSO
SALIDA=0
ACTIVACION=1
RETORNO=?
ACTIVACION=0
RETORNO=1
ACTIVACION=?
RETORNO=0
ACTIVACION=1
RETORNO=1
ESPERA
SALIDA=0
ACTIVO
SALIDA=1
Figura 3. Ejemplo de máquina de Moore
Ejemplo 5. Codificación de una máquina de estados finita tipo Mealy
Una máquina de estados finitos tipo Mealy es una máquina de estados que
evoluciona síncronamente en función del estado y las entradas y cuyas salidas toman
valores en función del estado en que se encuentra y de las propias entradas.
El código correspondiente a este ejemplo es el siguiente:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY maqMealy IS
PORT(
resetz : IN std_logic;
clk : IN std_logic;
habilita : IN std_logic;-- poner en marcha el contador
clr: IN std_logic;
-- puesta a ‘0’ síncrona
salida : OUT std_logic_vector (N-1 DOWNTO 0)
);
END maqMealy;
ARCHITECTURE comport OF maqMealy IS
47
TYPE estado IS (reposo, activo, espera);
SIGNAL actual, futuro: estado;
BEGIN
fsm: PROCESS ( actual, activacion, retorno)
BEGIN
CASE actual IS
WHEN reposo =>
IF (activacion=’1’) THEN
salida<=’1’;
futuro<=activo;
ELSE
salida<=’0’;
futuro<=reposo;
END IF;
WHEN activo =>
salida<=’1’;
futuro<=espera;
WHEN retorno =>
IF (retorno=’0’) THEN
salida<=’1’;
futuro<=activo;
ELSIF (activacion=’1’) THEN
salida<=’0’;
futuro<=espera;
ELSE
salida<=’0’; -- valor salida
futuro<=reposo;
END IF;
END CASE;
END PROCESS fsm;
Sinc: PROCESS(rstz,clk)
BEGIN
IF (resetz=’0’) THEN
valor<=(OTHERS=>’0’);
ELSIF (clk’EVENT) AND (clk=’1’) THEN
actual <= futuro;
END IF;
END PROCESS sinc;
END comport;
ACTIVACION=0
RETORNO=?
SALIDA=0
REPOSO
ACTIVACION=1
RETORNO=?
SALIDA=1
ACTIVACION=0
RETORNO=1
SALIDA=0
ACTIVACION=1
RETORNO=1
SALIDA=0
ACTIVACION=?
RETORNO=0
SALIDA=1
ESPERA
ACTIVO
SALIDA=1
Figura 4. Ejemplo de máquina de Mealy
48
Desde un punto de vista puramente formal no existen razones para recomendar
el uso de un tipo de máquina u otro, pero desde la perspectiva de una buena
implementación se recomienda la forma Moore. La forma Mealy presenta problemas
con los pulsos espúreos (glitches) en las entradas ya que los transfiere a la salida. La
forma Moore presenta un filtro a este tipo de efectos.
2. Codificación de sistemas aritméticos.
Una de las grandes aportaciones de los lenguajes de descripción de hardware el
la de poder trabajar con operadores aritméticos sin necesidad de preocuparse de su
forma o estructura, que por regla general sigue un patrón regular. Sin embargo este
hecho supone una pérdida en el control de la topología de los propios circuitos. De
alguna manera, mediante la inserción de un signo + ó *, dejamos que la herramienta de
síntesis automática haga el “trabajo sucio”. En la era de los esquemáticos había que
realizar una verificación exhaustiva del módulo en cuestión mediante un test funcional
que prácticamente contemplara todas las posibles combinaciones de las entradas.
En este apartado introducimos unos ejemplos de utilización de estos operadores y de
cómo su utilización afectaría al resultado.
Ejemplo 6. Operador aritmético con el signo tratado globalmente.
Proponemos un sumador:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
USE IEEE.std_logic_arith.all;
USE IEEE.std_logic_unsigned.all;
ENTITY Sumador IS
GENERIC(S1: integer:=8; S2: integer:=5);
PORT(
A : IN std_logic_vector(S1-1 downto 0);
B : IN std_logic_vector(S2-1 downto 0);
SUMA: OUT std_logic_vector(S1-1 downto 0) --suponiendo que S1>S2
);
END Sumador;
ARCHITECTURE comport OF Sumador IS
BEGIN
SUMA <= A+B;
END comport;
El ejemplo propone un sumador totalmente basado en números sin signo. En este
caso los tipos std_logic_vector se considerarán positivos. En el caso de una declaración
de la librería std_logic_signed obtendríamos un tratamiento equivalente pero las
cantidades con MSB=1 se tratarían como complemento a 2.
49
Por otra parte es interesante observar el tratamiento del bit de acarreo. La
función suma estándar no contempla la posibilidad de que el valor del resultado SUMA
exceda de la capacidad de representación del mayor de los valores de A ó B. Es por
tanto muy peligroso utilizar estas funciones sin haber protegido esta posibilidad. Por
tanto proponemos este código:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
USE IEEE.std_logic_arith.all;
USE IEEE.std_logic_unsigned.all;
ENTITY Sumador IS
GENERIC(S1: integer:=8; S2: integer:=5);
PORT(
A : IN std_logic_vector(S1-1 downto 0);
B : IN std_logic_vector(S2-1 downto 0);
SUMA: OUT std_logic_vector(S1 downto 0) --suponiendo que S1>S2
);
END Sumador;
ARCHITECTURE comport OF Sumador IS
SIGNAL auxA: std_logic_vector(S1 downto 0);
BEGIN
auxA<=”0” & A; --concatenar un cero por delante por ser unsigned.
SUMA <= auxA + B;
END comport;
Resulta, pues, muy útil revisar los códigos fuente de las librerías estándar para
evitar problemas relacionados con los operadores de este tipo. La concatenación se
realizaría:
auxA<= A(S1-1) & A;
en el caso de trabajar con señales del tipo signed.
Ejemplo 7. Operador aritmético con el signo tratado localmente.
El código del ejemplo anterior se puede escribir:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
USE IEEE.std_logic_arith.all;
ENTITY Sumador IS
GENERIC(S1: integer:=8; S2: integer:=5);
PORT(
A : IN std_logic_vector(S1-1 downto 0);
B : IN std_logic_vector(S2-1 downto 0);
SUMA: OUT std_logic_vector(S1-1 downto 0) --suponiendo que S1>S2
);
END Sumador;
ARCHITECTURE comport OF Sumador IS
SIGNAL sgA: signed(S1-1 downto 0);
SIGNAL sgB: signed(S2-1 downto 0);
BEGIN
sgA<=signed(A);
sgB<=signed(B);
50
SUMA <=sgA + sgB;
END comport;
Hemos omitido en este caso la librería que trata los operandos globalmente. Sin
embargo utilizamos los tipos vector con atributo signed para definir el tipo de operando
utilizado. En este caso podríamos mezclar los operandos dentro de la misma entidad.
Para poder utilizar distintos tipos de operandos dentro de la misma operación habría que
comprobar si existe la operación en la librería y qué tipo de vectores devuelve. El
mecanismo de sobrecarga se encargará de seleccionar la operación deseada.
3. Codificación de módulos de memoria.
Un ejercicio muy completo de codificación consiste en la creación de bloques de
memoria parametrizados. Utilizaremos el registro del ejemplo 2 como un componente
de este código. Además construiremos el decodificador de entrada y de salida.
Ejemplo 8. Memoria RAM síncrona de tamaño genérico.
El código del decodificador de N entradas puede ser:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY decod IS
GENERIC(N: INTEGER :=2);
PORT (
enable : IN std_logic;
address: IN std_logic_vector(N-1 DOWNTO 0);
pointers: OUT std_logic_vector (2**N-1 DOWNTO 0)
);
END decod;
ARCHITECTURE comport OF decod IS
SIGNAL aux: INTEGER;
BEGIN
aux<=CONV_INTEGER(address);
dec: PROCESS(enable,aux)
BEGIN
IF (enable='1') THEN
FOR i IN 0 TO 2**N-1 LOOP
IF aux=i THEN
pointers(i)<='1' ;
ELSE
pointers(i)<='0' ;
END IF;
END loop;
ELSE
pointers<=(others=>'0');
END IF;
END PROCESS dec;
END comport;
Ahora construimos un código para la memoria. Utilizaremos tres parámetros
libres, Nbdirecc, Npalabras y Nbpalabra, para dimensionar la memoria. Con ellos
definimos, respectivamente el tamaño del bus de direcciones, el número efectivo de
registros y el tamaño de cada uno de ellos.
51
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
ENTITY memoria IS
GENERIC(Nbdirecc: INTEGER :=2;Npalabras: INTEGER :=2; Nbpalabra: INTEGER :=2);
PORT (
clk: IN std_logic;
rstz: IN std_logic;
escribe: IN std_logic;
Dato: INOUT std_logic_vector(Nbpalabra -1 DOWNTO 0);
Direccion: IN std_logic_vector(Nbdirecc -1 DOWNTO 0)
);
END memoria;
ARCHITECTURE comport OF memoria IS
COMPONENT decod
GENERIC(N: INTEGER :=2);
PORT (
enable : IN std_logic;
address: IN std_logic_vector(N-1 DOWNTO 0);
pointers: OUT std_logic_vector (2**N-1 DOWNTO 0)
);
END COMPONENT;
COMPONENT registro_carga
GENERIC (N : INTEGER := 5);
PORT(
resetz : IN std_logic;
clk : IN std_logic;
carga : IN std_logic;
lectura: IN std_logic;
entrada : IN std_logic_vector (N-1 DOWNTO 0);
salida : OUT std_logic_vector (N-1 DOWNTO 0)
);
END COMPONENT;
SIGNAL lee : std_logic;
SIGNAL punterolee, punteroescribe : std_logic_vector(2** Nbdirecc-1 DOWNTO 0);
SIGNAL DatoIn, DatoOut: std_logic_vector(Nbitspalabra-1 DOWNTO 0);
BEGIN
decodEscr: decod
GENERIC MAP(N=>Nbdirecc)
PORT MAP(enable => escribe, address=>direccion, pointers=> punteroescribe);
lee<= NOT escribe;
decodLect: decod
GENERIC MAP(N=>Nbdirecc)
PORT MAP(enable => lee, address=>direccion, pointers=> punterolee);
FOR i IN 0 TO Npalabras-1 GENERATE
reg: registro_carga
GENERIC MAP(N => Nbpalabra)
PORT MAP(resetz =>rstz, clk =>clk,
carga => punteroescribe(i), lectura=> punterolee(i),
entrada =>Datoin, salida =>Datoout);
END GENERATE;
--Para evitar dejar las señales DatoOut a ‘Z’ mientras se escribe
DatoOut <= (OTHERS=>’0’) WHEN(lee=’0’) ELSE (OTHERS=>’Z’);
52
--La entrada/salida de Dato es bidireccional. Esto se resuelve mediante las sentencias
DatoIn<=Dato; --Entrada
Dato <= DatoOut WHEN(lee=’1’) ELSE (OTHERS=>’Z’); --Salida
END comport;
En este ejemplo hemos construido un multiplexor realizado con puertas
triestado. Está construido implícitamente utilizando cada registro del bucle generate
conectando sus salidas a la señal DatoOut. La activación se realiza mediante la
presencia de un decodificador. Sin embargo existe el riesgo de dejar el bus en alta
impedancia. Para este caso hemos puesto una salvaguarda de dejarlo a cero en caso de
que se esté en proceso de escritura. En este caso el decodificador no se habilitaría y el
bus de salida quedaría en alta impedancia. Las sentencias finales son un ejemplo de
cómo realizar un bus bidireccional típico.
53
6. VHDL NO SINTETIZABLE. SIMULACIÓN Y MODELADO
En este capítulo haremos una breve introducción a conceptos de VHDL
relacionados con su capacidad para describir tecnologías y entornos. El objetivo de este
capítulo es ofrecer un perspectiva diferente a la hasta el momento expuesta, ya que la
visión del VHDL sintetizable ofrece una perspectiva muy parcial del lenguaje, si bien es
la más útil desde el punto de vista de un diseñador.
Ahora podremos desarrollar una perspectiva completa de un sistema, ya que
podremos integrar en el mismo código Sistema y Circuito Digital, coexistiendo ambos
en igual ámbito de simulación.
1. Mecanismo de simulación.
El VHDL así como el resto de los lenguajes de simulación utilizan un sistema
basado en tabla de eventos para evaluar las sentencias del código. La lista de
sensibilidad representa un mecanismo para vigilar la evaluación de un proceso cuando
la señal sufre un cambio.
El sistema de simulación se basa en la evolución de una variable tiempo que se
incrementa en un tiempo mínimo de discretización o delta_time, que se establece en el
entorno de simulación. Esa base de tiempo es el nivel máximo de detalle que permite
discriminar la simulación. Por tanto podremos introducir modelos que describen un
comportamiento real sin más que implementar sus respuestas funcionales y temporales.
Simulación funcional
Es una simulación con retrasos en la lógica combinacional nulos, y en la lógica
secuencial mayores que cero, es decir, los tiempos de propagación de las señales a
través de la lógica se consideran cero y por tanto solamente se tiene en cuenta su
respuesta funcional. Para poder hacer funcionar la lógica secuencial es imprescindible
dotar a los elementos de memoria con un retardo no nulo. Es fácil ver que pasaría en
caso de no proceder así. Por ejemplo un registro de desplazamiento.
Simulación temporal
Es una simulación característica de sistemas ya sintetizados donde se trabaja con
circuitos descritos con primitivas y asociados a una tecnología de un fabricante. Estas
primitivas disponen de datos temporales. En este caso la simulación genera formas de
onda mucho más cercanas al comportamiento final. A partir de una simulación de este
tipo diversos comportamientos típicos se manifiestan, como spikes, pulsos,... etc. Hay
dos tipos de retardos: inerciales y de transporte. Los retardos inerciales consisten en que
el retardo tiene inercia, y esa ha de ser superada para que el circuito descrito manifieste
su valor a la salida. Por ejemplo, si una puerta tiene un retardo inercial de 20ns y le llega
un pulso en una entrada menor que 20ns, por ejemplo 10ns, este pulso no tendría
suficiente tiempo para vencer la inercia. Este es el retardo por defecto en VHDL.
El otro modelo de retardo es el de transporte, más bien referido a conexionados, es
decir, se simulan los retrasos de las puertas lógicas y se incorporan retardos de las líneas
54
obtenidos del programa de ruteado siendo un comportamiento muy próximo al real. Este
modelo de retardo requiere una definición adicional: transport.
2. Sentencias para modelado.
El control del flujo de simulación se realiza mediante los siguientes comandos
no sintetizables AFTER y WAIT.
Sentencia AFTER
Se utiliza para controlar la evolución de una señal a lo largo del tiempo. Los
incrementos temporales han de ser variables del tipo time.
Señal <= valor AFTER tiempo, valor AFTER tiempo, ... ;
Por ejemplo una señal oscilante puede construirse:
clock <= NOT clock AFTER 30 ns;
Una señal que realiza un pulso a nive bajo durante 1 microsegundo.
Rstz<=’1’,’0’ AFTER 10 ns, ‘1’ AFTER 1 us;
Un comportamiento realista de una primitiva de una puerta AND se describe en el
siguiente ejemplo, representando un comportamiento inercial:
ENTITY and2 IS
PORT(a, b: IN std_logic;
Y: OUT std_logic );
END and2;
ARCHITECTURE comport OF and2 IS
BEGIN
y <= a AND b AFTER 5 ns;
END comport;
Si a sufre un pulso de tamaño menor que 5ns la puerta no sufriría ningún cambio
incluso en el caso de que la función lógica obligara a un cambio en la salida.
Para simular un retardo de transporte en el que el valor se manifiesta al cabo de un
cierto tiempo:
b<=TRANSPORT a AFTER 10 ns;
Sentencia WAIT
Permite al diseñador suspender la ejecución secuencial de un proceso. Es por
tanto una sentencia que está siempre insertada dentro de la ejecución de un process.
Dado que establece un control sobre el mismo, es necesario que el proceso sea de
ejecución infinita, es decir, no exista la lista de sensibilidad.
Existen tres formas de trabajar con la sentencia wait:
•
WAIT ON lista de señales. Se vigilan cambios en las señales.
55
• WAIT UNTIL condición. Se vigila el cumplimiento de una sentencia.
• WAIT FOR tiempo. Detiene el proceso durante un tiempo
Igualmente es posible realizar combinaciones de distintas Veamos algunos conceptos
asociados a estas sentencias.
WAIT ON lista de señales. Se detiene el proceso a la espera que las señales que
contiene la lista sufran algún evento. Realmente cuando existe más de una señal se
vigila el or de los atributos EVENT de las señales.
Por ejemplo el típico proceso de sincronismo se podría escribir:
PROCESS
BEGIN
IF rstz=’0’ THEN
q<=’0’;
ELSIF (clk’EVENT) AND (clk=’1’) THEN
q<=d;
END IF;
WAIT ON rstz,clk;
END PROCESS;
Realizando la sentencia WAIT ON la tarea de la lista de sensibilidad.
WAIT UNTIL condición. La condición que se vigila en este caso es el cumplimiento
de una sentencia booleana, de lo contrario el proceso permanece suspendido. Por
ejemplo, si a y b son cantidades numérica y c un std_logic, se puede escribir:
WAIT UNTIL (a>b) AND (c=’1’);
WAIT FOR tiempo. La variable debe ser del tipo time. También se pueden introducir
expresiones complejas que devuelven un valor del tipo time. Por ejemplo, si a y b son
del tipo time:
WAIT FOR (a-b);
es una expresión válida.
Condiciones complejas. Se pueden realizar complejas combinando las diferentes
formas anteriores. En este caso se realiza el and de todas las combinaciones:
WAIT ON a,b UNTIL (a=’1’) AND (b=’0’) FOR 5 ns;
Se esperaría un cambio en a y b, continuará cuando este cambio satisfaga la condición
pero al cabo de 5 ns.
2.1 Lista de sensibilidad y WAIT ON.
Se ha comentado el hecho de que la lista de sensibilidad y la sentencia wait on
al final del proceso son equivalentes. Pero, ¿por qué al final del proceso y no al
principio? La respuesta es sencilla. Durante la inicialización de los mecanismos de
simulación todos los procesos se ejecutan una vez, independientemente de su lista de
56
sensibilidad. Por tanto para evitar que no se detenga la ejecución del mismo durante los
tiempos iniciales es preciso situar wait on al final.
2.2 Concurrencia de procesos.
En el capítulo 3 se explica cómo se producen las asignaciones de señales (tipo
signal) dentro de los procesos. Una asignación de una señal dentro del proceso no es
efectiva hasta la resolución del mismo al final. Esto no ocurre así con las variables
temporales (tipo variable). Veamos un pequeño ejemplo:
PROCESS(a)
BEGIN
k<=0;
IF(a=’1’) THEN k<=’1’;
END IF;
IF (k=’0’) THEN b<=a;
ELSE b<=’0’;
END IF;
END PROCESS;
En este caso k no toma un nuevo valor hasta el final del proceso, es decir, k no
evaluará correctamente la expresión de b, ya que se tomará el valor que k tuviese
cuando se evalúa el proceso.
La sentencia wait permite resolver este conflicto, si se inserta una sentencia wait
on después de cada sentencia secuencial. El efecto es que se puede evaluar dicha
sentencia debido a que se espera durante un paso de simulación y el simulador la evalúa.
PROCESS
BEGIN
k<=0;
WAIT FOR 0 ns;
IF(a=’1’) THEN k<=’1’;
END IF;
WAIT FOR 0 ns;
IF (k=’0’) THEN b<=a;
ELSE b<=’0’;
END IF;
WAIT ON a;
END PROCESS;
Para poder insertar la sentencia wait es preciso eliminar la lista de sensibilidad.
Obviamente la alternativa más elegante y correcta es la de transformar k en una
variable.
3. Construir un “Test Bench”.
En la práctica una simulación se realiza en lo que se conoce como un “test
bench”. Éste consiste en un banco de pruebas de nuestro circuito, que se estimula
determinando los valores de las señales externas al mismo. Típicamente las dos señales
más comunes son el reloj (clk) y la inicialización asíncrona (rstz). Además suele
introducirse un modelo del sistema exterior al circuito, por ejemplo, un motor eléctrico,
57
las valores de los pixeles de una imagen, una señal muestreada de un convertidor A/D,
... etc.
Asimismo se establecen condiciones temporales que gobiernan señales y
modelos. Una fuente de datos suelen ser los ficheros, que usualmente contienen
medidas reales de un sistema. El VHDL permite su lectura y el gobierno de la misma.
Al manejo de ficheros dedicaremos la sección 4.
3.1 Modelo de “test bench”.
Un entorno de simulación puede registrar señales internas a un circuito y
representarlos en formas de onda. La figura representa un esquema de los posibles
elementos de un “test bench”. También un sistema de ficheros es un mecanismo útil
para registrar resultados.
CLK
UUT
RSTZ
Datos
Modelo
Ilustración 5. Modelo general de un "test bench"
Típicamente un test bench es una entidad sin entradas ni salidas aparentes.
ENTITY tb IS
END tb;
Un segundo elemento es el reloj. En nuestro caso lo denominamos clk:
SIGNAL clk : std_logic :=’0’;
…..
clk <= NOT clk AFTER 20 ns;
….
Definimos clk como una señal que oscila a 50 Mhz. La asignación es
concurrente con el componente que simulamos. Asimismo precisa de un valor inicial y
éste se especifica en su declaración inicial.
El tercer elemento es la señal de inicialización asíncrona. Todo buen diseñador
debe poner cuidado en conseguir que el circuito funcione con un estado inicial bien
conocido, de ahí que sea aconsejable trabajar siempre de esta forma.
SIGNAL rstz : std_logic;
….
rstz <= ‘1’, ‘0’ AFTER 10 ns, ‘1’ AFTER 40 ns;
58
….
Básicamente hemos representado un monostable.
Obviamente el cuarto elemento es el propio circuito objeto de diseño, que en
este caso actúa como una unidad de jerarquía inferior.
Veamos un ejemplo completo:
ENTITY testbench IS
END ENTITY;
ARCHITECTURE ej OF testbench IS
COMPONENT nand
PORT (
A, B: IN std_logic;
Y: OUT std_logic);
END COMPONENT;
SIGNAL Ai, Bi, Yo: std_logic;
BEGIN
Uut: nand PORT MAP(A=>Ai, B=>Bi, Y=>Yo);
Ai <= ’0’, ’1’ AFTER 50 ns, ’0’ AFTER 100 ns, ’1’ AFTER 150 ns;
Bi <= ’0’, ’1’ AFTER 100 ns;
END ARCHITECURE ej;
4. La librería TextIO
Uno de los paquetes de librerías predefinidas es el que permite acceso a ficheros
que almacenan datos y permite, por tanto la inserción de datos en el circuitos generados
para la realización, por ejemplo de pruebas complejas. La librería permite tanto escribir
como leer datos formateados de ficheros. Son ficheros de caracteres ASCII. Los
ficheros han de ir formateados según especifique el usuario, es decir, ha de repetirse la
estructura de una línea. Una línea es una cadena de caracteres, datos separados por
espacios, que concluye con un retorno de carro. La línea es la unidad de transferencia
fundamental.
El procesamiento de un fichero se produce dentro de un proceso, cuya ejecución
ha de coordinarse cuidadosamente con señales que lo activan y los valores obtenidos del
fichero.
Para poder acceder a un objeto del tipo fichero ha de declararse la librería
estándar TextIO:
USE std.textio.all;
En ella existe el tipo básico FILE, que se utiliza dentro de la sección declarativa de un
proceso:
FILE infile : TEXT IS IN “fichero.in”;
FILE outfile : TEXT IS OUT “fichero.out”;
y de la misma manera se declara la línea como una variable del tipo line:
59
VARIABLE lineaent, lineasal: LINE;
Para controlar el fin de fichero se utiliza la función ENDFILE:
WHILE NOT( ENDFILE( infile )) LOOP
……..
END LOOP;
Para copiar una línea del fichero de entrada sobre la variable tipo line se utiliza la
función READLINE y para escribir sobre el fichero de salida una línea se utiliza la
función WRITELINE. Su sintaxis es:
READLINE(infile, lineaent);
WRITELINE(outfile,lineasal);
Finalmente para extraer un argumento de la línea leída se utiliza la función READ y
para escribirlo WRITE:
READ(lineaent, argumento1);
READ(lineaent, argumento2);
Finalmente para escribir argumentos:
WRITE(lineasal, argumento1);
WRITE(lineasal, argumento2);
Un ejemplo completo:
LIBRARY IEEE;
USE IEEE.std_logic_1164.all;
USE std.textio.all;
ENTITY testbench IS
END testbench;
ARCHITECTURE tbench_1 OF testbench IS
COMPONENT sf
PORT (
clk: IN std_logic;
rst: IN std_logic;
S_a: IN std_logic; -- sensORes A
S_b: IN std_logic; -- B
S_c: IN std_logic; -- C
S_d: IN std_logic; -- D
rav0: OUT std_logic _vector (2 DOWNTO 0); -- Luces calle 0
rav1: OUT std_logic _vector (2 DOWNTO 0) -- Luces calle 1
);
END COMPONENT ;
SIGNAL t_clk : std_logic := '0';
SIGNAL t_rst : std_logic ;
SIGNAL t_sa, t_sb, t_sc, t_sd: std_logic := '0';
SIGNAL rav0,rav1: std_logic _vector (2 DOWNTO 0);
TYPE luces IS (rojo, amarillo, verde, error);
SIGNAL calle0, calle1: luces;
BEGIN
60
uut : sf
PORT MAP (
clk=>t_clk,
rst=>t_rst,
S_a=>t_sa,
S_b=>t_sb,
S_c=>t_sc,
S_d=>t_sd,
rav0=>rav0,
rav1=>rav1
);
t_clk <= NOT t_clk AFTER 50 ns;
t_rst <= '0', '1' AFTER 55 ns;
t_sa <= '0', '1' AFTER 1000 ns;
PROCESS(rav0, rav1)
BEGIN
IF (rav0 = "100" ) THEN
calle0 <= rojo;
ELSIF (rav0 = "010" ) THEN
calle0 <= amarillo;
ELSIF (rav0 = "001" ) THEN
calle0 <= verde;
ELSE
calle0 <= error;
END IF;
IF (rav1 = "100" ) THEN
calle1 <= rojo;
ELSIF (rav1 = "010" ) THEN
calle1 <= amarillo;
ELSIF (rav1 = "001" ) THEN
calle1 <= verde;
ELSE
calle1 <= error;
END IF;
END PROCESS;
textOUT: PROCESS(rav0, rav1)
FILE fout: TEXT IS OUT "fout.dat";
VARIABLE linea: LINE;
VARIABLE str0: string(1 TO 5) := "rav0=";
VARIABLE str1: string(1 TO 5) := "rav1=";
VARIABLE espacio: string(1 TO 5) := " ";
BEGIN
WRITE(linea,str0);
WRITE (linea, rav0);
WRITE (linea, espacio);
WRITE (linea,str1);
WRITE (linea, rav1);
WRITELINE(fout,linea);
END PROCESS;
END tbench_1;
61