Beruflich Dokumente
Kultur Dokumente
VHDL VHDL
Este es un libro electrónico diseñado para Internet en el cual podrás
Presentación
aprender uno de los Lenguajes de Descripción de Hardware estándar en
el mundo: VHDL.
Índice
Con este tutorial conocerás a fondo todos los secretos de la sintaxis, qué
pasos deberemos dar para crear nuestros primeros ejemplos, cómo
Teoría simularlos y hasta implementarlos en un dispositivo de lógica
programable.
Prácticas
Selecciona en el menú de la izquierda la parte del tutorial a la que deseas
acceder.
DET
COMIENZO
EUITI
Links VHDL
Con este tutorial conocerás a fondo todos los secretos de la sintaxis, qué pasos deberemos
dar para crear nuestros primeros ejemplos, cómo simularlos y hasta implementarlos en un
dispositivo de lógica programable.
COMIENZO
Teoría
La parte teórica profundiza en la sintaxis del lenguaje, explicando cada sentencia y cada cláusula
minuciosamente, con ejemplos comentados
Práctica
Con nuestras prácticas podrás aprender VHDL de una forma rápida y práctica, con sólo tener de
conocimientos de Electrónica Digial y de Programación
Prácticas
En esta parte del tutorial aprenderás VHDL de una forma sencilla y rápida
practicando desde el principio y con posibilidad de simular los diseños explicados
desde esta misma página web. Las prácticas son las siguentes:
1. Multiplexor
2. Demultiplexor
3. Codificador
4. Decodificador
5. Comparador
7. Restador
8. Multiplicador
9. Flip-flops
10. Contador
11. Secuenciador
Teoría
En esta parte del tutorial aprenderás todos los secretos de la sintaxis del lenguaje
VHDL, en el cual se estudiará de una forma teórica y clara cómo crear un programa
en VHDL. Este apartado consta de 7 lecciones distintas:
1. Introducción
❍ Breve reseña histórica
❍ VHDL vs MSI
❍ Qué es un PLD
❍ Cocurrencias y señales
■ Modos
■ Tipos
■ Estilos
❍ Paquetes
❍ Identificadores
❍ Palabaras reservadas
❍ Símbolos especiales
❍ Tipos de datos
❍ Expresiones y operadores
❍ Asignación a señal
❍ Asignación a variable
❍ Sentencia if
❍ Sentencia case
Sentencia loop
❍
❍ Sentencia exit
❍ Sentencia next
❍ Sentencia null
❍ Sentencia wait
❍ Sentencia process
❍ Galaxy de Cypress
❍ Nova de Cypress
Introducción
● VHDL
A mediados de los años setenta se produce una fuerte evolución en los procesos de fabricación de los
circuitos integrados, y junto a las tecnologías bipolares, surge la MOS (metal oxide semiconductor ),
principalmente la NMOS, promoviendo el desarrollo de circuitos digitales hasta la primera mitad de los
años ochenta.
En aquellas épocas, el esfuerzo de diseño se concentraba en los niveles eléctricos para establecer
características e interconexiones entre los componentes básicos a nivel de transistor. El proceso de
diseño era altamente manual y tan solo se empleaban herramientas como el PSPICE para
simular esquemas eléctricos con modelos previamente personalizados a las distintas tecnologías.
A medida que pasaban los años, los procesos tecnológicos se hacían más y más complejos. Los
problemas de integración iban en aumento y los diseños eran cada vez más difíciles de depurar y de dar
mantenimiento. Inicialmente los circuitos MSI (Medium Scale Integration ) y LSI (Low Scale
Integration ) se diseñaron mediante la realización de prototipos basados en módulos más sencillos. Cada
uno de estos módulos estaba formado por puertas ya probadas, este método poco a poco, iba quedándose
obsoleto. En ese momento (finales de los años setenta) se constata el enorme desfase que existe entre
tecnología y diseño.
La considerable complejidad de los chips que se pueden fabricar, implica unos riesgos y costes de
diseño desmesurados e imposibles de asumir por las empresas. Es entonces, cuando diversos grupos de
investigadores empiezan a crear y desarrollar los llamados "lenguajes de descripción de hardware" cada
uno con sus peculiaridades. Empresas tales como IBM con su IDL, el TI - HDL de Texas Instruments,
ZEUS de General Electric, etc., así como los primeros prototipos empleados en las universidades,
empezaron a desarrollarse buscando una solución a los problemas que presentaba el diseño de los
sistemas complejos.
Sin embargo, estos lenguajes nunca alcanzaron el nivel de difusión y consolidación necesarios por
motivos distintos. Unos, los industriales, por ser propiedad de la empresa permanecieron encerrados en
ellas y no estuvieron disponibles par su estandarización y mayor difusión, los otros, los universitarios,
perecieron por no disponer de soporte ni mantenimiento adecuado.
Alrededor de 1981 el Departamento de Defensa de los Estados Unidos desarrolla un proyecto llamado
VHSIC (Very High Speed Integrated Circuit ) su objetivo era rentabilizar las inversiones en hardware
haciendo más sencillo su mantenimiento. Se pretendía con ello resolver el problema de modificar el
hardware diseñado en un proyecto para utilizarlo en otro, lo que no era posible hasta entonces porque no
existía una herramienta adecuada que armonizase y normalizase dicha tarea, era el momento de los
HDL's
VHDL
Tras varias versiones llevadas a cabo con la colaboración de la industria y de las universidades, que
constituyeron a posteriori etapas intermedias en el desarrollo del lenguaje, el IEEE publicó en diciembre
de 1987 el estándar IEEE std 1076-1987 que constituyó el punto firme de partida de lo que después de
cinco años sería ratificado como VHDL.
Esta doble influencia, tanto de la empresa como de la universidad, hizo que el estándar asumido fuera
un compromiso intermedio entre los lenguajes que ya habían desarrollado previamente los fabricantes, de
manera que éste quedó como ensamblado y por consiguiente un tanto limitado en su facilidad de
utilización haciendo dificultosa su total comprensión. Este hecho se ha visto incluso ahondado en su
revisión de 1993.
Pero esta deficiencia se ve altamente recompensada por la disponibilidad pública, y la seguridad que le
Un dispositivo lógico programable es un circuito integrado, formado por una matriz de puertas lógicas
y flip-flops, que proporcionan una solucion al diseño de forma análogas, a las soluciones de suma de
productos, productos de sumas y multiplexores.
La estructura básica de una PLD permite realizar cualquier tipo de circuito conbinacional basándose en
una matriz formada por puertas AND, seguida de una matriz de puertas OR. Tres son los tipos más
estendidos de PLD's, la PROM, PLA, y la PAL.
Este tipo de dispositivo se basa en la utilización de una matriz AND fija, seguida de una matriz OR
programable. La matriz programable esta formada por líneas distribuidas en filas y columnas en las
cuales los puntos de cruce quedaran fijos por unos diodos en serie con unos fusibles que serán los
encargados de aislar las uniones donde no se requiera la funcion lógica.
La fase de programación se realiza haciendo circular una corriente capaz de fundir el fusible en
aquellas uniones donde no se desee continuidad. Por otra parte, para cada combinacion de las señales de
entrada, el codificador activa una única fila y a su vez activa aquella columna a las que esta todavía unida
a travéz del diodo.
Parecido en la dispositivo a la PROM, difiere de esta, en que aquí en la PLD , ambas matrices, la de
puertas And, así como la de puertas Or es programable, por lo que nos vemos habilitados a incrementar
el número de entradas disponibles, sin aumentar el tamaño de la matriz. Esta estructura permite una
mejor utilización de los recursos disponibles en el circuito integrado, de tal forma que se genera el
Una PAL es diferente de una PROM a causa de que tiene una red Y programable y una red O fija. Con
un programador Prom podemos obtener los productos fundamentales deseados quemando los eslabones y
luego conseguir la suma lógica de dichos productos mediante las conexiones fijas de salida.
El lenguaje VHDL está creado específicamente para el diseño de hardware, es decir, podremos
implementar con él multitud de circuitos lógicos, tanto combinacionales como secuenciales. Éste
lenguaje también nos permite describir elementos más complejos, como CPU's (Unidad Central de
Procesamiento), manejar ficheros, retrasos en el tiempo, etc. pero no siempre se puede implementarlos;
tan sólo, y en según que casos, se llegará a la simulación. Este libro se centra en el VHDL sintetizable, es
decir, con el que es posible llegar a grabar un dispositivo lógico programable.
Un programa en VHDL consta de dos partes. La primera, la entidad, nos sirve para relacionar nuestro
diseño con el mundo exterior, es decir, analizamos lo que tratamos de crear como una "caja negra", de la
que sólo conocemos sus entradas, sus salidas y la disposición de las mismas. La segunda parte, la
arquitectura, describe como trata el circuito la infomación correspondiente a las entradas para obtener las
salidas.
Esta comportamiento de los circuitos reales obliga a que VHDL soporte estructuras específicas para el
modelado y diseño de este tipo de especificaciones de tiempos y concurrencias en el cambio de las
distintas señales digitales de los diseños.
Por el contrario, los asignamientos secuenciales, son más bien propios de los SDL (soft design
lenguage) en los que la programación tiene un flujo natural secuencializado, siendo propio de este tipo de
eventos las sentencias case, if, while, loop, etc más propias de estas sintaxis.
Las construcciones concurrentes del lenguaje son usadas dentro de estructuras concurrentes, por
ejemplo una arquitectura tiene una naturaleza eminentemente concurrente (es decir que está activo todo
el tiempo), mientras que el cuerpo de un process es en principio eminentemente secuencial. La
asignacion de eventos secuenciales dentro de una estructura concurrente se ejecutará de forma
concurrente, es decir, al mismo tiempo que las demás sentencias.
VHDL soporta con este motivo, tres típos de objetos, las variables, las constantes y las señales . Como
las variables y las señales pueden variar su valor mientras ejecutamos un programa, serán éstas las
encargadas de almacenar dichos datos, asimismo serán los portadores de la información. Únicamente las
señales pueden tener la connotación de globalidad dentro de un programa, es decir, que pueden ser
empleadas dentro de cualquier parte del programa a diferencia de las variables que solo tienen sentido en
el interior de un process .
Los process son estructuras concurrentes constituidas por sentencias de ejecución secuencial, esto
provocará que dentro de un process nos encontremos con sentencias similares a las de los SDL (lenguajes
de descripción de software) que nos llevan a emplear VHDL como si de otro lenguaje común se tratara.
Dentro de un process nos podemos encontrar con la declaración y utilización de las variables como
parámetros locales al process .
De ejecución secuencial, las variables evaluan su valor dentro del cuerpo del proceso de forma
inmediata, sin consumir tiempo de ejecución, pero como están dentro de un process, que es una
estructura concurrente, este valor no será asumido, sino hasta el final de la ejecución de dicho process.
w<= not a;
x <= a and b;
y <= c and w;
z <= x or y;
● Paquetes
Como hemos dicho, cada señal en una declaración de entidad está referida a un puerto (o grupo de
señales), el cual es análogo a un(os) pin(es) del símbolo esquemático. Un puerto es un objeto de
información, el cual, puede ser usado en expresiones y al cual se le pueden asignar valores. A cada
puerto se le debe asignar un nombre válido. A continuación se exponen algunos ejemplos:
Como se puede deducir de los ejemplos anteriores, seguido del nombre del puerto y separado de éste
por dos puntos, se debe indicar el tipo de puerto. El modo describe la dirección en la cual la información
es transmitida a través del puerto: in, out, buffer e inout. Si no se especifica nada, se asume que el puerto
es del modo in.
Modo out: Un puerto es de modo out si la información fluye hacia fuera de la entidad.
Este modo no permite realimentación ya que al declarar un puerto como out estamos
indicando al compilador que el estado lógico en el que se encuentra no es leíble. Esto le da
una cierta desventaja pero a cambio consume menos recursos de nuestros dispositivos
lógicos programables.
Modo buffer: Es usado para una realimentación interna ,es decir, para usar este puerto
como un driver dentro de la entidad. Este modo es similar al modo out, pero además,
permite la realimentación y no es bidireccional, y solo puede ser conectado directamente a
una señal interna, o a un puerto de modo buffer de otra entidad. Una aplicación muy
común de este modo es la de salida de un contador, ya que debemos saber la salida en el
momento actual para determinar a salida en el momento siguiente.
Modo inout: Es usado para señales bidireccionales, es decir, si necesitamos que por el
mismo puerto fluya información tanto hacia dentro como hacia afuera de la entidad. Este
modo permite la realimentación interna y puede reemplazar a cualquiera de los modos
anteriores, pudiéndose usar este modo para todos los puertos, pero reduciremos la lectura
posterior del código por otra persona, y reduciendo los recursos disponibles de la
dispositivo.
Como se ha comentado más arriba, el lenguaje sólo admite cuatro modos para los puertos, pero puede
haber tantos tipos de señales como queramos, ya que los podemos crear nosotros mismos. VHDL
incorpora varios tipos de forma estándar (por haber sido creado así), pudiendo usar otros definidos en
librerías normalizadas, y las creados por el usuario. La norma internacional IEEE 1076/93 define cuatro
tipos nativos para VHDL como son:
Tipo boolean: puede tomar dos valores: verdadero/true o falso/false. Un ejemplo típico
es la salida de un comparador que da verdadero si los números comparados son iguales y
falso si no lo son:
Tipo bit: Puede tomar dos valores: 0 ó 1 ( o también "low" o "high", según se prefiera).
Es el tipo más usado de los nativos.
Tipo bit_vector: Es un vector de bits. Debemos tener cuidado al definir el peso de los
bits que lo integran, ya que según pongamos la palabra reservada downto o to estaremos
diciendo que el bit más significativo es el número más alto o el más bajo del vector,
respectivamente..
Tipo integer: Para manejar números enteros. Hay que advertir que el uso de enteros
consume muchos recursos del dipositivo de lógica programable, siempre y cuando sea
sintetizable, debido a que está prácticamente creado para la simulación.
Pero ante la necesidad de ampliar la operatividad del tipo bit, la norma IEEE 1164, definió un nuevo
tipo llamado std_logic, std_ulogic, y sus derivados tales como std_logic_vector y std_ulogic_vector.
Como su nombre pretende indicar, es el tipo de tipo lógico estándar, que es el más usado en la actualidad,
así como en la mayoría de ejemplos de este libro.
Debemos recordar dos puntos más a la hora de dar el nombre a algún puerto, que se tratarán más
adelante en el aprtado de objetos:
VHDL no distingue las letras mayúsculas de las minúsculas, por lo que un puerto
llamado por nosotros "EnTraDA" será equivalente a otro que se llame "ENTRADA" o
"entrada". Pulsa aquí para ir a una práctica donde se demuestra esto.
El primer carácter de un puerto sólo puede ser una letra, nunca un número. Así mismo,
no pueden contener caracteres especiales como $, %, ^, @, ... y dos caracteres de
subrayado seguidos.
Estos dos detalles a tener en cuenta surgieron del comité que creó este lenguaje, por lo que no se debe
considerar como un fallo de nuestra herramienta (WARP2), sino como una característica más del
lenguaje.
La arquitectura indica el tipo de procesado que se realiza con la información correspondiente a las
señales de entrada, (declarados previamente en la entidad) para llegar a tener los puertos de salida
(también declarados en la entidad). En la declaración de arquitecturas es donde reside todo el
funcionamiento de un circuito, ya que es ahí donde se indica que hacer con cada entrada, para obtener la
salida. Si la entidad es vista como una "caja negra", para la cual lo único importante son las entradas y las
salidas, entonces, la arquitectura es el conjunto de detalles interiores de la caja negra.
La declaración de arquitecturas debe constar de las siguientes partes como mínimo, aunque suelen ser
más:
Como podemos apreciar, es una estructura muy sencilla, y que guarda alguna relación con Turbo
Pascal. Las sentencias entre begin y end son las que describen el circuito, y es en lo que se centra tanto
este libro electrónico como cualquier otro que trate sobre VHDL. A continuación, se muestra el código
fuente de un programa en VHDL de un multiplexor (esta es una de las múltiples formas de implementar
un multiplexor en VHDL), el cual debe ir unido a la entidad expuesta en el apartado de la declaración de
entidades, ya que una parte sin la otra carecen de sentido.
Para describir una arquitectura podremos usar cuatro estilos, teniendo cada uno, su propio nivel de
abstracción. Los estilos son:
Estilo dataflow o flujo de datos: Este estilo podremos encontrarlo de dos formas
similares, pero ambas implican cómo la información será transferida de señal a señal y de
la entrada a la salida sin el uso de asignaciones secuenciales, sino concurrentes. Es decir,
en este estilo no se pueden usar los procesos. El comparador descrito de forma behavioral
o de comportamiento se puede escribir usando el estilo dataflow de cualquiera de las dos
formas siguientes:
Estilo mixto: Es el estilo que está compuesto en mayor o menor medida de dos o más
de los estilos descritos anteriormente.
Deberemos tener en cuenta que el código VHDL que escribamos no siempre va a describir una función
de forma óptima, la cual no siempre va a poder ser reducida por la herramienta de compilación. Esto se
traduce en un peor aprovechamiento de los recursos de las PLD's. Por lo tanto, diferentes diseños
producen diferentes, aunque equivalentes, ecuaciones de diseño, pudiéndose dar, sin embargo,
disposiciones diferentes de los recursos.
Para concluir el apartado dedicado a las arquitecturas sólo resta el recordar que tanto la entidad y la
arquitectura deben ir unidas en el mismo fichero, ya que una parte carece de sentido sin la otra. Pulsa
aquí para ver el proceso a seguir en la compilación.
Paquetes
package nombre_del_package is
-- declaración de procedures
Esta parte es a una entidad, lo mismo que un
-- declaración de funciones paquete es a un programa normal en VHDL.
-- declaración de tipos, etc...
end nombre_del_package;
-- definición de procedures
Esta parte se corresponde con una arquitectura.
-- definición de funciones
-- definición de tipos, etc.
end nombre_del_package;
Todo esto hace posible que una vez declarados los subprogramas dentro de un package, podamos
utilizarlos haciendo únicamente uso de una llamada al proceso, asignándole un nombre y la lista de
parámetros necesarios.
Para poder usar o llamar a un package que ha sido descrito anteriormente, debemos incluir la cláusula
use antes del cuerpo de la arquitectura.
Hay ocasiones en las que deseamos habilitar todos los componentes declarados en un paquete, ya sea
por comodidad o por no saber exactamente donde se encuentra el recurso que deseamos usar. En tal caso,
haríamos uso de la palabra all de la siguiente forma:
elsif(enable1='1') then
case seleccion is
when "00" => salida <= "0001";
when "01" => salida <= "0010";
when "10" => salida <= "0100";
when "11" => salida <= "1000";
when others => salida <="1111";
end case;
end if;
end process decodificador;
end archidecoder;
Para poder hacer uso, de cualquier componente de el package "uno" (el decodificador o el
contador),es necesario que primero se incluya la sentencia:
use work.uno.all;
La LPM fue propuesto en 1990 como una extensión del estándar Electronic Design Interface Format
(EDIF) para permitir al diseñador crear un circuito completo con independencia de la arquitectura,
separando las partes físicas y lógicas del diseño
Cada componente en la librería esta definido por unos parámetros, que le permiten al diseñador de
hardware representar una amplia variedad de variables lógicas, evitando la repetición del trabajo.
La librería LPM System Library, en la cual se nos ofrecen múltiples módulos, ya creados, puede
facilitarnos enormemente nuestro trabajo, ya que incluye desde el generador de constantes más sencillo
hasta contadores y multiplicadores con todas las características opcionales posibles. La lista de
componentes incluida en esta es la siguiente:
Módulo de
MADD_SUB
sumadores/restadores
Para usar cualquiera de éstos módulos, sólamente deberemos incluir en nuestro fichero de código la
siguiente línea, encima de la declaración de entidades y de arquitecturas:
use work.lpmpkg.all;
Como ejemplo crearemos un multiplicador de 4 bits (a y b), cuyo resultado, obviamente, debe se de 8
bits (p), y que nos permite además sumarle otro número de ocho bits (s) al resultado. Para ello deberemos
invocar previamente a la librería lpmpkg tal y como habíamos dicho:
library ieee;
use ieee.std_logic_1164.all;
use work.lpmpkg.all;
entity multi_lpm is port(
a,b: in std_logic_vector(3 downto Llamamos a lpmpkg
0);
s: in std_logic_vector(7 downto Vamos a multiplicar a y b
0); Y vamos a sumar s
p: out std_logic_vector(7 downto El resultado es p
0)
);
end multi_lpm;
Llamamos a lpmpkg
use work.lpmpkg.all;
architecture archimulti of multi_lpm
is
begin Usamos el módulo mmult
a0: mmult generic map(4,4,8,8)
port map(a,b,s,p);
end archimulti;
En este ejemplo hemos hecho uso del módulo mmult, el cual nos exige no sólo introducirle las señales
con las cuales operar (con la sentencia port map) sino también su dimensión (sentencia generic map).
Pulsa aquí para ir al ejemplo del multiplicador.
Como cada módulo tiene unas especificaciones de entradas, salida y dimensiones distintas, tendremos
que consultar el manual de la librería lpmpkg para conocer que entradas y salidas necesita cada módulo.
Podemos acceder a esta documentación siguiendo estos pasos:
Una vez realizado estos pasos, aparecerá debajo de estas líneas la documentación
sobre la librería lpmpkg, en el capítulo 5 del documento. Si no te aparece puedes bajarlo
desde aqui: refmanl.pdf
Objetos
Identificadores
Palabras reservadas
Símbolos especiales
Tipos de datos
Expresiones y operadores
Objetos
En un lenguaje de descripción de software (SDL) una variable contiene un valor y puede aceptar un nuevo valor a través de una
asignación secuencial. Por otro lado, las constantes tienen valores prefijados a lo largo de toda la ejecución del programa. Sin embargo,
en VHDL se hace necesaria la utilización de un nuevo tipo de objeto que puede emular las asignaciones concurrentes propias de los
circuitos eléctricos reales; este nuevo tipo de objeto son las señales.
Un objeto en VHDL es un elemento que tiene asignado un valor de un tipo determinado. Según sea el tipo de dato, el objeto poseerá
un conjunto de operaciones que se le podrán aplicar. En general, no será posible realizar operaciones entre dos objetos de distinto tipo,
a menos que definamos previamente un programa de conversión de tipos.
Identificadores
Los identificadores son un conjunto de caracteres dispuestos de una forma adecuada y siguiendo unas normas propias del lenguaje,
para dar un nombre a los elementos en VHDL, por lo que es aconsejable elegir un nombre que sea representativo y que facilite la
comprensión del código.
Los identificadores deben empezar con un carácter alfabético, no pudiendo terminar con un carácter subrayado, ni
VHDL identifica indistintamente tanto las mayúsculas como las minúculas, pudiéndose emplear por igual el
identificador "sumador" o "SUMADOR". Pulsa aquí para ir a la práctica del sumador, donde se demuestra la indiferencia
de mayúsculas y minúsculas.
El tamaño o extensión del identificador no está fijado por VHDL, siendo recomendable que el usuario elija un
tamaño que confiera sentido y significado al identificador, sin llegar a alcanzar longitudes excesivamente largas.
Los identificadores pueden contener caracteres numéricos del '0' al '9', sin que éstos puedan aparecer al principio.
Palabras reservadas
Las palabras reservadas son un conjunto de identificadores que tienen un significado específico en VHDL. Estas palabras son
empleadas dentro del lenguaje a la hora de realizar un diseño. Por esta razón y buscando obtener claridad en el lenguaje, las palabras
reservadas no pueden ser empleadas como identificadores definidos por el usuario.
Símbolos especiales
Además de las palabras reservadas empleadas como identificadores predefinidos, VHDL utiliza algunos símbolos especiales con
funciones diferentes y específicas, tales como el símbolo "+" se utiliza para representar la operación suma y, en este caso, es un
operador. El símbolo "- -" es empleado para los comentarios realizados por el usuario, de tal forma que el programa al encontrar una
instrucción precedida por "- -" la saltará ignorando su contenido. De esta forma, el programador puede hacer más comprensible el
código del programa.
Para finalizar, recordar que el símbolo más empleado por un programador es el " ; ", símbolo que debe finalizar todas y cada una
de las líneas del código dando por terminada dicha sentencia en el programa.
Tipos de datos
El tipo de datos es un elemento básico en VHDL, ya que delimita que valores puede tenr un objeto y que operaciones podemos
realizar con él. Aparte de los tipos ya creados, podemos crear nuevos tipos y subconjuntos de tipos.
La declaración de un tipo de datos es la sentencia VHDL utilizada para introducir un nuevo tipo. Esta declaración está formada por
un identidificador que nos permitirá usar el nuevo tipo al llamarlo y la descripción del conjunto de valores que forman el tipo de datos.
Para ello usamos la palabra reservada type. La declaración puede tener varios formatos como por ejemplo:
Una vez declarado el nuevo tipo podremos usarlo para declarar objetos de este tipo, como por ejemplo:
Cada tipo es diferente e incompatible con los demás, aunque estén declarados de la misma forma, por lo cual no podemos asignar a
una señal de un tipo otra de otro tipo distinto, a menos que definamos una función de transformación.
Los tipos pueden ser clasificados según las características de lo que van a determinar:
Tipos enumerados: En éste se define el conjunto de posibles valores del tipo especificado, presentando una lista que
contiene a todos los valores. El primer identificador es el nombre del tipo y sirve para referenciarlo, y entre paréntesis y
separados por comas se adjuntan todos los valores legales del tipo.
Si no está especificado ningún valor inicial, el objeto se inicializa con el valor más a la izquierda de los especificados en
la declaración del tipo. Es decir, un objeto del tipo "vocales" toma el valor 'a' por defecto.
Tipos enteros / reales: Esta modalidad de tipo sirve apra definir un objeto con valores reales y enteros. En VHDL
vienen definidos el tipo integer, que puede ir desde -2147483647 hasta 2147483647, y el tipo real, quepuede ir desde -
1.0e38 hasta 1.0e38. Para definir un tipo de esta naturaleza hay que especificar el rango de valores que puede llegar a
tener asignado un objeto, como en los ejemplos siguientes
Si no está especificado ningún valor inicial, el objeto se inicializa con el valor más a la izquierda de los especificados en
la declaración del tipo. Deberemos tener cuidado si hemos usado la palabra to o la palabra downto para definir el tipo, ya
que se asignará un valor por defecto u otro. En el ejemplo se da por defecto a un objeto del tipo "edad" el valor 0, y a
otro del tipo "dias", el valor 31.
Tipos fisicos: Sirven para representar magnitudes del mundo real como el tiempo, peso, capacidad,... por lo que
llevan, aparte de un literal numérico, la magnitud física a medir. Podemos asignar unidades auxiliares a la predefinida.
Si no está especificado ningún valor inicial, el objeto se inicializa con el valor más a la izquierda de los especificados en
la declaración del tipo. Deberemos tener cuidado si hemos usado la palabra to o la palabra downto para definir el tipo, ya
que se asignará un valor por defecto u otro. En el ejemplo se da por defecto a un objeto del tipo "edad" el valor 0, y a
otro del tipo "dias", el valor 31.
Expresiones y operadores
La metodología de programación de un componente, basada en la descripción por comportamiento (behavioral), puede emplear en
su diseño la mayoría de operadores que se encuentran habitualmente útiles en los SDL's ( software design languages)
En el cuadro adjunto se puede ver una relación de los operadores predefinidos más empleados en VHDL, así mismo se aprecia que
su clasificación atiende al tipo de dato que vaya a manejar:
Los operadores lógicos, pueden ser empleados con los tipos predefinidos, BIT y BOOLEAN, dándonos como resultado un valor
booleano del mismo tipo que los operadores.
Para graficar un poco la importancia de emplear correctamente los distintos tipos de operadores, a continuación lo ilustramos con la
ayuda del diseño de un sumador de cuatro bits mas el carry de la etapa anterior. Pulsa aquí para ir a la práctica del Cuádruple Sumador
Total.
Los operadores relacionales también nos generaran un resultado de tipo booleano sin importar el tipo de operando con el que lo
empleemos, como nota comentar que el operador "diferente que" viene dado por la combinación " /= ".
Los operandos de tipo aritméticos están en la obligación de ser empleados con elementos del mismo tipo y de devolver un resultado
que a su vez este contenido en el mismo tipo que los operandos.EL signo "+", "-" dará como resultado sumas de unidades es decir que
al elemento sumado le añade uno,(como se aprecia en el anterior ejemplo).
El operador concadenacíon es empleado para concadenar arrays de bit´s, como muestra se grafíca este operador con el siguiente
ejemplo.
aux:=('0' & a)
Los elementos a concadenar deben de ser del mismo tipo. Este tipo de operador es muy empleado a la hora de trabajar con buses o
registros. El resultado que nos devuelve este operador es un array de los elementos de los operandos concatenados. En nuestro ejemplo,
si "a" es un array de cuatro bits, la variable "aux", pasara a tener en su primera poscicion el valor "0" y a continuacion tendra el valor
de los elementos de "a", de forma que "aux" pasa a tener dimension cinco.
Un atributo nos proporciona información sobre ciertos elementos como las entidades, arquitecturas, tipos y señales. Hay varios
atributos de para señales que son muy útiles en síntesis, y especialmente en el VHDL simulable (no sintetizable). Ai ae trabaja con
VHDL sintetizable sólo se pueden utilizar algunos atributos como:
Atributo 'event: se usa para conocer si una variable ha cambiado o no, soliéndose usar como variable booleana:
Sentencia loop
Sentencias secuenciales
Sentencia exit
Asignación a una señal
Sentencia next
Asignación a variable
Sentencia null
Sentencia if
Sentencia wait
Sentencia case
Sentencia wait
until
Sentencias secuenciales
En la mayoría de los lenguajes de descripción de software, todas las sentencias de asignamiento son de
naturaleza secuencial. Esto significa que la ejecución del programa se llevara a cabo de arriba a abajo, es
decir siguiendo el orden en el que se hayan dispuesto dichas sentencias en el programa, por ello es de
vital importancia la disposición de las mismas dentro del código fuente.
VHDL lleva a cabo las asignaciones a señales dentro del cuerpo de un proceso (process) de forma
secuencial, con lo que el orden en el que aparezcan las distintas asignaciones será el tenido en cuenta a la
hora de la compilación. Esto hace que cuando utilicemos modelos secuenciales en VHDL, estos se
comporten de forma parecida a cualquier otro lenguaje de programación como Pascal, C, etc.
Podremos asignar un cierto valor a una señal siempre que ésta haya sido declarada en la entidad en el
apartado de declaración de puertos, o bien porque la hayamos creado específicamente dentro de un
process.
La asignación a una señal dentro de un proceso es siempre secuencial, es decir, la señal no cambia su
valor hasta que se ha evaluado el proceso en el cual se incluye. Si no está dentro de un proceso, como por
ejemplo, usando el estilo dataflow, el cambio es concurrente, es decir, la asignación está siempre activa,
y se actualiza instanteneamente. Para hacer una asignación a una señal deberemos usar el operador <=,
estando la señal a asignar a la izquierda y el valor que debe tomar a la derecha.
Las señales son el objeto más usado dentro de la síntesis de diseños, teniendo la particularidad de que
los cambios en una señal son programados, no son inmediatos. Esto resulta extraño, sobre todo si
comparamos su comportamiento con el de las variables, y para dejar claro este concepto veamos el
siguiente ejemplo:
process
begin
a <= b; La señal a tendrá el valor b
b <= a; La señal b tendrá el valor a
wait on a,b;
end process; Se acualizan los cambios AQUÍ
En este ejemplo, las dos señales intercambian sus valores, ya que cuando se ejecuta la segunda (b<=a),
el valor de la señal a no ha cambiado todavía aunque esté la sentencia a<=b, ya que ninguna señal
cambia de valor hasta que se hayan evaluado todas las órdenes de un proceso, y es en ese momento
cuando a y b toman el valor que se les ha indicado tomar.
Asignación a variable
La asignación a una variable viene totalmente influenciada por su propia naturaleza, la cual hace que
sólo tengan su campo de acción en un proceso o un subprograma (areas secuenciales). Una variable no
retiene sus valores dentro de un subprograma después de cada llamada, soliendo usarse como índices
para ciclos loop y para asignaciones inmediatas. Si queremos usar una variable fuera de un proceso,
deberemos asignar su valor a una señal, y operar con la señal fuera. Para hacer el asignamiento a una
variable deberemos usar el operador := , como se muestra en los ejemplos siguientes.
a toma el valor b
a := b;
b toma el NUEVO valor de a (el de
b := a;
b)
En esta ocasión, no se intercambiarán los valores de ambas señales, sino que acabarán con el valor
inicial de la variable b. Para conseguir que se intercambien las variables, deberemos usar una variable
temporal de la siguiente forma:
Deberemos tener en cuenta que una variable se reinicializa siempre al hacer una nueva llamda a un
subprograma.
Sentencia if
if (condición) then
haz una cosa;
else
haz otra cosa diferente;
end if;
La sentencia if-then-else puede ser expandida para incluir la sentencia elsif, la cual nos permite incluir
una segunda condición si no se ha cumplido la primera (la cual tiene prioridad). Su estructura es la
siguiente:
if (condición) then
haz una cosa;
elsif (otra condición) then
haz otra cosa diferente;
else
haz otra totalmente diferente;
end if;
Si se da la situación en la cual la primera condición es verdad ejecuta las sentencias que van después
del primer then. Si no es verdadera la primera condición, se pasa a evaluar la segunda, y de ser esta
verdad, ejecuta las sentencias que están a continuación del segundo then. Si ninguna de las dos es
verdadera, se ejecuta lo que está detrás de la palabra else. Nótese que para que se ejecute las sentencias
con el nombre "otra cosas diferente", no solo debe ser la segunda condición verdadera, sino que además
la primera condición debe ser falsa. Un ejemplo de esta sentencia se puede ver a continuación:
Deberemos tener cuidado al usar una sentencia if-then-else ya que podemos caer en el error de la
memoria implícita. Si no incluimos la palabra else, y si no se cumple la primera condición, las señales de
salida no cambian, reteniendo el valor previo a la sentencia if. Esto puede ayudarnos (flip-flops) o bien
hacer que un diseño sea totalmente erróneo. Analicémoslo con un ejemplo:
En este caso se dice que debe ejecutarse si hay un flanco de subida en la señal clk, pero no se dice
nada si no hay cambios o si el flanco es de bajada. Es en este caso se ha definido una memoria implícita,
y VHDL hace que la señal q siga con el valor que tenía antes de ejecutarse la sentencia if. Esto hace que
este código y el que se lista a continuación sean equivalentes:
Esto hace que se recomiende siempre el especificar con else que debe pasar con las señales si no se
cumpla la condición indicada con el if. En la parte práctica hay varios ejemplso con la sentencia if, pulsa
aquí para ir a la parte práctica.
del circuito en aquellos casos en los que no se cumple la condicion de una sentencia if, o de una
sentencia case.
Sentencia case
La sentencia case es usada para especificar una serie de aciones según el valor dado de una señal de
selección. Esta sentencia es equivalente a la sentencia with-select-when, con la salvedad que la sentencia
que nos ocupa es secuencial, no combinacional. La estructura es la siguiente:
En el caso que la señal a evaluar (situada después del case) tenga el "valor 1", entonces se ejecuta "una
cosa", si tiene el "valor 2", se ejecuta "otra cosa", ... y si tiene el "último valor", se ejecuta "tal cosa". Esta
sentencia parece hecha a la medida para crear multiplexores, como se ve en el siguiente ejemplo, del cual
sólo estudiamos la parte correspondiente a la sentencia case:
begin
if enable='1' then d<="1111"; se evalúa la señal control
elsif enable='0' then si control vale "00" entonces
case control is d<=a
when "00" => d <= a; si control vale "01" entonces
when "01" => d <= b; d<=b
when "10" => d <= c; si control vale "10" entonces
when "11" => d <= "1111"; d<=c
end case; si control vale "11" d valdrá
end if; "1111"
end process; se cierra la sentencia case con
end archmul; end
se cierra la sentencia if
finaliza la arquitectura
Esta sentencia es muy típica de los lenguajes de programación y cuenta además con una variante, muy
importante en VHDL, como es dar un valor especificado a la cierta señal si no se contemplan todos los
casos posibles de la señal a evaluar. Para ello se usa la palabra reservada others, que aparece en el
siguiente fragmento de código:
Al igual que era recomendable especificar un else dentro de cada if, se recomienda, con más razón el
uso de others dentro de un case aunque en un rpincipio paresca que estan especificados todos los valores
posibles, ya que algunos tipos soportan más valores aparte del nivel uno y cero lógico, como el estado Z
o de alta impedancia. Pulsa aquí para ir a la práctica del multiplexor.
La sentencia case también nos permite especificar un rango de valores posibles de la señal de
selección, para los cuales hacer una asignación, mediante la palabra reservada to. Como ejemplo veamos
dos fragmentos de código que son equivalentes:
case control is
when "000" => d <= a;
when "001" => d <= a;
case control is
when "010" => d <= a;
when "000" to "010" => d <= a;
when "011" => d <= b;
when "011" to "110" => d <= b;
when "100" => d <= b;
when "111" => d <= c;
when "101" => d <= b;
when others => d <= null;
when "110" => d <= b;
end case;
when "111" => d <= c;
when others => d <= null;
end case;
Sentencia loop
La sentencia loop (ciclo en castellano) se usa para ejecutar un grupo de sentencias un número
determinado de veces, y consiste tanto en un ciclo for como en un ciclo while. La sentencia for se
ejecuta un número específico de iteraciones basado en el valor de una variable. La sentencia while
continuará ejcutándo una operación mientra una condición de control local sea cierta. Estas sentencias
son muy típicas de los lenguajes más usuales de programación, usándose habitualmente para ello la
variable i. A continuación se listan sendos ejemplos para cada caso:
process (a)
begin
ciclo1: for i in 7 downto 0 loop
entrada(i) <= ( others => '0' Cabecera del ciclo
) Instrucciones a ejecutar 8 veces
end loop; Finalización del ciclo
end process;
Este fragmento de código hace que uno por uno los 8 últimos bits de la señal entrada tengan el valor
lógico cero. Que se indique que deben hacerse cero 8 bits, no quiere decir que la señal entrada esté
formada por más de ocho. El encabezamiento de la sentencia nos dice que se va a repetir ocho veces
seguidas las instrucciones entre loop y end loop. Debemos destacar que en un ciclo for, la variable del
mismo se declara atomáticamente, y no tenemos que incrementarla o inicializarla, ya que el programa lo
hace por nosotros. La etiqueta ciclo1 se usa (aunque es opcional) para dar más posibilidades de
organización al programador.
process (a)
variable i: integer := 0;
begin
Mientras i sea menor que 7 =>
ciclo2: while i < 7 loop
ciclo
entrada(i) <= (others => '0');
i := i + 1;
end loop;
Finaliza el ciclo
end process;
En esta ocasión se usa la sentencia while, para lograr el mismo resultado que en el ejemplo anterior, es
decir, inicializar los 8 últimos bits del vector entrada a cero. Esta vez, se ejecutan las líneas entre loop y
end loop mientras la variable i sea menor que siete. Es lógico que esta variable cambie dentro del ciclo,
ya que de no ser así, se ejecutaría para siempre. De forma distinta al ejemplo anterior, la variable del
ciclo while debe ser declarada (como integer), inicializada e incrementada por nosotros, por lo que
requiere más trabajo.
Sentencia exit
Usada siempre dentro de un loop, la sentencia exit nos permite salir del mismo si se alcanza una
condición fijada por nosotros. Su verdadera utilidad la encontramos si diseñamos controladores de
memoria. A continuación analizamos un ejemplo:
process (a)
begin
ciclo1: for i in 7 downto 0 loop
if a'length < i then exit
ciclo1;
entrada(i) <= ( others => '0' );
end loop;
end process;
Ahora se ejecuta el mismo ejemplo que expusimos en el caso de un ciclo for con la salvedad que si la
variable i supera la longitud del vector a el ciclo deja de ejecutarse aunque no se hayan cumplido las
veces pedidas en el encabezamiento.
Sentencia next
La sentencia next también debe estar dentro de un ciclo loop, y nos sirve para saltarnos una o más de
las ejecuciones programadas.
process (a)
begin
ciclo1: for i in 7 downto 0 loop
Cabecera del ciclo
if i=4 then next;
Si i vale 4, se salta el ciclo
else
Si no vale 4,...
entrada(i) <= ( others => '0'
... se inicializa entrada
);
end if;
Finaliza el ciclo
end loop;
end process;
Ahora se ejecuta el programa todas las veces programadas, excepto la cuarta, ya que hay una sentencia
if que hace que se pase a la siguiente iteración si la varaibale i vale cuatro.
Sentencia null
La sentencia null se utiliza, al igual que en otros lenguajes de programación, para que dada una
condición especial no pase nada , es decir, que ninguna señal o variable cambie, y que el programa siga
su curso habitual. Su comportamiento dentro de un loop es similar al de la sentencia next.
Sentencia wait
La sentencia wait se usa para suspender un proceso si éste no tiene lista sensitiva. Lo único que exige
esta sentencia es estar situada la final del proceso. Para entender mejor ésto, basta con decir que los dos
códigos siguientes son equivalentes.
process
process (a,b,c)
begin
begin
x <= a and b and c;
x <= a and b and c;
wait on a,b,c;
end process;
end process;
Ambos procesos se ejecutarán cuando haya un cambio en la señal a, b o c se muestra en este caso la
equivalencia de una lista sensitiva y una sentencia wait explícita.
No debemos confundir la sentencia wait until con la sentencia wait vista en el apartado anterior. En
esta ocasión, tampoco se puede usar una lsita sensitiva con el proceso, ya que ésta queda definida con la
misma sentencia. Para las descripciones la fórmula wait until debe estar al prncipio del proceso, por lo
que la lógica descrita de esta menera no puede ser reseteada asíncronamente. Veamos su uso en el caso
de la arquitectura de un flip-flop d:
Este proceso se suspende hasta que la condición siguiente a wait until es verdadera. Una vez que es
verdad, y las asignaciones de señal se han realizado, el proceso vuelve a esperar hasta que la señal clk
vuelve a valer de nuevo 1, es decir, un flanco de subida. Pulsa aquí para ir a la práctica del Flip Flop D.
Llamada concurrente a
Sentencias concurrentes
subprograma
Sentencia Process
Sentencias estructurales
Asignación concurrente a señal
Subprogramas
Asignación concurrente condicional
Funciones
Asignación concurrente con selección
Procedimientos
Sentencias concurrentes
La naturaleza propia de los circuitos eléctricos obliga a VHDL a soportar un nuevo tipo de asignación de señales, que nos permita
implementar este tipo de operatividad. En ella todas las asignaciones se llevan a cabo en paralelo (al mismo tiempo). En una asignación
concurrente la señal que esté a la izquierda de la asignación es evaluada siempre que alguna de las señales de la derecha modifique su
valor. Como ejemplo tenemos las siguientes sentencias de asignación:
c <= a and b;
s <= a xor b;
Si las señales de entrada (situadas a la derecha), a o b, cambian su valor, las señales de salida (situadas a la izquierda), c y s, son
evaluadas, de forma que su valor se verá modificado si fuese necesario
Sentencia Process
La sentencia process es una de las construcciones típicas de VHDL usadas para agrupar algoritmos. Esta sentencia se inicia (de
forma opcional) con una etiqueta seguida de dos puntos ( : ), después la palabra reservada process y una lista de variables sensibles. La
lista sensible, indica que señales harán que se ejecuta el proceso, es decir, qué variable(s) debe(n) cambiar para que se ejecute el
proceso. Dentro de un proceso se encuentran sentencias secuenciales, no concurrentes. Esto hace que el orden de las órdenes dentro de
un proceso sea importante, ya que se ejecuta una después de otra, y los posibles cambios que deba haber en las señales alteradas se
producen después de evaluar todo el ciclo al completo. Esta característica define una de las particularidades de VHDL.
Siempre que queramos utilizar sentencias secuenciales se deberá usar un proceso. Como ejemplo del uso de un proceso se muestra el
siguiente fragmento de código correspondiente a un flip-flop d.
Es muy importante tener en cuenta dos cosas a la hora de usar un proceso respecto a las señales y las variables. La primera es que las
variables toman instantáneamente el valor especificado y que sólo tienen sentido de existencia dentro de un proceso. La segunda es que
las señales cambian su valor solamente al llegar al final del proceso. Esto se aprecia claramente en los dos ejemplos siguientes,cuyo
comportamiento se pretende que sea el siguiente (suponiendo que hemos cargado el operador +):
Si a tiene el valor "0000", entonces la salida/entrada b deberá incrementar su valor en una unidad, y si después e haber incrementado
la señal b, ésta vale "0001", c deberá valer '1', si no es así deberá valer '0'.
El primer ejemplo no funciona adecuadamente si b tiene le valor inicial de "0000". Veamos porqué: si y a cambia para tomar el
valor "0000", b se incrementará en una unidad al finalizar la evaluación del proceso, por lo que b no valdrá "0001" hasta entonces, por
lo que c no valdrá '1', que no era lo que deseábamos.
El segundo ejemplo si que funciona como habíamos especificado para cualquier valor de b. Si a cambia y toma el valor "0000", la
variable v toma instantáneamente el valor inicial de b (esto es "0000"), y se incrementará en una unidad instantáneamente por lo que
valdrá "0001". Entonces c tomará el nivel lógico alto, y seguido a esto, b tomará el valor incrementado de v. Los nuevos valores de b y
c aparecerán a la salida al acabar la evaluación completa del proceso.
En un apartado anterior vimos como era la asignación secuencial a una señal. Pero las asignaciones a señales también pueden ser
concurrentes. La principal forma de ésta asignación es que no se encuentra en los procesos o subprogramas, sino en las arquitecturas.
La sintaxis de asignación tiene la misma forma que si fuese secuencial, es decir, usando el operador <=.
Esta asignación es equivalente a la sentencia if, con la salvedad que es concurrente. La sintaxis a seguir es la siguiente:
En este caso, a señal_uno se le asigna el valor de señal_dos si se cumple lo especificado en condición, y en caso de no cumplirse, se
le asigna el valor de señal_tres. Un ejemplo muy típico para el asignamiento concurrente condicional es el de un multiplexor. Los dos
listados siguientes tienen la misma operatividad:
process(control,entrada1,entrada2)
begin
if control='1' then
salida <= entrada1 when control='1' salida<=entrada1;
else entrada2; else
salida<=entrada2;
end if;
end process;
La última asignación también puede llevar condición (sólo a partir de la norma de 1993).
Es muy importante notar que no se incluye el punto y coma (;) habitual más que al final de la sentencia, ya que de otra forma sería
incorrecto. La sentencia asigna a señal1 el valor de señal2 si se da que expresion toma el valor valor_expresión_1, toma el valor
señal3 si expresion toma el valor valor_expresión_2,... pudiendose ampliar esta cadena de condiciones tanto como queramos.
Como ejemplo veamos los siguientes fragmentos de código, ambos equivalentes, que modelan a una simple Unidad Lógica:
process(a, b, operacion)
begin
with operacion select case operación is
resul <= a and b when "00", when "00" => resul <= a and b;
a or b when "01", when "01" => resul <= a or b;
a xor b when "10", when "10" => resul <= a xor b;
not (a and b) "11"; when "11" => resul <= not(a and b);
end case;
end process;
En ambos fragmentos de código se realiza la operación and a las señales a y b siendo el resultado asignado a resul si operacion vale
"00", se realiza la operación or si operación vale "01", etc. La diferencia entre ambas rutinas es que la izquierda es concurrente y la de
la derecha es secuencial.
La llamada a una función o a un procedimiento (los dos tipos existentes de subprogramas) la podemos encontrar tanto en una
arquitectura como dentro de un proceso, teniendo en ambos casos la misma apariencia. La que nos ocupa ahora es la primera, que es la
concurrente, la cual se ejecuta si cualquiera de los parámetros de entrada cambia. Debemos tener en cuenta que las funciones que
vayamos a usar deben ser visibles para el compilador, es decir, que la librería que las contenga debe estar declarada correctamente. La
sintaxis general es la siguiente:
Al subprograma llamado nombre_de_la_función se le introduce el objeto entradas y sacamos el objeto salidas. Los objetos de
entrada y salida no tienen porque ser del mismo tipo, ya que dentro del subprograma puede haber cambiado.
Sentencias estructurales
Las sentencias estructurales (o de instantación), son una forma más de hacer llamadas a subprogramas en VHDL. Con ellas se puede
hacer uso de un componente o circuito definido con anterioridad sin necesidad de incluirlo en la descripción que se está realizando; sólo
habrá que hacer una llamda a dicho componente para usarlo con las especificaciones propias del diseño actual. Se aprecia que su
operatividad es muy similar a la de una librería. El lenguaje nos proporciona una serie de sentencias dedicadas a la descripción de la
estructura de hardware que son concurrentes, y aparecen en la arquitectura llamando a un modelo fuera de cualquier proceso. Estas son
las sentencias estructurales, cuyo principal elemento son los componentes con su función port map (mapa de puertos).
Componentes: Para realizar la descripción estructural de un sistema es necesario conocer qué sistemas o componentes lo
forman, indicando la interconexiones entre ellos. Para operar de esta forma, VHDL ofrece los componentes. Para ser usado, un
componente debe estar declarado previamente para poder hacer referencia al mismo. Si se declara un componente en una arquitectura,
podrá ser usado en cualquier parte de la misma, pero si a referencia se hace en un paquete, se podrá lamar en todas las arquitecturas que
llamen a ese paquete.
El estilo estructural es fácilmente reconocible porque la operatividad del programa no se puede leer del código ya que está formado
íntegramente por componentes y las señales que les unen a otros. Es decir, está formado por bloques o cajas negras a los cuales
metemos información y sacamos las salidas, las cuales podrán o no ir a otros bloques. Para esto debemos conocer la operatividad de
estos bloques, los cuales suelen estar en librerías. Para usar las puertas que están en el paquete gatespkg debemos primero invocar a la
librería ieee (léase "i e cubo") como está expuesto en la primera línea de la entidad que debemos usar. Para usar el paquete (o sub-
librería) gatespkg la llamaremos de la forma en que está en la segunda línea. Estas librerías vienen incluidas en la versión 3.5 del
programa WARP, de Cypress Semiconductor. Como ejemplo se lista el código de un multiplexor implementado por descripción
estructural.
La funcionalidad de este fragmento de código se basa en los elementos "inv", "and2" y "or2", que se encuentran en la librería
gatespkg, que listamos a continuación:
begin
q <= (a and b);
end archand2;
use work.cypress.all;
entity or2 is Empezamos la entidad or2
port (
a,b : in bit;
q : out bit
);
end or2;
architecture archor2 of or2 is
begin
q <= (a or b);
end archor2;
use work.cypress.all;
entity inv is
port (
a : in bit; Empezamos la entidad inv
qn : out bit
);
end inv;
architecture archinv of inv is
begin
qn <= not (a);
end archinv;
Subprogramas
Los subprogramas se usan para describir algoritmos de cualquier tipo que son más tarde usados tantas veces como se desee. Un
subprograma consta de una parte declarativa en la cual se definen los datos de entrada y de salida al mismo, y una parte de sentencias
en la cual se indica que operaciones se realizan sobre los datos.
Los subprogramas constan de dos partes: la definición del subprograma y la definición del cuerpo del subprograma. En la primera se
define el nombre del mismo y los parámetros que son introducidos en él. En la segunda se incluye el algoritmo que va a realizar el
subprograma.
Hay dos tipos de subprogramas, las funciones y los procedimientos, los cuales tratamos en puntos separados a éste.
Funciones
Las funciones están destinadas a realizar cálculos, siendo una nueva forma de definir nuevos operadores que pueden aparecer en una
expresión. A las funciones se le pueden introducir todos los valores que se necesiten, pero sólo devuelven un único valor, siendo las
variables internas a ella imposibles de recuperar para otras operaciones.
Parte declarativa: En ella indicamos cuales son los parámetros que introducimos en la función, la cual entenderá que
son constantes, y de modo in (por defecto). Podemos definir, además, todas las estructuras de datos que necesitemos
(tipos, constantes, variables,...), pero sólo existirán cuando la función haya sido llamada y se crean e inicializan cada vez
que esto ocurra. Esta es la razón de no poder incluir señales en la parte declarativa.
Parte de sentencias: En ella se transforman los objetos que introducimos en la función para obtener la salida. En esta
parte podremos usar el valor de señales y variables externas, pero no podremos modificarlas. Tampoco podremos usar la
sentencia wait.
Como ejemplo crearemos una función que realiza una operación tan sencilla como asignar a la salida de la función, suma, la suma de
dos números, a y b.
Empieza por la palabra reservada function y seguido va el nombre de la función y entre paréntesis los objetos de entrada con su tipo
correspondiente. Después va la palabra return y el tipo del resultado de la función. Tiene el mismo sentido que las entidades.
Las primeras líneas de la parte de sentencias coinciden con la parte declarativa, sólo que la primera lleva al final la palabra is, tal y
como se aprecia en el ejemplo. Seguido viene la declaración de tipos, subtipos y variables, etc. en la cual debe incluirse la variable que
va a ser devuelta por la función, que en este caso es suma. Entre la palabra reservada begin y end debemos incluir el algoritmo que
dará un valor a la variable que será el resultado, y la sentencia return suma. Esta última sentencia es imprescindible para definir la
función, ya que de no hacerlo ésta se quedaría sin resultado.
Para llamar a esta función deberemos escribir un código parecido a este en nuestro programa, teniendo en cuenta que la llamada a
una función no es por si misma una sentencia:
process
variable numero1, numero2: std_logic_vector(3 downto 0);
variable salida: std_logic_vector(3 downto 0);
begin
...
salida := sumar (numero1,numero2);
...
end process;
En esta ocasión hemos hecho la asignación de las entradas por posición: al ejecutarse la función, el compilador usa el valor de
numero1 como a, ya que numero1 es el primer valor que se introduce en la función y a es el primero incluido en la declaración de la
función. Lo mismo ocurre con numero2 y con b. También podíamos haber hecho el asignamiento por nombre de cualquiera de las
siguientes formas, siendo ambas equivalentes:
Sea cual sea la forma en la cual asignemos las señales a la entrada del subprograma, éstas deben coincidir en el tipo, es decir, en el
ejemplo no podíamos haber introducido en la función numero1 y numero2 si éstos hubieran sido del tipo entero o bit. De la misma
forma la variable o la señal a la cual asignamos el valor de la función debe coincidir con el tipo del objeto que hemos especificado para
la salida en la función. Es decir, no podemos asignar a salida el valor de la función que hemos creado si es del tipo integer (por
ejemplo), ya que la salida de la función es suma que es del tipo std_logic_vector.
Procedimientos
Los procedimientos están destinados a realizar alteraciones en los datos a los que tienen acceso, tanto internos como externos. De
forma distinta a las funciones, un procedimiento puede devolvernos más de un valor e incluso modificar alguno de los valores que le
introducimos. Un procedimiento o procedure consta al igual que las funciones de una parte declarativa y otra de sentencias:
Parte declarativa: En ella indicamos cuales son los parámetros que introducimos en la procedure, pudiendo ser de
tres modos posibles: in, out, e inout, y son por defecto del modo in. Si el modo de una señal de entrada es in, no
podremos modificar su valor, solamente usarlo, y a ojos de la procedure será una constante. Si el modo es out podrán ser
modificados en la parte de sentencias, pero al igual que pasa en una entidad no podremos leer su valor. Solamente si es de
modo inout podremos leer su valor y además modificarlo. Al igual que en las funciones podremos declarar todos los
tipos, constantes, variables, etc. pero sólo existirán cuando se haga un llamamiento a la procedure y se reinicializarán
cada vez que se vuelva a llamar.
Parte de sentencias: En ella se modifican señales y variables tanto internas como externas al procedure, pudiendo
además usar la sentencia wait.
Para aclarar conceptos, se muestra el siguiente codigo, en el cual se consigue el mismo resultado que en el ejemplo expuesto en el
apartado de funciones. La parte declarativa es la siguiente:
Su estructura se asemeja a la de una entidad y empieza con la palabra reservada procedure seguida del nombre que le vamos a
asignar a la misma (en este caso es sumar), y entre paréntesis se declaran los objetos de entrada como si de una entidad se tratase. En
los procedimientos no hace falta usar la palabra return, ya que se especifica cuál de las señales es de entrada y/o salida.
La primera parte consiste en repetir la parte declarativa y seguidamente, el subprograma con sus algoritmos correspondientes.
Una vez definido el procedimiento podremos usarlo en cualquier parte del programa ya sea secuencial o combinacional como por
ejemplo:
process
variable numero1, numero2: std_logic_vector(3 downto 0);
variable salida: std_logic_vector(3 downto 0);
begin
...
sumar (numero1,numero2,salida);
...
end process;
Al igual que en una función, los tipos de los objetos de entrada y salida a una función deben coincidir con los declarados en el
procedimiento. Es importante notar que una llamada a una procedure es una sentencia, no como una llamada a una función.
En este caso hemos hecho la asignación por posición, ya que al no indicar nada, el compilador supone que queremos asignar el
primer objeto que hemos introducido en la función al primer objeto que habíamos declarado cuando creamos el procedimiento. Esto es
numero1 se corresponde a a, numero2 se corresponde con b, y así sucesivamente. Si queremos asignarlos por nombre deberemos
hacerlo como se describe a continuación:
VHDL sintetizable
Galaxy de Cypress
Nova de Cypress
Grabación de nuestros
diseños
VHDL sintetizable
En un principo VHDL, al igual que los demás HDL'S, nacieron con el proposito de facilitar la labor de los
diseñadores de circuitos electrónicos, agilizando su diseño y haciendo más flexible su posterior depuración y
mantenimiento. Por este motivo se dotó a VHDL con abundantes instrucciones más orientadas a la simulación
que a la implementación física del diseño. Ello trajo consigo la diferenciacion del VHDL sintetizable del
simulable, siendo este último el más extendido y el que cuenta con más herramientas en los programas. Si
trabajamos con VHDL sintetizable, sólo podremos hacer uso de un conjunto de instrucciones válidas.
El lenguaje nos permite describir circuitos complejos manejando todas las sentencias y herramientas de las que
dispone, pero no siempre se garantiza que se pueda llegar a grabar en un dispositivo de lógica programable
(PLD), ya que ciertas instrucciones no tienen equivalente físico.
Como conclusión, se puede decir que todo el código de un programa en VHDL es simulable, pero no siempre
será sintetizable.
Galaxy de Cypress
Dado que el VHDL es el lenguaje estándar, todas las empresas fabricantes de PLD’s y FPGA’s (Cypress,
Xilinx, Altera, Actel, etc.) han desarrollado su propio compilador cada uno con sus propias funciones y
características especiales. Este turorial ha sido realizado basándose en el programa Warp2, del cual se muestran
varias ventanas cortesía de la compañía Cypress Semiconductor.
A pesar de ser un producto no muy pensado de cara al usuario, la herramienta de Cypress es tan potente como
las demás, contando además con un gran soporte técnico vía e-mail. El nombre de la herramienta se llama WARP
2, que actualmente va por su versión 4.3. Este conjunto de programas está orientado a la creación de un fichero
propio de VHDL (*.vhd), para compilarlo (Galaxy) y posteriormente simularlo (Nova).
Para conseguir este software se puede solicitar por correo ordinario o electrónico en la página web de Cypress.
El programa Galaxy es el núcleo de la suite WARP2, ya que nos gestiona los programas creados, nos permite
editarlos y elegir distintas opciones de compilación. Su pantalla principal es la siguiente:
Con este programa podremos describir un circuito en VHDL, compilarlo y simularlo, sin más que usar el menú
adecuado.
Al arrancar por primera vez el programa, nos aparecerá una pantalla que nos gestionará el control de un
proyecto, entendiéndose por proyecto la creación de una serie de PLD’s concernientes al mismo trabajo o tema.
Deberemos introducir en este cuadro de diálogo, la ruta y nombre del proyecto, según nuestras preferencias, y
añadirle la extensión wpr:
Una vez hecho esto pasamos a la ventana principal del programa. Si queremos crear un nuevo fichero para
compilar, deberemos llamar al editor de texto, para lo que deberemos pulsar el botón llamado selected o el botón
llamdo new, en la parte de edit, según querramos modificar un fichero ya creado o bien hacer uno nuevo,
respectivamente. Entonces se nos abrirá una ventana similar a la siguiente:
En esta ventana crearemos o modificaremos el código fuente de VHDL según lo que queramos realizar. Este
editor tiene una serie de guías para ayudarnos a programar, por si se nos olvida la sintaxis. Una vez escrito todo el
código del programa, lo deberemos guardar, con extensión vhd, en el mismo subdirectorio en el cual hemos
creado el fichero de extensión wpr (en este caso es c:\warp\proyecto) para que pueda ser compilado sin ningún
problema por el programa.
Si queremos crear una librería nueva para este proyecto, deberemos especificarla después de haber creado el
fichero de extensión wpr. Para ello deberemos ir al comando files/libraries y crear una nueva si así lo deseamos.
Una vez creada la librería, deberemos decir al programa qué queremos compilar, para lo cual deberemos ir al
menú files/add y seleccionar el fichero que queramos compilar. Una vez hecho esto, aparecerá el nombre de
dicho fichero en la parte izquierda de la ventana, debajo del menú de comandos.
Antes de pulsar los botones que nos compilarán el programa creado, deberemos seleccionar qué tipo de PLD
vamos a usar, el tipo de optimización, etc. Eta tarea la realizamos con los botones de la parte inferior del
programa: file, set top, generic y device.
El botón file nos servirá para decirle al programa que queremos usar la librería por defecto (es lo más
habitual) o si queremos usar un creada. Su aspecto es el siguiente:
El botón set top sirve para indicar cual de los ficheros a compilar es el principal, del cual cuelgan los demás
ficheros. El botón generic nos lleva al menú que nos permitirá elegir ciertas opciones como si deseamos
optimizar la velocidad o el área usada del dispositivo de lógica programable. El aspecto del menú es el siguiente:
El botón device nos permitirá elegir el tipo de PLD que usaremos para grabar nuestro diseño, que hacer con las
salidas no usadas, que tipo de FlipFlops queremos usar, etc. El aspecto del menú es el siguiente:
Una vez hechas todas estas operaciones habremos dejado todo preparado para empezar a compilar.
Pulsando en los botones selected o smart, de la parte compile, podremos compilar uno o más ficheros de
VHDL simultáneamente. Una vez pulsado cualquiera de los botones de compilación, nos aparecerá una pantalla
con las incidencias de la misma, incluyendo las librerías usadas, fecha, hora, errores, etc.
Desde esta pantalla podremos acceder a las líneas del código erróneas directamente y modificarlas para
solucionar el problema, lo cual es una gran ayuda para eliminar errores de nuestros programas. En esta ventana
apreciamos la forma de trabajar interna del programa, ya que nos dice el nombre de los programas que van
verificando el fichero en busca de errores, y como se va transformando el código original en un fichero que nos
permitirá grabarlo en una PLD. Dicha pantalla tiene el aspecto siguiente:
Una vez compilado el programa, llamaremos al programa simulador de ficheros de VHDL compilados (de
extensión jed), llamado Nova, pulsando en el menú "Tools/Nova", tal y como se aprecia en al siguiente figura:
Nova de Cypress
El programa Nova es el complemento a Galaxy, ya que nos va a simular los ficheros compilados previamente.
Su aspecto es similar a un analizador lógico, y le podremos introducir las entradas que queramos para ver si las
salidas que vayamos a obtener son las correctas. Su aspecto es el siguiente:
Para simular un fichero jed (de vectores JEDEC) deberemos primero abrirlo con el comando "file/open". Lo
siguiente que nos aparece es un cronograma con las entradas y las salidas que admitirá la PLD. Nosotros
podremos introducir las entradas y darle la forma que queramos (nivel lógico alto o bajo, que sea un reloj, que
tenga un pulso,...) según lo que deseemos, sin más que acceder al menú "edit".
Una vez modificada la forma de las entradas, y accediendo al comando "execute" del menú "simulate", el
programa nos ofrecerá en rojo las salidas que hubiéramos obtenido de haber grabado la PLD con este fichero.
Haciendo doble clic en la pare inferior de la ventana nos aparecerá una línea de guía para poder seguir mejor el
resultado de la simulación.
También nos permite el organizar las entradas y salidas en forma de bus para controlar mejor los resultados.
Esto es muy útil si estamos manejando entradas y/o salidas de varios bits de anchura y necesitamos saber su valor
en todo momento.
Un ejemplo de cómo es útil la creación de un bus se muestra en las siguientes imágenes, donde, en la superior
no se usa un bus y en la inferior donde sí se usa. En ambas hemos introducido la misma entrada aleatoria y
queremos saber el resultado de al simulación para las salidas en el instante 50ns.
En el primer caso, sin bus, deberemos analizar cual es el valor de cada uno de los bits de salida, e interpretarlo
para ver que es "111", o bien, "7".
Si hemos creado un bus, como en la siguiente imagen. El resultado de los bits de salida los va marcando el
programa sin más que desplazar la guía hasta el instante deseado, para ver que la salida es "00x7". Para crear un
bus hay que ejecutar el comando edit/create bus, y seleccionar que señales queremos que formen parte del bus.
Una vez que hayamos dado este paso, estaremos preparados para grabar nuestra PLD en las máquinas
dispuestas a tal efecto.
Para poder implementar las descripciones realizadas en VHDL en un dispositivo de lógica programable, se
necesita un programador de PLD's y el software de que nos grabará el cirucito usando el fichero compilado por
Warp (de extensión jed). Como ejemplo usaremos el software y la máquina de Advantech Lab Tool, del cual se
muestra a continuación la pantalla principal.
Para comenzar, se debe indicar al grabador el tipo de dispositivo en el cual vamos a programar nuestro diseño.
Esto se lleva a cabo pulsando el botón select de la barra de herramientas del Lab Tool. Una vez hecho esto,
aparecerá un cuadro de diálogo en el cual se pide al usuario que elija el tipo de dispositivo a utilizar (tipo, marca y
modelo), como se muestra en la imagen.
Una vez seleccionado el dispositivo es conveniente comprobar que el mismo esté en blanco (sin usar),
pulsando el botón blank . En el caso de que el PLD no estuviera vacío, deberemos borrarlo (si se nos permite)
usando el botón erase .
Una vez que se haya seleccionado el tipo de encapsulado y lo hemos verificado, se indica al software grabador
qué queremos implementar en el dispositivo elegido. Para esta tarea deberemos indicar mediante un cuadro de
dialogo estándar de Windows el fichero deseado, como se aprecia a continuación.
Cuando se haya elegido el encapsulado y el fichero a programar, con tan sólo pulsar el botón prog se grabará la
PLD de forma automática, y habremos concluido el proceso, yaque el mismo programa, después de grabar el
dispositivo, lo verifica.
Esta parte del tutorial te permitirá implementar desde el primer momento los ejercicios
propuesto y entender el lenguaje VHDL. Lo único que se necesita para seguir este curso es
saber Electrónica Digital y tener nociones de cualquier lenguaje de programación ya sea
Pascal, C, Basic,...
El lenguaje de descripción hardware VHDL (Very high speed Hardware Description Logic) es
un lenguaje orientado a la descripción de hardware pero con muchos elementos heredados de
otros lenguajes como C o Pascal. Una vez realizado un programa en VHDL (con extensión
VHD) y haberlo compilado con éxito, tendremos un fichero con el mismo nombre y extensión
JED, con el cual podremos grabar una PLD (Dispositivo Lógico Programable) con la misma
operatividad que el fichero VHD.
Al describir cualquier dispositivo en VHDL (desde una simple puerta and hasta un sistema
completo) se deben definir dos elementos principales:
Entidad o entity que es la interfaz del dispositivo con el exterior. Tiene por
objeto decir que señales son visibles o accesibles desde el exterior, es decir los
puertos o ports del dispositivo. Es describir las patillas del circuito que serán
operativas. Su estructura mas general es la siguiente, donde las palabras en
negrita son propias del lenguaje, y las restantes elegidas por el ususrio:
entity entidad is
genericos
puertos
declaraciones
end nombre;
entity entidad is
puertos
end entidad;
Para acabar esta introducción deberemos tener en cuenta una serie de detalles más de éste
lenguaje:
Las variables deben empezar por una letra, no deben contener ni espacios ni
símbolos como &, %, $, #, !, etc. Su longitud no está limitada, no pueden acabar
con un carácter de subrayado o tener dos subraryados seguidos.
Un multiplexor es un dispositivo lógico que recibe información por sus dos o más entradas (de uno o
mas bits de ancho) y mediante una señal de control decidimos cual de la entradas aparece reflejada
en la salida; esto es, un convertidor de paralelo a serie. Si tienen una señal de "enable" esta hace
que el multiplexor esté habilitado o no
Los multiplexores disponibles tienen todos señal de enable menos el primero. Las arquitecturas
están realizadas solo para algunas entidades
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver
pulsa el botón que está al lado del título
Entidad 3: Qué hacer para que cada canal sea de mas bits
Si queremos que cada entrada (y la salida, por supuesto), tengan más anchura, es decir, más bits, no nos sirve el tipo bit, ya
que su anchura es de un bit. Para ello deberemos usar vectores de bits, que al igual que bit está predefinida en VHDL. En este
ejemplo usaremos entradas de 4 bits, que para definirlos hay que escribir el número de mayor peso seguido de la palabra
downto y del número de menor peso.
control enable d
XX H HHHH
LL L a
LH L b
HL L c
HH L HHHH
siguiente:
● Entre las líneas 9 y 22 es donde se encuentra toda la operatividad del programa, es decir que hacemos con las entradas
para que se conviertan en las salidas.
● En la línea 10 empieza un proceso (process), el cual acaba en la línea 21. Si alguna de las variables que están dentro
del paréntesis cambia, el proceso se ejecuta. Una vez ejecutado el proceso cambian las señales, no durante él.
● En la línea 12 hay una sentencia que acaba en la línea 20. Si se cumple la condición que está después de la palabra if,
se ejecuta lo que está después de la palabra then. En caso de que la condición no se cumpla, se evalúa lo que está
detrás de la palabra elsif, y si es verdad se ejecuta lo que está después del segundo else. Si se cumple la primera
condición, es decir, que enable valga "1", el multiplexor pasa a tener a la salida el valor "1111". Si no se cumple, pasa
a ejecutarse una case. Pulsa aquí para ver la sentencia if. Pulsa aquí para ver la sentencia case.
● La sentencia case empieza en la línea 14 y acaba en la 29. Según los valores que tome control, la salida d tendrá un
valor u otro.
● Todas las sentencias que se abran deben estar correctamente cerradas con la palabra clave end. Debemos saber que al
cerrar una sentencia if también cerramos a la vez todas las sentencias elsif asociadas a ese if. Lo mismo ocurre con el
programa principal, el cual debemos cerrar con end y el nombre de la arquitectura.
● La sentencia case debe estar siempre dentro de un proceso para que la sintaxis esté correcta.
Recordemos que unas sentencias son concurrentes si se ejecutan todas a la vez. De este código debemos destacar estas dos
cuestiones:
● En la línea 9 se declaran las señales aux1, aux2 y aux3, que son señales intermedias que no forman parte de la entrada
ni de la salida y que las utilizamos para hacer más cómodamente la estructuración del programa. Si estas señales, el
código sería más confuso aunque igualmente válido, ya que de no usarlas deberíamos haber sustituido las líneas de la
11 a la 14 por la siguiente:
● La explicación de las sentencias que están en la línea 11 a la 14 habla por si solas sin más que mirar el código. Con una
simple inspección comprobamos que se corresponde con el gráfico situado sobre el código.
Este tipo de estilo es solo recomendable usarlo en "estado puro" solo para diseños tan sencillos , siendo de verdadera utilidad
al usarlo como complemento del estilo algorítmico y en ocasiones del estructural. A partir de ahora la única forma en que la
veamos será esta: como complemento.
En este ejemplo vemos que debemos hacer para usar las librerías que vienen predefinidas con el lenguaje VHDL y más
concretamente con Warp 3.5 de Cypress Semiconductor. Para trabajar con librerías debemos tener en cuenta estos puntos:
● La librería debe estar visible para nuestro programa, es decir, debemos decirle al compilador que lo que no está
definido por defecto en VHDL debe buscarlo en esa librería. Para ello nuestras primeras líneas del programa deben
hacer referencia a la librería que necesitemos. En este ejemplo, se usa la librería ieee. Dentro de la librería está el
paquete de puertas lógicas comunes, que vienen en el fichero gatespkg.vhd. Este paquete es el que debemos invocar
(ya que no vamos a usar la librería en toda sus extensión, sólo una parte). Para esto debemos conocer el paquete, que
utilidades nos puede ofrecer y cual es el orden correcto de las variables entre paréntesis. Para ello no hay más remedio
que ir llevando una lista con las librerías que vayamos conociendo y estudiar la estructura de las librerías por dentro.
● Una vez que hemos cargado la librería, la usamos de la forma expuesta en las líneas de la 13 a la 17, indicando que
parte de la librería usamos (inv, and2, or2,...) seguido de las palabras claves port map, y entre paréntesis las señales
que introducimos en las estructuras de las librerías. En estos casos, la última señal suele ser la que sacamos, aunque no
tiene porque se así. Para más información detén del cursor al lado del icono de ayuda en la línea correspondiente.
Si alguien cambiase el contenido de la librería e hiciese que el bloque and2 dejase de ser una puerta and de dos entradas,
nuestro diseño no sería correcto, ya que no haría lo que nosotros pretendemos en un principio. Por ésto, debemos estar
seguros de que función realiza una parte de una librería antes de usarla.
Al igual que el estilo de flujo de datos, este estilo es poco usado en estado puro, usándose solamente para unir diseños
complejos creados anteriormente.
Un demultiplexor es un dispositivo lógico que como su nombre indica realiza la operación inversa
al multiplexor; esto es, un convertidor de serie a paralelo. El demultiplexor recibe información por
su única entrada (de uno o mas bits) y una señal de control decide en cual de las salidas se
refleja. Si tienen una señal de "enable" esta hace que el demultiplexor esté habilitado o no
Estos demultiplexores son los inversos a la mayoría de los multiplexores de la práctica anterior.
No hemos usado los estilos de flujo de datos y estructural por no ser "rentable".
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver
pulsa el botón que está al lado del título
Entidad 3: Qué hacer para que cada canal sea de mas bits
Si queremos que cada entrada (y la salida, por supuesto), tengan más anchura, es decir, más bits, no nos sirve el tipo bit, ya
que su anchura es de un bit. Para ello deberemos usar vectores de bits, que al igual que bit está predefinida en VHDL. En este
ejemplo usaremos entradas de 8 bits.
control enable a b c d
XX H H H H H
LL L entra b c d
LH L a entra c d
HL L a b entra d
HH L a b c entra
● Entre la línea 12 hay una sentencia if que de cumplirse, hace que se ejecuten cuatro instrucciones. La primera y la
segunda son iguales, es decir, hacen que a y b tomen el valor "11111111". Las otras dos hacen lo mismo con las
salidas c y d, pero de otra forma distinta, usando la palabra reservada others. La segunda forma tiene la ventaja de no
importa la anchura de la entrada o salida, por lo que si tenemos que cambiar en la entidad el número de bits por los que
está formado un puerto, no habrá que hacerlo en la arquitectura.
● Cuando se ejecuta la sentencia case, hay que tener en cuenta lo siguiente: si control es "00" está claro que a toma el
valor de entra. Si más adelante control es "01", b toma el valor de entra, pero a sigue con el valor anterior de entra
ya que no hay ninguna instrucción que modifique su valor. Esta memoria está implícita en VHDL, y debemos tener
cuidado con ella, ya que puede ayudarnos (el caso de la memorias) o crearnos problemas. Si queremos que al cambiar
el valor de entra, todas las salidas valgan "00000000" (por ejemplo), excepto la seleccionada, deberemos sustituir las
líneas de la 17 a la 22 por lo siguiente:
case control is
when "00" => a <= entra;
b <= "00000000";
c <= "00000000";
d <= "00000000";
Ahora al cambiar el valor de control, sólamente una salida toma el valor de entra y las otras el valor "00000000", ya que así
lo hemos pedido explícitamente.
A partir de ahora crearemos las entidades y arquitecturas a la vez para trabajar de forma normal
prescindiremos de la columna de ayuda de la derecha.
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver
pulsa el botón que está al lado del título
Pero debemos darnos cuenta de que este diseño es del todo incorrecto por la memoria implícita que tiene el lenguaje VHDL,
ya que en el momento en que haya un dos entradas seleccionas a la vez, la señal error permanecería para siempre a nivel
lógico alto, ya que no hay ninguna instrucción que haga que esté a nivel lógico cero. Para corregir esto deberemos escribir el
siguiente código, en el cual, cada vez que hay una entrada correcta la señal error se pone a cero.
dentro fuera EO
HXXXXXXX HHH L
LHXXXXXX HHL L
LLHXXXXX HLH L
LLLHXXXX HLL L
LLLLHXXX LHH L
LLLLLHXX LHL L
LLLLLLHX LLH L
LLLLLLLH LLL L
LLLLLLLL LLL H
library ieee;
use ieee.std_logic_1164.all;
La primera línea llama a la librería ieee, la cual
use work.std_arith.all;
contiene el paquete std_logic_1164, que lo
entity coder is port(
necesitamos para usar el tipo std_logic_vector, y
dentro: in std_logic_vector(7 downto 0);
dentro de este paquete está std_arith, que nos
fuera : out std_logic_vector(2 downto 0);
proporciona la función std_match. Se usan los
eo : out bit
tipos bits y std_logic.
);
end coder;
Hacer este ejercicio con el estilo de flujo de datos hubiera sido impensable, ya que habría que buscar las ecuaciones lógicas
para cada bit de la salida, lo cual hubiera sido, aparte de complicado, superfluo, ya que VHDL se creó para evitar el hacer esto
precisamente. Como ejemplo de ésto, a continuación se muestran la ecuación lógica para conseguir el bit fuera(0):
Se comprueba claramente que este modo es, aparte de complicado, poco legible para una persona que trate de analizar el
código.
Para este codificador las especificaciones son las siguientes: una entrada de dieciseis bits sin prioridad, activadas por cero,
una salida de cuatro bits activadas por uno (que corresponderían con tecnología CMOS) y una salida de interrupción que
tomará el valor uno cuando una sola de las entradas esté activada. En las especificaciones, se nos dice que aparte de tener una
operatividad especial y única el circuito también requerirá una disposición determinada de sus pines de entrada y de salida
de forma que seremos nosotros los que deberemos especificarle al compilador donde y como queremeos que esten
posicionados. El diseño del dispositivo lógico debe ser el siguiente:
library ieee;
use ieee.std_logic_1164.all;
entity coder is
port(
ent: in std_logic_vector(16 downto 1); Al igual que antes, para usar el tipo std_logic o
interr: out std_logic; alguno de sus derivados (como los vectores),
sal: out std_logic_vector(4 downto 1) debemos cargar la librería ieee, y, de ésta, el
); paquete std_logic_1164. La palabra all indica que
cargamos TODO el paquete, no una parte.
attribute pin_numbers of coder:entity is
Para prefijar la dispocicion de las señales en las
"ent(1):3 ent(2):2 ent(3):20 ent(4):19
patillas de la cápsula, hemos usado la sentencia
ent(5):1 ent(6):23 ent(7):22 ent(8):21
attribute pin_numbers, en la cual primero se
ent(9):10 ent(10):11 ent(11):4 ent(12):5
especifica el orden de las entradas y despues el de
ent(13):8 ent(14):9 ent(15):6 ent(16):7 "
las salidas.
&
"sal(1):14 sal(2):15 sal(3):16 sal(4):17
inerr:18 ";
end coder;
Como nota curiosa hay que señalar que el compilador que usamos, el Galaxy / Warp de Cypress no admite que las líneas de la
posición de los pines tengan varias línea, sino sólamente una para las entradas y sólamente una para las salidas. Además entre
la última asignación y las comillas debe existir un espacio.
Este ejemplo no tiene nada de extraño a estas alturas del tutorial, ya que la sentencia case ha aparecido en muchas ocasiones.
Lo único es que hemos puesto un nombre al proceso (code). Esto es útil para cuando tenemos varios de ellos en el mismo
programa y queremos estructurar la programación para hacerla más legible. Al cerrar un proceso con nombre, hay que
especificarlo, como aparece en la línea 24.
Los decodificadores expuestos será una muestra práctica de la versatilidad de VHDL para la
implementación de este tipo de circuitos lógicos.
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver
pulsa el botón que está al lado del título
Decodificador 3 a 8
Decodificador 3 a 8
Entidad 1: Decodificador de 3 a 8
Para el diseño de la entidad necesitamos tener una entrada de tres bits, dos entradas de control, y una salida de ocho bits que
en este caso serán activas por nivel bajo. El código no requiere mayor explicación, ya que la entidad es muy sencilla:
library ieee;
use ieee.std_logic_1164.all;
entity decoder is
port(
La entrada de información es entrada, las de
entrada: in std_logic_vector(2 downto 0);
control son g1 y g2 y la salida es salida.
g1, g2: in std_logic;
salida: out std_logic_vector(7 downto 0)
);
end decoder;
Arquitectura 1: Decodficador de 3 a 8
Para crear la arquitectura de este decodificador deberemos saber que operatividad exacta le vamos a proporcionar. En este
caso usaremos como modelo del circuito MSI 74138 y traducirla al lenguaje VHDL.
Entrada Salida
g1 g2
(2 a 0) (7 a 0)
X H XXX HHHHHHHH
L L XXX HHHHHHHH
H L LLL LHHHHHHH
H L LLH HLHHHHHH
H L LHL HHLHHHHH
H L LHH HHHLHHHH
H L HLL HHHHLHHH
H L HLH HHHHHLHH
H L HHL HHHHHHLH
H L HHH HHHHHHHL
En este ejemplo introducimos una nueva sentencia, la sentencia when-else que viene a ser como una scesión de sentencias if
anidadas. No debemos olvidar al final la sentencia others en la que se contemplan todos los demás posibles valores qu puede
tomar la entrada, ya que aunque en un principio parecen estar contempladas, no debemos olvidar queel tipo std_logic puede
tener más valores aparte del nivel alto y el nivel bajo, como el de alta impedancia o el don't care (poco importa).
library ieee;
use ieee.std_logic_1164.all;
Al igual que antes, para usar el tipo std_logic o alguno
entity convertidor is
de sus derivados (como los vectores), debemos cargar
port(
la librería ieee, y, de ésta, el paquete std_logic_1164.
bcd: in bit_vector(3 downto 0);
La palabra all indica que cargamos TODO el paquete,
led: out bit_vector(6 downto 0)
no una parte.
);
end convertidor;
Entrada Salida
(BCD) (LED)
LLLL HHHHHHL
LLLH HHLLLLL
LLHL HLHHLHH
LLHH HHHLLHH
LHLL HHLLHLH
LHLH LHHLHHH
LHHL LHHHHHH
LHHH HHLLLHL
HLLL HHHHHHH
HLLH HHHLHHH
LLLL LLLLLLL
Este ejemplo no tiene nada de extraño a estas alturas del tutorial, ya que la sentencia case ha aparecido en muchas ocasiones.
Lo único es que hemos puesto un nombre al proceso (conv). Esto es útil para cuando tenemos varios de ellos en el mismo
programa y queremos estructurar la programación para hacerla más legible. Al cerrar un proceso con nombre, hay que
especificarlo, como aparece en la línea 18.
Un comparador es un dispositivo lógico que recibe dos números a la entrada, A y B, y a la salida indica si el
número A es mayor, menor o igual que el número B. La longitud de las palabras de la entrada es
indiferente, pero iguales para ambos números. Pueden llevar además entradas en cascada, usadas para
utilizar varios comparadores.
Los comparadores van desde el más sencillo hasta el más complejo posible. Se puede ver claramente en
este ejemplo, las mejoras de VHDL frente a los MSI.
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver pulsa el
botón que está al lado del título
library ieee;
use ieee.std_logic_1164.all;
entity compara is port(
a,b: in std_logic_vector(1 downto 0);
Necesitamos dos entradas de dos bits (a y b) y tres salidas
mayor: out std_logic;
de un bit cada uno (mayor, menor e igual).
menor: out std_logic;
igual: out std_logic
);
end compara;
Este primer método es un ejemplo de qué no debemos hacer, no porque esté incorrecto, sino porque no aprovecha la potencia de
VHDL, es complicado de entender y seguir, y por ser un programa demasiado largo para solamente indicar si el número a es mayor,
menor o igual que el b.
17 elsif (a(0) = '0') and (b(0) = '1') then mayor <= '0';
18 menor <= '1';
19 igual <= '0';
20 end if;
21 end process;
22 end archicompara;
El siguiente diseño ya es mucho más aceptable y utiliza toda la potencia de VHDL para dar un resultado preciso. Para esto utiliza la
librería ieee (que la utilizaremos siempre que haya que usar una función especial), la cual contiene al paquete std_arith que nos
permite hacer operaciones tales como comparaciones o sumas, restas, ... Ya que hay que cargar este paquete, escribiremos de nuevo
la entidad con la llamada al mismo.
library ieee;
use ieee.std_logic_1164.all;
use work.std_arith.all;
entity compara is port(
a,b: in std_logic_vector(1 downto 0);
mayor: out std_logic;
menor: out std_logic;
igual: out std_logic
);
end compara;
Esta estrucuta es mucho más sencilla de crear y de comprender en un posterior análisis por una persona distinta a la que escribió el
código.
library ieee;
use ieee.std_logic_1164.all;
entity compara is port(
a,b: in std_logic_vector(1 downto 0);
antes_mayor: in std_logic; Necesitamos dos entradas de cuatro bits (a y b), tres
antes_menor: in std_logic; entradas de un bit para la entrada en cascada
antes_igual: in std_logic (antes_mayor, antes_menor y antes_igual) y tres salidas
mayor: out std_logic; de un bit cada uno (mayor, menor e igual).
menor: out std_logic;
igual: out std_logic
);
end compara;
En esta ocasión hemos usado parte del código del ejemplo anterior para comparar cuatro bits en cascada. Esta es otra de las ventajas
de VHDL; su modularidad permite el usar otras aplicaciones ya creadas.
Un CST es un dispositivo lógico cuya operatividad básica conciste en sumar dos numeros de cuatro bits,
recibiendo un posible carry o llevada de una etapa anterior y generando el carry propio de esta.
El CST ha sido un circuito básico en el diseño de ALU's y circuitos ariméticos en general.Poe ello VHDL nos
permite crearlos fácilmente.
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver pulsa el
botón que está al lado del título
library ieee;
use ieee.std_logic_1164.all;
use work.std_arith.all; Los números a sumar son a y b, el carry de entrada es cin, el
entity sumador is port( resultado de la suma es sum y el carry siguente es cout. Para
a,b: in std_logic_vector(3 downto 0); este diseño emplearemos la librería aritmética de VHDL
cin: in std_logic; llamada std_arith, que se carga al incluir la tercera línea de
sum: out std_logic_vector(4 downto 0) este programa.
);
end sumador;
En la línea 5 se suman a y b pero como el resultado puede ser de cinco bits, la señal a la que asignaremos la suma debe ser de esta
anchura. Para que no haya problemas concatenamos a y b con un cero. Una vez sumados a y b, deberemos emplear el carry de entrada
el cual incrementará el resultado en uno si es necesario, o no tocarlo (null) en caso contrario.
ENTITY fcadd4 IS
PORT (CI : IN BIT;
A3,A2,A1,A0: IN BIT;
En vez de usar vectores de bits vamos a usar las entradas
B3,B2,B1,B0: IN BIT;
y salidas desglosadas en bits.
SUM3,SUM2,SUM1,SUM0 : OUT BIT;
CO: OUT BIT);
END fcadd4;
Este tipo de descripción, por flujo de datos, nos lleva a la consecución de un código árido de leer que dificulta su comprensión y
posteriores modificaciones.
Un restador es un dispositivo lógico que recibe dos números a la entrada, A y B, y a la salida nos da su
diferencia, admitiendo una posible llevada de una operación anterior y proporcionándonos un na nueva.
Los restadores utilizan el formato de complemento a dos para representar los números negativos.
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver pulsa
el botón que está al lado del título
Nuestra entidad sólo necesita los números de entrada (de 4 bits) y el de salida (de 4 bits también). La llevada anterior está
deshabilitada y la de salida se debe ignorar.
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.sumador.all;
4 entity resta is port (
5 a,b: in std_logic_vector(3 downto 0);
6 sr: in std_logic;
7 c: out std_logic_vector(3 downto 0)
8 );
9 end resta;
Para complementar un número a dos, deberemos invertirlo y sumarle uno. La sentencia if invierte al número b si es necesario.
Se le suma una unidad más adelante.
Para restar deberemos sumar a, la señal aux y la señal de suma/resta, para acabar de complementar a dos el número b.
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.lpmpkg.all;
4 entity resta is port (
5 a,b: in std_logic_vector(3 downto 0);
6 cin,sr: in std_logic;
7 c: out std_logic_vector(3 downto 0);
8 cout,ov: out std_logic
9 );
10 end resta;
Para complementar un número a dos, deberemos invertirlo y sumarle uno. La sentencia if invierte al número b si es necesario.
Se le suma una unidad más adelante.
Para restar deberemos sumar a, la señal aux y la señal de suma/resta, para acabar de complementar a dos el número b.
Un flip flop es un circuito electrónico, llamdo también simplemente biestable, que tiene dos estados
estables. El flip flop es un elemento básico de memoria que es capaz de almacenar un número binario
(bit), es decir, que permanece indefinidamente en uno de sus dos estados posibles aunque haya
desaparecido la señal de excitación que provocó su transición al estado actual.
Debido a su amplia utilización, los flip flops se han convertido en un elemento elemental dentro de los
circuitos secuenciales y con el paso del tiempo se han desarrolado varios tipos: RS, JK, D y T.
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver pulsa
el botón que está al lado del título
Multiplicador de 3 bits
Multiplicador de 3 bits
Para crear un multiplicador de 3x3 bits, necesitaremos una única cápsula, gracias a VHDL (y cualquier HDL). Nuestro
circuito va a tener dos entradas de tres bits cada una, y el resultado debe ser de seis bits, ya que en el caso que nos da un
mayor resultado (7x7=49), necesitamos 6 bits para completar el número.
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.std_arith.all;
Hay que destacar que la suma de las señales aux1, aux2 y aux3 se hace FUERA del proceso, ya que de no ser así, c no se
actualizaría correctamente y adoptaría el valor del producto anterior.
Para la entidad sólo necesitamos declarar los dos números a multiplicar y el producto, pero queremos que además de hacernos
el producto de a y b nos sume un número d (de la misma anchura que el producto). Por lo tanto la entidad debe ser como se
muestra en el ejemplo.
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.std_arith.all;
Hay que destacar que un circuito multiplicador no puede ser implementado en una PLD del tipo 22V10, sino en dispositivos
mayores como la C374i. Esto es debido a que cada uno de los elementos que componen al número c necesita mucho
propuctos y sumas lógicas, requiriendo mucho espacio de cápsula.
use work.lpmpkg.all;
Para la entidad sólo necesitamos declarar los dos números a multiplicar y el producto, pero queremos que además de hacernos
el producto de a y b nos sume un número d (de la misma anchura que el producto). Por lo tanto la entidad debe ser como se
muestra en el ejemplo.
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.lpmpkg.all;
1 use work.lpmpkg.all;
2 architecture archimulti of multi is
3 begin
4 mul: mmult
5 generic map (3,3,6,6)
6 port map(dataA,dataB,sum,result);
7 end archimulti;
Hay que destacar que un circuito multiplicador no puede ser implementado en una PLD del tipo 22V10, sino en dispositivos
mayores como la C374i. Esto es debido a que cada uno de los elementos que componen al número result necesita mucho
propuctos y sumas lógicas, requiriendo mucho espacio de cápsula.
Se debe aclarar que se ha hecho la introducción de datos al módulo mmult por posición.
Un flip flop es un circuito electrónico, llamdo también simplemente biestable, que tiene dos estados
estables. El flip flop es un elemento básico de memoria que es capaz de almacenar un número binario
(bit), es decir, que permanece indefinidamente en uno de sus dos estados posibles aunque haya
desaparecido la señal de excitación que provocó su transición al estado actual.
Debido a su amplia utilización, los flip flops se han convertido en un elemento fundamental dentro de los
circuitos secuenciales
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver pulsa
el botón que está al lado del título
clk S R q
ñ L L q
ñ L H L
ñ H L H
ñ H H X
1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity flipfloRS is port ( Se define como entradas las señales s, r y la señal de
4 r,s,clk: in std_logic; reloj clk, La.senal de salida q, se define como buffer
5 q: buffer std_logic; para poder emplear su valor, y asignarselo a la señal
6 notq: out std_logic de salida notq
7 );
8 end flipfloRS;
1
architecture archiflipfloRS of flipfloRS is
2
begin
3
process (clk)
4
begin
5
if (clk'event and clk='1') then
6 La asignación del valor de la señal notq
if (r='1')and(s='0') then q <= '0';
7 debe de hacerse a la salida del proceso, para
elsif (r='0')and(s='1')then q <='1';
8 que la señal q, ya haya actualizado su valor.
end if;
9
end if;
10
end process;
11
notq <= not q;
12
end archiflipfloRS;
13
Es reseñable de esta arquitectura la utilización de la sentencia event, que detecta cuando cambia la señal a la que esta
asociada. En el caso de esta arquitectura, es empleada para detectar un flanco de reloj (clk). La sentencia booleana clk'event
and clk='1' indicará si se ha producido un flanco de subida en la señal de reloj clk. El falnco de bajada se indica con la
sentencia clk'event and clk='0'.
clk D q
ñ L L
ñ H H
1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity flipflopd is port (
4 d,clk: in std_logic;
5 q : out std_logic
6 );
7 end flipflopd;
Esta entidad es muy sencilla ya que las entradas y la salida son de un único bit de ancho.
En la líne 5 tenemos un caso de memoria implícita ya que solamente se especifíca que se debe hacer con la señal de salida q
cuando aparece un flanco de subida en la señal de reloj, por lo que la salida q, mantendra su valor anterior mientras no se de
dicho flanco..
clk J K q
ñ L L q
ñ L H L
ñ H L H
ñ H H notq
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.rtlpkg.all;
4 entity ffjk is
5 port (
En el flip flop JK, las entradas J K son análogas a las
6 j : in std_logic;
entradas set (s) y reset (r), respectivamente.
7 k : in std_logic;
8 clk : in std_logic;
9 q : out std_logic
10 );
11 end ffjk;
El flip flop JK es el más completo de ,los flip flops que se emplean. Tiene dos entradas J y K, similares a las entradas S y R
de un flip flop RS. La estrada J realiza la función set y la entrada K la función reset . La principal diferencia entre ambos es
que J y K pueden valer uno simultáneamente, a diferencia del flip flop RS, en este caso la salida cambia de estado, pasando a
valer lo contrario de lo que valía antes.Es el basculamiento
En este ejemplo, se ha implementado un flip flop JK, basándonos en la operatividad de un flip flop d, previamente descrito en
la librería rtlpkg.Esto se hace através de la utilización de las señales qx y dx,
clk T q
ñ L q
ñ H not q
1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity tff is port (
En el flip flop T, las únicas entradas seran la señal de
4 t, clk : in std_logic;
reloj, clk, y la señal t, dandonos por salida q.
5 q : buffer std_logic
6 );
7 end tff;
Un contador es un dispositivo lógico sínrono o asíncrono, en cuyas salidas tenemos un número que va
creciendo o decreciendo a intervalos regulares según lo hayamos especificado nosotros. Suelen contar
así mismo con entrada de reset y preset (a partir de que número se cuenta).
Los comparadores pueden contar tanto en binario natural, BCD, octal,... y contar de uno en uno, de
dos en dos,...
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver
pulsa el botón que está al lado del título
Contador ascendente
Contador BCD
Lo único a destacar de este ejercicio es que la salida debe ser del tipo buffer, no del tipo out, ya que debemos conocer la
salida que tenemos, para poder sumarla uno. Este ejemplo no merece más comentarios, ya que iremos añadiendo operatividad
a medida que avancemos en esta práctica.
Lo único a destacar de este ejercicio es que al haber dos sentencias if deberemos cerrar ambas. VHDL, como todos los
lenguajes de programación, cierra primero la última sentencia if en abrirse.
Si el contador llega a "1111", y le sumamos uno, la salida que obtengamos será "0000", ya que automáticamente VHDL
realiza la operación de despreciar el overflow que se daría, ya que el resultado debiera de ser de cinco bits y no de cuatro, tal y
como hemos declarado la salida.
Deberemos tener en cuidado en este caso, ante la cantidad de if's y de elsif, dentro de cuál de ellos estamos, a la hora de
cerrarlos o de asignar cambios a señales, ya que el resultado puede ser distinto al que queríamos.
Ahora deberemos extremar el cuidado con los if's que hay, por lo que deberemos organizar el testo de tal forma que no nos
lleve a error. Ante tantos if's, deberemos plantearnos si es mejor utilizar la sentencia case.
En los circuitos secuenciales, la salida, a diferencia de los combinacionales, se debe tanto a las
entradas actuales como a los valores anteriores, de esta forma, las salidas toman distintos niveles
lógicos para las mismas entradas de contro. Emplearemos el principio de los circuitos secuensiales para
implementar un circuito cuyas salidas variaran en función del tiempo
Elige en la lista una práctica para empezar, sin más que pinchar en el botón adecuado. Para volver
pulsa el botón que está al lado del título
Decodificador 2/4
Decodificador 2/4
Contador
Contador
Secuenciador
Secuenciador
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.uno.all; Es necesario poner en la cabecera del
4 entity decoder is port ( código, la llamada a la librería "uno", que
5 seleccion:in std_logic_vector(1 downto 0); es donde guardaremos a este
6 enable1,enable2: in bit; decodificador como un componente.
7 salida: out std_logic_vector(3 downto 0));
8 end decoder;
16 en if;
17 end process decodificador;
18 end archidecoder;
El habilitador de entrada de este decodificador, enable1 y enable2, le confiere a este diseño la particularidad de poder
controlar tanto su estado de funcionamiento así como sincronizarlo con la entrada de reloj, para que funcione a la vez que el
contador al que estará unido.
Entidad 2: Contador
Este circuito tiene como entradas las señales dereloj (clk) y de información (d), siendo activa por flanco de subida. Como
salida se tiene al puerto q.
1 library ieee;
2 use ieeee.std_logic_1164.all;
3 use work.std_arith.all;
4 use work.uno.all;
5 entity count is port (
6 clk,reset:in bit;
7 conya :buffer std_logic_vector(1 downto 0));
8 end count;
Esta entidad es muy sencilla ya que las entradas y la salida son de un único bit de ancho. La entrada reset, se emplea paea
habilitar o sesabilitar al reloj.
Arquitectura 2: Contador
Un contador es un circuito secuencial que repite su estado cada cierto número de pulsos de reloj. El número de estados por los
que pasa antes de volver al mismo estado se llama "modulo".
Estas dos entidades, tanto el contador como el decodificador, estarán incluidas dentro de un paquete o Package para poder
hacer uso de ellos através de la utilización de dichos elementos como componentes. Ambos circuitos serán guardados dentro
de la librería personalizada "uno".A continuación se lista el código de este paquete
A continuación se edita el paquete (package) "uno" donde se guarda como componentes tanto el contador como el
decodificador. Estos elementos serán posteriormente llamados para formar parte del diseño completo del secuenciador que ha
sido diseñado dentro de "total".
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.std_arith.all;
4
5 package uno is
6 component
7 count port (
8 clk,reset:in bit;
9 conta :buffer std_logic_vector(1 downto 0));
10 end component;
11 component
12 decoder port (
13 seleccion :in std_logic_vector(1 downto 0);
14 enable1,enable2:in bit;
15 salida :out std_logic_vector(3 downto 0));
16 end component;
17 end package;
18
19 library ieee;
20 use ieee.std_logic_1164.all;
21 use work.std_arith.all;
22 use work.uno.all;
23 entity count is port (
24 clk,reset:in bit;
25 conta:buffer std_logic_vector(1 downto 0));
26 end count;
27
28 architecture archicount of count is
29 begin
30 contador :process (clk,reset)
31 begin
32 if (reset='1') then conta <= (others => '0') ;
33 elsif clk'event and clk='1' then conta <= conta + 1;
34 end if;
35 end process contador;
36 end archicount;
37
38 --descripcion del decodificador 3/8 (74ls138)
39 library ieee;
40 use ieee.std_logic_1164.all;
41 use work.uno.all;
42 entity decoder is port (
43 seleccion :in std_logic_vector(1 downto 0);
44 enable1,enable2:in bit;
45 salida :out std_logic_vector(3 downto 0));
46 end decoder;
47
48 architecture archidecoder of decoder is
49 begin
50 decodificador:process(seleccion,enable1,enable2)
51 begin
52 if enable2='1' then salida<=(others=>'0');
53 elsif enable2='0' and enable1='0' then salida<=(others => '0');
54 elsif(enable1='1') then
55 case seleccion is
56 when "00" => salida <= "0001";
57 when "01" => salida <= "0010";
58 when "10" => salida <= "0100";
59 when "11" => salida <= "1000";
60 when others => salida <="1111";
61 end case;
62 end if;
63 end process decodificador;
64 end archidecoder;
La especificación de un package consta de dos partes claramente diferenciadas, la primera es la cabecera donde se declara
como componente (component), los elementos que van a estar contenidos en dicha librería. A continuación aparece la
declaración de las entidades y arquitecturas de cada uno de los componentes de forma similar a como se declara un circuito
cualquiera, la única aclaración a tener en cuenta sera que habra que incluir necesariamente la sentencia use work.uno.all;
donde se especifíca el nombre de la librería en la que estamos.
Entidad 3: Secuencaidor
En esta practica se parte de la gráfica siguente. Las especificaciones del diseño nos pide que creemos un circuito que se ajuste
al comportamiento de la siguente gráfica.
Tras el análisis de los requerimientos, se diseña el siguente circuito donde se implementara, previamente, los componentes
count y deco, que seran guardados en el package uno. El circuito total se implementara haciendo las llamadas pertinentes a
traves de sentencias estructurales port map a dicho package.
1 library ieee;
2 use ieee.std_logic_1164.all;
3 use work.uno.all;
4 entity total is port(
5 clk,reset:in bit;
6 salida:out std_logic_vector(3 downto 0));
7 end total;
Arquitectura 3: Secuenciador
En esta parte se unen el contador y el decodificador antes descrito, para conseguir el comportamiento del secuenciador
previamente diseñado. Para ello se hace uso de la señal intermedio, que nos sirve para pasar las señales propias del count y
llevarlas a la entrada del decoder.
DEPARTAMENTO DE ELECTRONICA Y
TELECOMUNICACIONES
ESCUELA TECNICA DE INGENIERIA INDUSTRIAL
Castellano Euskara
Euskal-Herriko Unibertsitatea
Elektronika eta Telekomunikazio Saila
La Casilla Plaza, 3, Bilbao (Bizkaia)
Tlf: 94 601 43 04
CiberStats.
Pincha para
estadisticas.
Profesores
Proyectos Fin de
Carrera
Asignaturas
Virtuales DEPARTAMENTO DE
ELECTRONICA Y
Asignaturas
Impartidas
TELECOMUNICACIONES
Curriculums de
alumnos
Examenes
ESPECIALIDAD
Irakasleak
Ikasketa
bukaerako
Proiektuak
Ikasleen
Curriculum-ak ESPEZIALITATEA
Azterketak INGENIARITZA
TEKNIKO
Hasiera
ELEKTRONIKOA
Simulación
En esta parte del tutorial, se ofrecen los ficheros fuentes (.vhd) de los diseños
presentados en las prácticas expuestas. Para ello deberemos tener el programa
WARP2 , versión 4.1 o superior o cualquier compilador de VHDL ya que este es un
lenguaje estandar.
Multiplexor
Multiplexor de 3 canales, 4 bits y con enable.
Demultiplexor
http://det.bp.ehu.es/vhdl/pagina/express/simula.htm (2 of 14) [03/04/02 13:52:48]
Página de simulación
Codificador
Codificador binario 4 a 2 sin prioridad, diseño incorrecto.
dentro fuera EO
HXXXXXXX HHH L
LHXXXXXX HHL L
LLHXXXXX HLH L
LLLHXXXX HLL L
LLLLHXXX LHH L
LLLLLHXX LHL L
LLLLLLHX LLH L
LLLLLLLH LLL L
LLLLLLLL LLL H
Decodificador
Decodificador 3 a 8
Entrada Salida
g1 g2
(2 a 0) (7 a 0)
X H XXX HHHHHHHH
L L XXX HHHHHHHH
H L LLL LHHHHHHH
H L LLH HLHHHHHH
H L LHL HHLHHHHH
H L LHH HHHLHHHH
H L HLL HHHHLHHH
H L HLH HHHHHLHH
H L HHL HHHHHHLH
H L HHH HHHHHHHL
Decodificador 74154************************
Entrada Salida
(BCD) (LED)
LLLL HHHHHHL
LLLH HHLLLLL
LLHL HLHHLHH
LLHH HHHLLHH
LHLL HHLLHLH
LHLH LHHLHHH
LHHL LHHHHHH
LHHH HHLLLHL
HLLL HHHHHHH
HLLH HHHLHHH
LLLL LLLLLLL
Comparador
Restador
Sólo hay que ejecutar el programa de simulación y pedirle que abra el
fichero deseado
Multiplicador
Sólo hay que ejecutar el programa de simulación y pedirle que abra el fichero
deseado
ALU
Sólo hay que ejecutar el programa de simulación y pedirle que abra el
fichero deseado
Flip Flop
Flip Flop RS
Flip Flop JK
Flip Flop D
Flip Flop T
Contador
Contador ascendente
Secuenciador
Decodificador de 2 a 4
Contador
Secuenciador
Autómatas
Sólo hay que ejecutar el programa de simulación y pedirle que abra el
fichero deseado