Documentos de Académico
Documentos de Profesional
Documentos de Cultura
VHDL Bueno PDF
VHDL Bueno PDF
Curso 01/02
LENGUAJE VHDL
1. Introducción. ................................................................................................................................................. 2
Bibliografía...................................................................................................................................................... 28
1. Introducción.
Con la creciente complejidad de los diseños digitales ha aparecido una necesidad de describir un
circuito de la forma más eficiente y práctica posible. Un lenguaje de programación ofrece la
posibilidad de un alto nivel de abstracción y es la solución adecuada para dicha tarea. Entre los
lenguajes para la descripción de circuitos digitales, el VHDL es el que está alcanzando mayor
popularidad, por ser un estándar y por su amplio campo de aplicación, desde el modelado para la
simulación de circuitos, hasta la síntesis automática de circuitos.
El significado de las siglas VHDL es Very high speed integrated circuit (VHSIC) Hardware
Description Language. VHDL es una forma en que los humanos y las máquinas puedan leer y
entender la funcionalidad y la organización de sistemas hardware digitales. Las ventajas del VHDL
son:
En este curso, se explicarán los fundamentos del VHDL pensando en su utilización para
programar dispositivos de tipo PLD o FPGA. No conviene olvidar que el VHDL en sí mismo no
está asociado a dispositivos programables, sino que es una descripción de un circuito en alto nivel.
De cualquier modo, una descripción que sea sintetizable es casi siempre válida también para
simulación, mientras que una descripción para simulación puede tener más problemas a la hora de
compilarla sobre un dispositivo hardware.
En este capítulo se introducen mediante ejemplos, la sintaxis del lenguaje, de forma que se
cubran los bloques más habituales en diseño digital, intentando que rápidamente sea posible escribir
programas útiles, por comparación con los ejemplos dados en los apuntes. No pretende ser una guía
extensa del VHDL. Para ello, y para resolver problemas concretos, es necesario acudir a la
bibliografía o a los manuales que cada fabricante ofrece.
herramientas de otras marcas. Se puede conseguir una versión gratis (de estudiante) en
www.altera.com, que aunque obviamente no tiene todas las posibilidades de la herramienta
completa, permite el contacto con un entorno profesional.
Comentarios: empiezan por dos guiones "--" seguidos, hasta el final de línea.
Identificadores: Se usan para dar nombre a los diferentes objetos del lenguaje. Pueden ser
cualquier nombre compuesto por letras y números, incluyendo el símbolo de subrayado "_". Nunca
puede contener uno de los símbolos especiales, no puede empezar por un número ni contener dos
subrayados seguidos. Las mayúsculas y minúsculas se consideran iguales. Tampoco puede coincidir
con una de las palabras reservadas del lenguaje (que tienen un significado predefinido).
Números: Se considera que están en base 10. Se admite la notación científica para números en
coma flotante. Es posible escribir números en otras bases utilizando el símbolo #. Así,
2#11000100# es un número en base 2, 16#c4# en hexadecimal. No obstante, los números son
mucho menos utilizados en VHDL que en otros lenguajes de programación, puesto que es un
lenguaje orientado a diseños digitales, donde los valores que se manejan son bits o cadenas de bits.
Cadenas de bits: los bits son en realidad caracteres, y es posible formar con ellos cadenas y
representar números. "1110100", O"126", X"FE"; el primero es binario, el segundo octal, indicado
por la O delante de la cadena, el último es hexadecimal, indicado por la X delante de la cadena.
Operadores.
& concatenación. Concatena cadenas: así "110" & "001" representa "110001".
SLL, SRL Desplaza un vector de bits un número de bits a la izquierda o a la derecha, rellenando
con ceros los huecos libres
SLA, SRA Como el anterior pero el desplazamiento conserva el signo, el valor del bit más
significativo.
ROL, ROR rotación a izquierda o a derecha. Como un desplazamiento, pero los huecos que se
forman son ocupados por los bits que van saliendo.
=, /= igualdad o desigualdad
<, <=, >, >= menor, menor o igual, mayor, mayor o igual
Not, and, nand, or, nor, xor, xnor.
Tipos de datos.
La sintaxis de VHDL es estricta con respecto a los tipos. Cualquier objeto que se defina debe
tener un tipo. En VHDL no existen tipos propios del lenguaje, pero existen los mecanismos para
poder definir cualquier tipo. Las librerías que se declaran al principio del programa incluyen los
tipos más habituales.
Tipos escalares: Son tipos simples. Tienen un orden que permite usar operadores
relacionales con ellos. Pueden ser enumerados, enteros, flotantes y físicos.
Reales (coma flotante): Se deben definir también en un rango, pero con límites reales.
Físicos: Datos que trabajan con magnitudes físicas, es decir, con valor y unidades. Hay un tipo
predefinido en VHDL que es el tiempo, time.
El estandar IEEE 1164 define un tipo enumerado adicional, std_ulogic, y varios subtipos. El tipo
std_ulogic se define con una lista de 9 posibilidades:
type std_ulogic is ('U', -- Sin inicializar
'X', -- Fuerza a desconocido
'0', -- fuerza a 0
'1', -- fuerza a 1
'Z', -- Alta impedancia
'W', -- Desconocido débil
'L', -- 0 débil
'H', -- 1 débil
'-', -- no importa
);
El subtipo std_logic proviene del std_ulogic y la lista de valores es la misma, pero este subtipo
tiene una función de resolución (concepto en el que no entraremos). En la práctica, nosotros
usaremos muy a menudo el tipo std_logic para síntesis. Es más amplio que el tipo bit, al incluir los
estados de alta impedancia y de no importa. Para usar el subtipo std_logic hay que incluir el paquete
std_logic_1164 de la librería ieee.
A los elementos de una matriz se accede mediante los índices. Si dato es una señal de tipo word,
dato(3) es el elemento 3 de dato, dato(29 downto 25) es una parte del array.
Son también de gran importancia los tipos unsigned y signed. Representan vectores de std_logic
pero considerándolos con signo. El tipo unsigned representa valores numéricos positivos o cero. El
tipo signed representa valores tanto negativos como positivos en complemento a 2. Esto tiene una
implicación a la hora de hacer comparaciones y otras operaciones.
Subtipos.
Atributos.
Los elementos en VHDL pueden tener información adicional basada en atributos. Estos atributos
están asociados a ciertos elementos del lenguaje y se manejan mediante la comilla simple '.
Por ejemplo, si t es una señal de un tipo enumerado, entero, flotante o físico, se tienen los
siguientes atributos:
t'left límite izquierdo del tipo left
t'right
t'low menor de los valores en t
t'high
t'length da el número de elementos de t.
Otro atributo que aparece con frecuencia es 'range. Da el rango de un objeto limitado. Por
ejemplo, si definimos
signal word: std_logic_vector(15 downto 0);
En ciertos programas informáticos, hay también atributos que permiten agregar información
adicional a los objetos que se están definiendo en VHDL. Estas informaciones adicionales sirven
para pasar información a las herramientas de diseño que se estén utilizando en VHDL, por ejemplo
si queremos que ciertas señales estén en determinados pines de una PLD. En MaxPlusII se realiza
de forma gráfica.
Constantes.
Una constante es un elemento que se inicializa a un determinado valor que no puede ser
cambiado:
En el segundo caso max_size no tiene ningún valor asociado. Esto se permite siempre y cuando
el valor sea declarado en algún otro sitio.
Variables.
Su valor puede ser alterado en cualquier instante. Es posible asignarle un valor inicial:
Las variables sólo tiene sentido en bloques donde la ejecución es en serie: subprogramas y
procesos (process).
Señales.
La señal no es un objeto del lenguaje que guarda un valor, sino que lo que hace es guardar un
valor y hacerlo visible en el momento adecuado. Esto es, se puede decir que la señal tiene dos
partes, una donde ese escribe y que almacena el valor, y otra que se lee y que no tiene por qué
coincidir con lo que se acaba de escribir.
Desde un punto de vista más cercano al mundo de los circuitos digitales, una señal se entendería
como un nodo en el circuito (nodo en el mismo sentido que en SPICE). Las entradas y salidas de un
bloque digital deben ser definidas como señales. Asimismo, cualquier posible conexión real en el
circuito debe ser definida como señal.
Las asignaciones de señales se realizan con el operador "<=", mientras que las de constantes y
variables utilizan el operador ":=".
Entidades y arquitecturas.
library ieee;
use ieee.std_logic_1164.all;
Las dos primeras líneas del programa contienen las librerías (ieee) y los paquetes
(std_logic_1164.all) que serán utilizados en el programa. Los tipos más habituales están declarados
en esta librería, por lo que su uso es casi imprescindible. Como en el programa se usa el tipo
std_logic, es necesario incluir este paquete.
En la entidad llamada MUX2to1_a, definimos las salidas (Y) y las entradas del sistema (A,B y
Sel). Estas señales son puertos (port). Todas ellas están definidas como de tipo std_logic. En este
caso, se trata de un multiplexor de dos canales. En la arquitectura, se define lo que realiza la
entidad. En este caso, la descripción son unas simples ecuaciones booleanas.
Los puertos de las entidades se definen con un modo. Los modos pueden ser de 4 clases: in, out,
buffer, inout:
- in. Entrada a la entidad.
- out. Salidas de la entidad. Este tipo de puertos no se considera legible dentro de la entidad (por
ejemplo, no puede aparecer a la derecha en una asignación)
- buffer. Es como el modo out, pero es legible dentro de la entidad. Dicho de otro modo,
permite la realimentación de la señal en la arquitectura.
- inout. Para señales bidireccionales, se tiene que declarar el puerto como inout, que permite que
se pueda escribir o leer desde el exterior.
Lenguajes como VHDL, pensado para describir circuitos, deben ser ante todo concurrentes. Un
circuito no se ejecuta en serie, sino que las conexiones entre componentes siempre actúan. No
obstante, el lenguaje VHDL también permite descripciones con ejecución en serie, que hacen más
fácil la programación en abstracto.
library ieee;
use ieee.std_logic_1164.all;
end archBCD_9;
En este caso, usamos varios when ... else anidados. Cuando la entrada A toma un número en
BCD, la línea correspondiente se activa.
¿Qué significa en este caso que la estructura es concurrente? Por ejemplo, es imposible asignar
otros valores a la salida Y en el mismo programa. Así, este otro código daría un error de
compilación:
-- Incorrecto
library ieee;
use ieee.std_logic_1164.all;
Aquí hemos escrito dos decodificadores distintos, uno escrito después del otro. Es un error
pensar que, puesto que el segundo bloque está después del primero, es la activación en baja de las
líneas la que se va a ejecutar. Este programa no puede compilarse. En Pascal por ejemplo, no hay
ningún problema en dar dos valores distintos a las variables, quedándose con el último. En realidad,
lo que estoy haciendo es, pensando en un circuito, conectar las salidas de dos decodificadores
distintos al mismo punto (Y(9), Y(8) etc.). Por tanto, ¿qué ocurre cuando los dos decodificadores
manden señales distintas a las salidas? Esta incompatibilidad es la impide la compilación. (Siendo
estrictos, el VHDL permite este tipo de situaciones si se incluye un mecanismo de resolución de las
señales, que indica qué ocurre en caso de incompatibilidad; éste es un concepto de VHDL
avanzado, que no veremos). Esto es válido para cualquier estructura concurrente.
with A select
Y<="0000000001" when "0000",
"0000000010" when "0001",
"0000000100" when "0010",
"0000001000" when "0011",
"0000010000" when "0100",
"0000100000" when "0101",
"0001000000" when "0110",
"0010000000" when "0111",
"0100000000" when "1000",
"1000000000" when "1001",
"0000000000" when others;
end archBCD_9;
Según el valor de la señal A, se produce una asignación u otra. Es importante la última línea,
when others. Si no hemos agotado todas las posibilidades de la entrada A, es necesaria esta última
línea para indicar qué debe hacerse en los casos que no se pongan de forma explícita anteriormente.
En la práctica, es casi obligatorio ponerlo. No conviene olvidar que al definir A como un tipo
std_logic_vector(3 downto 0), no sólo hay 16 posibilidades para A, sino que el resto de valores
posibles para A también cuentan, por ejemplo "ZZZZ", o "----". Por ello, incluso un decodificador
de 4 a 16 líneas necesitaría when others al final, puesto que se ha definido la entrada A como de
tipo std_logic_vector.
Se trata de partes del programa con una ejecución en serie, definidos dentro de unidades que
comienzan con la palabra clave process. En un mismo programa puede haber múltiples bloques
process. Cada uno de ellos equivale a una instrucción concurrente. Es decir, aunque internamente el
proceso describe el comportamiento de un circuito mediante una ejecución de instrucciones en
serie, el compilador para síntesis deduce un circuito a partir de un proceso, y por tanto es
concurrente con el resto de las instrucciones (no puedo asignar un valor a una señal dentro y fuera
de un proceso).
library ieee;
use ieee.std_logic_1164.all;
El proceso contiene un identificador, p1 (el nombre que queramos). Posee además una
"sensitivity list" o lista sensible, (a,b,c), que indica que el proceso se ejecutará cuando haya un
cambio en una de estas variables. Como en cualquier ejecución en serie, hay que tener cuidado al
anidar los if-then, de forma que el resultado sea el esperado. La utilización de else es opcional, pero
si no se agotan todas las opciones, puede dar lugar a latches. Cuando se utilizan varios if then
anidados, puede usarse una contracción de else if, elsif. En caso de usar la primera forma, es
necesario cerrar el nuevo if que se crea. En caso de usar la forma contraída, no hay que cerrar
ningún if adicional. Si la condición que se ha de cumplir se refiere a la detección de un flanco de
reloj y va precedida de otro if previo, es necesario usar elsif en la detección del flanco de reloj, por
ejemplo, en un contador con reset asíncrono, la condición de detección del reset va antes de la
detección de flanco de reloj.
Es conveniente dejar claro en el código qué es lo que tiene que asignarse a las señales de salida
en el proceso para todas las posibilidades de las señales en la lista sensible. En caso de que haya
casos no especificados al anidar los if, el compilador guarda el valor anterior de la señal en
cuestión, es decir se trata de un latch.
La forma en que un proceso se evalúa puede ser entendida del siguiente modo. Cuando una señal
que se encuentra en la "sensitivity list" del proceso cambia, este es ejecutado de forma secuencial.
Una vez que se ha llegado a la última instrucción, el proceso se detiene y las señales se actualizan.
Es decir, se puede imaginar como los valores antes y después de un paso de simulación.
library ieee;
use ieee.std_logic_1164.all;
y<= a or b or c;
end archand3;
Es muy importante distinguir entre variables y señales dentro de un proceso. Las señales no
cambian su valor hasta que no acaba el proceso. Las variables sólo se definen dentro del proceso, (
o dentro de bloques de ejecución en serie, como funciones y procedimientos) y cambian su valor en
el momento en el que tienen una asignación.
Por ejemplo, consideremos estos dos procesos:
En definitiva, supongamos que antes de iniciarse la ejecución se tiene "c=2", "a=4", y "b=6".
Entonces al final de la ejecución de este proceso se tendrá "c=b=6", "x=4", "y=4". De todas formas,
puesto que la señal "c" ha cambiado y, como se encuentra en la lista sensible, el proceso se vuelve a
ejecutar, quedando finalmente "c=b=6", "x=y=8".
En el programa de la derecha "c" es una variable. La definición de esta variable se hace dentro
del proceso, puesto que sólo tiene sentido dentro de la ejecución serie. También "c" desaparece de
la lista sensible puesto que las variables son internas a los procesos (o a los subprogramas) y nunca
pueden formar parte de las listas sensibles.
La ejecución es muy simple. Primero "c" toma el valor de "a", "c:=a". Hay que hacer notar el
símbolo de asignación ":=" para variables. Como "c" es una variable, "c" toma el valor 4 justo en
este momento. A continuación se hace la fuente de "x" igual a "c+2", es decir "x" tomará el valor 6
el próximo paso de simulación. A continuación se hace "c:=b", de manera que ahora "c" vale 6.
Después viene "y<=c+2", por lo que "y" valdrá 8 cuando se acabe la ejecución. Al finalizar el
process se tiene que "x=6" e "y=8", y no se volverá a ejecutar puesto que ni "a" ni "b" han
cambiado.
En la práctica, las variables pueden ser útiles para hacer algunas construcciones, y son
inevitables en los índices que recorren los vectores, pero se puede prescindir de su uso en la
mayoría de las ocasiones. Un proceso con variables y señales puede ser bastante complicado de
comprender. Hay que evitar el uso de
Conviene recordar también que las señales sólo se actualizan al final del proceso. No hay que
poner sentencias en el proceso que dependan de una asignación previa a una señal.
Se trata de una estructura de ejecución en serie. Es parecido al with ... select, pero más general,
puesto que en cada caso no sólo se puede hacer una asignación, sino que después de cada "=>"
puede escribirse una sentencia o un conjunto de ellas.
library ieee;
use ieee.std_logic_1164.all;
end process;
end archmux4to1;
Como en el caso del with ... select es necesario completar con when others, cuando no se han
revisado todos los casos posibles.
library ieee;
use ieee.std_logic_1164.all;
entity cuatromux2to1 is
port(
a: in std_logic_vector(7 downto 0);
sel: in std_logic;
y: out std_logic_vector(1 to 4));
end cuatromux2to1;
begin
end archcuatromux2to1;
Este programa es poco práctico, sólo se escribe para mostrar, además de los bucles, el uso de los
arrays. Hemos definido un array de vectores de std_logic. Es en definitiva una matriz:
Hay que hacer notar también el uso del for loop, así como del atributo 'range. Este atributo da el
rango de un objeto. En nuestro caso es de 1 a 4, puesto que hemos definido el tipo lineas como un
array (1 to 4).
La señal dum está definida como de tipo lineas. Entonces dum(1) es el primer elemento del
array. Como cada elemento es un std_logic_vector, dum(1) es un std_logic_vector, y dum(1)(1) es
su primer elemento. Una definición alternativa de un array con el mismo número de elementos es:
En este caso, si dum fuese de tipo lineas, un elemento de dum necesita dos índices, dum(1,0) por
ejemplo, y cada elemento es un std_logic.
Es interesante también la manera de referirse a una parte de una cadena, en la forma a(7 downto
6), que selecciona dos elementos de la cadena total "a", definida como un std_logic_vector(7
downto 0). Hacemos hincapié asímismo la definición de señales y de tipos dentro de la
arquitectura:
La señal dum no es de salida ni de entrada. Se podría entender como un nodo interno al circuito.
De forma análoga al for ... loop se define el while ... loop. Existe también la posibilidad de salir
de los bucles con next y exit. (*) Nota: exit no está soportado por MaxPlusII
Este tipo de descripción es cercano a una net-list de otras herramientas CAD. La descripción
estructural es especialmente interesante para la incorporación de elementos de biblioteca al diseño y
la realización de diseños jerárquicos a partir de componentes. Consideremos dos entidades,
and_de_3 y or_de_n:
library ieee;
use ieee.std_logic_1164.all;
entity and_de_3 is port(
a,b,c: in std_logic;
y: out std_logic);
end and_de_3;
architecture archand_de_3 of and_de_3 is
begin
y<='1' when a='1' and b='1' and c='1' else
'0';
end archand_de_3;
-----------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity or_de_n is
generic(n: integer:=2);
port(
a: in std_logic_vector(n-1 downto 0);
y: out std_logic);
end or_de_n;
architecture archor_de_n of or_de_n is
begin
p1: process(a)
variable i: integer range 0 to n-1;
variable res_parcial: std_logic;
begin
res_parcial:='0';
bucle:for i in 0 to n-1 loop
res_parcial:=res_parcial or a(i);
end loop bucle;
y<=res_parcial;
end process;
end archor_de_n;
Obsérvese que en el segundo caso existe una definición de una entidad con un parámetro
variable, por ello se usa entity ... generic. De esta forma se puede definir una puerta or de n
entradas, siendo por defecto de dos entradas.
Estas entidades se pueden definir en una librería, con el paquete (package)correspondiente. Esto
permite a un programa principal llamar a esos componentes (es decir, otras entidades definidas en
librerías), asociándoles los puertos correspondientes. Veremos como definir librerías en MaxPlusII.
Suponiendo que la librería se llama "milibrería" y que el paquete se llama "puertas", el programa
principal quedaría:
-- Programa principal
library ieee;
library milibreria;
use ieee.std_logic_1164.all;
use milibreria.puertas.all;
La entidad and_de_3 es una puerta and de tres entradas y la segunda or_de_n una puerta or de n
entradas, donde n es un parámetro que por defecto es 2. Es interesante el uso de generic antes de
port, para permitir que la entidad tenga uno o varios parámetros variables.
En la arquitectura del programa principal se hace una asignación de los "nudos" de cada
componente. La orden básica es port map:
La siguiente orden:
es otra asignación usando el operador "=>", en este caso a una componente or_de_n. Como no
hacemos referencia al tamaño (parámetro n), se toma por defecto el definido en la entidad or_de_n,
es decir, 2. En el paréntesis, podemos ver otra forma de realizar una asignación, en lugar de por
posición como en el ejemplo anterior, mediante el operador =>. Asignamos la señal "dum1" al
puerto llamado "a" de or_de_n, y la señal "z" al puerto "y". "a" e "y" son los nombres de los puertos
en el componente, mientras que "dum1" y "z" son los nombres de las señales en el programa
principal. La señal "dum1" es una señal intermedia, que no es más que la concatenación de "r" y
"p". Así obtengo un std_logic_vector, que es compatible con la definición del puerto "a" en la
componente "or_de_n". Si utilizo esta forma de asignación, no necesito dar las señales en el mismo
orden que en la definición del componente.
La siguiente orden:
es una puerta or de 3 entradas. Generic map (3) sirve para pasar el parámetro al componente, en
este caso se le dice que el tamaño del vector de entrada es 3.
Finalmente, la salida final, "y", no se define mediante componentes sino mediante operadores
booleanos:
y<=x or z or w;
Se puede, por tanto, mezclar tipos de descripciones dentro de un mismo programa, siempre que
una misma señal no sea escrita en distintos puntos del programa.
Las señales dum1 y dum2 sólo sirven para tener una compatibilidad con los tipos definidos en
las componentes. Así, or_de_n admite como entrada un std_logic_vector. Por tanto, debo pasarle un
objeto del mismo tipo.
U? U?
2 z 2
1 3 1
3 4
p
y
OR2 OR3
U?
2 w
3 1
4
OR3
Es posible definir también las componentes and_de_3 y or_de_n en un único programa, en lugar
de en una librería. En el listado, se comenzaría por las entidades y arquitecturas de cada uno de
ellos, repitiendo cada vez las librerías que se usen en cada entidad-arquitectura. En la arquitectura
principal, se definen las componentes que se van a utilizar, mediante las sentencia component.
Los nombres y los modos de los puertos en estos componentes deben coincidir con los de la
entidad definida para cada componente, y deben también colocarse en el mismo orden.
library ieee;
use ieee.std_logic_1164.all;
-- Programa principal
library ieee;
use ieee.std_logic_1164.all;
begin
u1: celdasumadora port map(a(0),b(0),cin,sum(0),c(0));
bucle:for i in 1 to 7 generate
begin
u2: celdasumadora port map(a(i),b(i),c(i-1),sum(i),c(i));
end generate;
cout<=c(7);
end archsumador2;
Es posible también definir varias arquitecturas para un mismo componente, y asignar a cada
sentencia la arquitectura adecuada en cada caso (sentencia configuration).
ATENCIÓN: En MaxPlusII, existen bastantes problemas a la hora de la compatibilidad de los
modos (in, out, buffer, inout) tal y como se han definido en un componente y el modo de la señal
que se coloca en el programa principal. Especialmente problemático resulta el modo buffer. La
forma más elegante de evitar estos problemas es definir señales de apoyo en la arquitectura. Al estar
definidas en la arquitectura, no tienen modo y pueden escribirse sin problemas en los port map. Las
verdaderas variables pueden ser obtenidas a partir de las de apoyo por simples asignaciones.
MÁS ATENCIÓN: MaxPlusII no permite el uso de constantes como puertos de componentes en
el comando port map. Es necesario usar siempre señales. Esto no es en la práctica un
inconveniente. Una señal llamada uno a la que se le asigna un '1' (uno<='1') fuera de un proceso o
función es en la práctica una constante puesto que al ser un lenguaje concurrente no le puedo
asignar un valor en ningún otro lugar del programa.
Como otros lenguajes de programación, VDHL permite el uso de subprogramas, que contienen
una porción de código y a los cuales se les puede llamar. La instrucción component es, de algún
modo, un subprograma. Veremos a continuación otras dos estructuras que permite el VHDL:
funciones y procedimientos.
Son similares a las estructuras de otros lenguajes. Son subprogramas a los que se les pasan unos
parámetros. Las diferencias entre funciones y procedimientos son:
-Una función siempre devuelve un valor, mientras que un procedimiento sólo puede devolver
valores a través de los parámetros que se le pasen.
-Los argumentos de una función son siempre de entrada (in), por lo que dentro de la función sólo
se pueden leer. No es necesario especificar el modo. En el procedimiento pueden ser de entrada, de
salida o de entrada y salida, por lo que pueden sufrir modificaciones. Por defecto es in, pero out,
buffer, e inout también son válidos.
-Una función no tiene efectos colaterales, pero un procedimiento sí, es decir, puede provocar
cambios en objetos externos a él debido a que pueden cambiar las señales aunque no se hubiera
especificado en el argumento. Es decir, en los procedimientos se permite realizar asignaciones sobre
señales declaradas en la arquitectura y, por tanto, externas al procedimiento.
-Las funciones, como devuelven un valor, se usan en expresiones, mientras que los
procedimientos se llaman como una sentencia secuencial o recurrente.
-La función debe contener la palabra clave return seguida de una expresión puesto que siempre
devuelve un valor, mientras que en el procedimiento no es necesario.
-Una función jamás puede tener la instrucción wait, mientras que un procedimiento sí.
-En las funciones es necesario especificar qué tipo de objeto se devuelve. Como las funciones
siempre devuelven algo, esto implica que debe existir una instrucción return en el interior del
cuerpo de la función, seguida de una expresión, que es precisamente lo que se devuelve. El uso de
return en procedimientos es posible, pero no lleva una expresión puesto que los procedimientos no
devuelven nada. Simplemente, interrumpe la ejecución del procedimiento.
-Las funciones pueden definirse en la parte de declaraciones de una arquitectura, en cuyo caso la
definición de la función sirve como declaración de la función. Este es el ejemplo que se muestra
más abajo. Otra manera de definirlas es declararlas en un paquete "package", incluyendo la
definición de la función en el cuerpo del paquete "package body". De esta forma, la definición de
la función será visible a cualquier programa que utilice la orden use con el nombre del package
correspondiente. Lo mismo se aplica para los procedimientos.
Ejemplos:
El primer ejemplo es una célula sumadora de un bit construida con ayuda de una función,
llamada majority. Esta función necesita tres parámetros, y da como resultado un bit.
begin
sum<=a xor b xor carry_in;
carry_out<= majority(a,b,carry_in);
end architecture;
El segundo ejemplo es una puerta or por procedimiento. Un procedimiento define una puerta or.
Después, se llama a este procedimiento para realizar la puerta or en el programa principal.
begin
p: process(a,b)
variable va,vb,vz: bit;
begin
va:=a;vb:=b;
dff (va,vb,vz);
z<=vz;
end process;
end architecture;
En MaxPlusII los procedimientos deben ser llamados con variables, por lo que también deben
tener como parámetros variables. Por ello, el cálculo de la puerta or se realiza a través de variables.
En otros entornos informáticos esto no es necesariamente así. Los procedimientos en VHDL no son
algo tan usado como en otros lenguajes como el Pascal. La forma "natural" de hacer subprogramas
en VHDL es mediante el uso de componentes.
Para organizar ciertos diseños conviene definir ciertos elementos en una biblioteca, que luego se
usará en el programa principal. En la biblioteca se pueden incluir los ficheros de algunos elementos,
que incluyan las entidades y arquitecturas. Se incluyen también los paquetes ("packages"). Los
paquetes permiten introducir componentes (cuya definición de entidad y arquitectura puede estar en
otro fichero), tipos, funciones y procedimientos. Tienen una parte declarativa y otra descriptiva. Por
ejemplo, las sentencias que están casi siempre a principio de todo programa son:
library ieee;
use ieee.std_logic_1164.all;
Esto indica el uso de la librería "ieee"; dentro de ella se usa el paquete "std_logic_1164"
(sentencia use); y dentro del paquete se usan todos los elementos (".all"). Si se necesitase sólo uno,
bastaría poner el nombre del elemento.
La forma concreta de organizar los directorios y ficheros depende de la herramienta informática
concreta que usemos.
Por ejemplo, en MaxPlus II, supongamos que queremos construir realmente el ejemplo descrito
en el apartado 3.3 sobre la descripción estructural de programas. Allí se definieron dos componentes
and3 y orn. Cada uno puede estar en su fichero .vhd, "and3.vhd" y "orn.vhd". Colocamos ambos
ficheros en un directorio, por ejemplo, en el directorio "c:\ejemplo". Compilamos cada uno de ellos
por separado, como cualquier otro programa. Basta hacer una compilación funcional (con el
compilador activado, Processing/Functional SNF extractor), sin especificar una PLD concreta.
Después habría que hacer el paquete correspondiente:
library ieee;
use ieee.std_logic_1164.all;
package puertas is
Si ahora queremos compilar un programa principal usando and3 y orn, una vez que está escrito
deberíamos hacer lo siguiente antes de compilarlo:
a) En el editor de texto, en options- user libraries, se debe dar el "path" al directorio donde están
los bloques y el package. En nuestro caso sería "c:\ejemplo"
b) Con el compilador activo, en interfaces-VHDL Netlist reader settings se le da el nombre a la
librería y otra vez el "path" al directorio donde se encuentre. El nombre de la librería debe coincidir
con el encabezamiento asociado a library en el programa principal. En el ejemplo del apartado 3.3.
sería milibrería. Es independiente del nombre del directorio donde hayamos compilado el paquete.
c) Ya podemos compilar el programa principal, que deberá reconocer la nueva librería.
Las librerías existentes en MaxPlus II son las siguientes, según la ayuda on-line del propio
programa:
Algunas de estas librerías ofrecen funciones útiles. Destacamos por su especial interés:
Gates
lpm_and lpm_inv
lpm_bustri lpm_mux
lpm_clshift lpm_or
lpm_constant lpm_xor
lpm_decode mux
busmux
Arithmetic Components
divide* lpm_compare
lpm_abs lpm_counter
lpm_add_sub lpm_divide
lpm_mult
Storage Components
altdpram* lpm_latch
csfifo lpm_shiftreg
dcfifo* lpm_ram_dp
scfifo* lpm_ram_dq
csdpram lpm_ram_io
lpm_ff lpm_rom
lpm_fifo lpm_dff*
lpm_fifo_dc lpm_tff*
Other Functions
Altera also offers a variety of MegaCore/OpenCore functions. These functions are available from
Altera's world-wide web site at http://www.altera.com.
5. Ejemplos de VHDL.
library ieee;
use ieee.std_logic_1164.all;
Es importante darse cuenta de la detección del flanco de reloj, característica de todos los
sistemas síncronos. Dentro de un proceso, un if con clk'event (detecta cambios en clk) y clk='1'
(después del cambio vale uno) detecta un flanco de subida del reloj. Un flanco de bajada sería if
clk'event and clk='0'.
Todo lo que venga después de la detección del flanco de reloj corresponde a salidas que pasan a
través de biestables.
library ieee;
use ieee.std_logic_1164.all;
p: process(clk,d,set,reset)
begin
if reset='1' then q<='0';
elsif set='1' then q<='1';
elsif clk'event and clk='1' then
q<=d;
end if;
end process;
end;
Hay que hacer notar que las condiciones del reset y del set van antes que la detección de la señal
de reloj. Por ello, son asíncronas. Puedes intentar hacer un biestable con preset y puesta a uno
síncronas. También puedes verificar qué es lo que ocurre si set='1' y reset='1' ¿El biestable se pone a
cero o a uno?
5.3. Contadores.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
p: process(clk,d,q,load,up,reset)
begin
if reset='1' then q<=x"00";
elsif clk'event and clk='1' then
if load='1' then q<=d;
elsif up='1' then q<=q+1;
else q<=q-1;
end if;
end if;
end process;
end architecture;
También es interesante la definición del modo de q, como buffer. Ello es debido a que en la
ecuación q<=q+1, q aparece a la derecha, y una señal definida como modo out no podría ser leída.
Otra manera de evitar esto, sería la creación de una señal auxiliar en la arquitectura que
utilizaríamos dentro del proceso. Una vez fuera del proceso, podemos hacer que la salida sea igual a
esa señal. De este modo, la salida nunca aparece a la derecha en una asignación.
Recordamos también que si un vector es definido como (3 downto 0), el bit 0 es el menos
significativo.
Finalmente, hacemos notar también el uso de vectores en hexadecimal indicado por una x
delante del vector: x"00" es equivalente a 8 ceros binarios.
Un contador es también una máquina de estados, por lo que se puede definir como haremos en el
apartado siguiente. No obstante, esta definición es mucho más pesada.
La máquina de estado tiene una parte donde se describe la tabla de evolución de estados, y otra
parte donde se describe las salidas. Consideremos un ejemplo: dos sensores en la vía del tren
indican cuando pasa el tren por la vía (la vía es bidireccional). Los sensores se encuentran a ambos
lados de un cruce con una carretera. Un semáforo debe ponerse en rojo cuando se detecte el paso
del tren.
library ieee;
use ieee.std_logic_1164.all;
La función rising_edge() también detecta el flanco de subida mientras que falling_edge() detecta
el de bajada. Se aplica al tipo std_logic, pero no es aplicable al tipo bit.
Otra descripción alternativa podría ser definir un tipo con nombres de estado, más próximos al
lenguaje humano. Esto se haría en la arquitectura, antes del inicio (begin):
De esta forma, he creado un nuevo tipo que puede ser uno de los estados (de igual modo que el
tipo bit puede ser 1 o 0). Defino mi señal con ese nuevo tipo, y luego puedo usar esos nombres en el
programa, en lugar de cadenas de bits o nombres de constantes. La codificación de los estados la
elegirá el compilador.
Una salida de un biestable no se puede definir directamente como 'Z'. Para hacer que una salida
de un registro esté en alta impedancia, se puede definir una señal en la arquitectura, que sea como la
salida del registro, o que esté en alta impedancia cuando se cumplan las condiciones adecuadas.
Consideremos el caso de un contador con las salidas en alta impedancia y con capacidad de
carga por los mismos pines de salida del contador.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
Este contador se puede cargar por los mismos pines de salida (pines bidireccionales). Tales tipos
de puertos son necesariamente de tipo inout. El sistema que controla las entradas ld y oe debe hacer
que cuando se cargue un valor (ld='1'), a la vez no se habilite la salida (oe='0').
Bibliografía
K. Skahill, "VHDL for Programmable Logic", Ed. Addison-Wesley, 1996. Incluye en un CD-ROM el
programa Warp2.
F. Pardo, J.A. Boluda. "VHDL: Lenguaje para síntesis y diseño de circuitos digitales". Ed. Rama, 1999.
Incluye un CD-ROM con un simulador (Veribest) de VHDL (máximo 2000 líneas de código), y una
herramienta de Altera, MaxII-Plus, aunque la versión ya está desfasada.
S. Olloz, E. Villar, Y. Torroja, L. Teres: "VHDL, lenguaje estándar de diseño electrónico", Ed. McGraw-Hill.
www.altera.com Distribuye gratis un software (Max-Plus II) que admite entrada en VHDL y en AHDL,
captura esquemática, simulación funcional y temporal. Cada cierto tiempo actualiza las versiones libres. Es
una herramienta mucho más completa que Warp2.
Otras páginas Web:
http://tech-www.informatik.uni-hamburg.de/vhdl/
www.cypress.com
www.xilinx.com
www.actel.com
www.synopsis.com