Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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:
-Permite diseñar y modelar un sistema en varios niveles de abstracción: flujo de datos,
estructural, algorítmico.
-Una descripción en VHDL es independiente de la implementación hardware final del
proyecto. Puede ser sintetizado sobre una PLD o un ASIC. Incluso puede servir para
simulación exclusivamente.
-Permite el diseño Top-Down y modular, es decir, dividir un sistema complicado en
subsistemas más sencillos, tantas veces como sea necesario hasta poder resolver cada
módulo (subsistema) por separado. Ello facilita la prueba de cada módulo
independientemente y da más seguridad al correcto funcionamiento del sistema final. VHDL
ofrece sus propias maneras de definir "subprogramas".
-Es un estándar (IEEE Std 1076-1987, IEEE Std 1076-1993). No obstante, hay que decir que
cada fabricante ofrece sus propias librerías con funciones útiles no definidas en el estándar.
Por ello, el paso de un entorno de programación a otro no es trivial. Nosotros suponemos que
trabajamos con el estándar del año 93.
Inicialmente, VHDL fue diseñado para el modelado de circuitos digitales. Su utilización en
síntesis (implementación hardware) no es inmediata, aunque la sofisticación de las actuales
herramientas es tal que permite implementar diseños en un alto nivel de abstracción.
En este trabajo, 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.
Elementos sintácticos en VHDL.
Daremos unas breves definiciones de los elementos que se usan en VHDL
Comentarios: empiezan por dos guiones "--" seguidos, hasta el final de línea.
Símbolos especiales: de un sólo carácter + - / * ( ) .,:;&'"<>|=#
de dos caracteres ** => := /= >= <= <> --
Identificadores: 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.
Caracteres: Cualquier letra o número entre comillas simples: '2',’t’.
Cadenas: Conjunto de caracteres en comillas dobles: "hola"
Cadenas de bits: los bits son en realidad caracteres, 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.
Palabras reservadas: Son aquellas que tienen un significado especial en VHDL
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.
Enteros: Se definen incluyendo el rango.
type index is range 7 downto 1;
type integer is range –2147483648 to 2147483647; -- tipo predefinido
Reales (coma flotante): Se deben definir también en un rango, pero con límites reales.
Físicos: Datos que trabajan con magnitudes físicas, es decir, con valor y unidades. Hay un tipo
predefinido en VHDL que es el tiempo, time.
Enumerados: Pueden tomar cualquier valor en una lista.
type bit is ('0','1'); --Predefinido
type boolean is (FALSE, TRUE); -- Predefinido
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.
Tipos compuestos: Están compuestos por tipos escalares.
Entidades y arquitecturas.
La descripción de un circuito en VHDL consta al menos de dos elementos: la entidad y la
arquitectura. En la entidad se definen las señales de entrada y salida. En la arquitectura, se
define lo que hace el circuito. Previamente a la definición de ambas, se pueden incluir las
librerías y los paquetes necesarios en el programa. Veamos un ejemplo.
library ieee;
use ieee.std_logic_1164.all;
entity MUX2to1_a is port(
A, B: in std_logic;
Sel: in std_logic;
Y: out std_logic);
end MUX2to1_a;
architecture behavior of MUX2to1_a
is begin
Y<= ( B and Sel )
or ( A and not(Sel) );
end behavior;
Las dos primeras líneas del programa contienen las librerías (ieee) y los paquetes
(std_logic_1164.all) que serán utilizados en el programa. Los tipos más habituales están
declarados en esta librería, por lo que su uso es casi imprescindible. Como en el programa se
usa el tipo std_logic, es necesario incluir este paquete.
En la entidad llamada MUX2to1_a, definimos las salidas (Y) y las entradas del sistema (A,B y
Sel). Estas señales son puertos (port ). Todas ellas están definidas como de tipo std _logic. En
este caso, se trata de un multiplexor de dos canales. En la arquitectura, se define lo que
realiza la entidad. En este caso, la descripción son unas simples ecuaciones booleanas.
Los puertos de las entidades se definen con un modo. Los modos pueden ser de 4 clases: in,
out, buffer, inout:
in. Entrada a la entidad.
out. Salidas de la entidad. Este tipo de puertos no se considera legible dentro de la
entidad (por ejemplo, no puede aparecer a la derecha en una asignación)
buffer. Es como el modo out, pero es legible dentro de la 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.
¿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.
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).
process: if ... then
library ieee;
use ieee.std_logic_1164.all;
entity and3 is port(
a,b,c: in std_logic;
y: out std_logic);
end and3;
architecture archand3 of and3
is begin
p1: process (a,b,c)
begin
if (a='1' and b='1' and c='1') then y<='1';
else y<='0';
end if;
end process;
end archand3;
El proceso contiene un identificador, p1 (el nombre que queramos). Posee además una
"sensitivity list" o lista sensible, (a,b,c), que indica que el proceso se ejecutará cuando haya un
cambio en una de estas variables. Como en cualquier ejecución en serie, hay que tener
cuidado al anidar los if-then, de forma que el resultado sea el esperado. La utilización de else
es opcional, pero si no se agotan todas las opciones, puede dar lugar a latches. Cuando se
utilizan varios if then anidados, puede usarse una contracción de else if, elsif. En caso de usar
la primera forma, es necesario cerrar el nuevo if que se crea. En caso de usar la forma
contraída, no hay que cerrar ningún if adicional.
Si la condición que se ha de cumplir se refiere a la detección de un flanco de reloj y va
precedida de otro if previo, es necesario usar elsif en la detección del flanco de reloj, por
ejemplo, en un contador con reset asíncrono, la condición de detección del reset va antes de
la detección de flanco de reloj.
Es conveniente dejar claro en el código qué es lo que tiene que asignarse a las señales de
salida en el proceso para todas las posibilidades de las señales en la lista sensible. En caso de
que haya casos no especificados al anidar los if, el compilador guarda el valor anterior de la
señal en cuestión, es decir se trata de un latch.
La forma en que un proceso se evalúa puede ser entendida del siguiente modo. Cuando una
señal que se encuentra en la "sensitivity list" del proceso cambia, este es ejecutado de forma
secuencial. Una vez que se ha llegado a la última instrucción, el proceso se detiene y las
señales se actualizan. Es decir, se puede imaginar como los valores antes y después de un
paso de simulación.
Desde el punto de vista de la síntesis, en el proceso se determinará el valor de una o varias
señales, en función de otras que deben estar todas en la lista sensible. El compilador deducirá
el circuito lógico que dé el mismo comportamiento que el conjunto de las sentencias
secuenciales del proceso (con la distinción entre variables y señales que veremos luego). Una
vez obtenido el circuito lógico, éste es concurrente con respecto al resto de construcciones
dentro de la arquitectura. El siguiente programa por ejemplo, es un error.
library ieee;
use ieee.std_logic_1164.all;
entity and3 is port(
a,b,c: in std_logic;
y: out std_logic);
end and3;
architecture archand3 of and3
is begin
p1: process (a,b,c) begin
if (a='1' and b='1' and c='1') then y<='1'; else y<='0';
end if;
end process;
y<= a or b or c;
end archand3;
Diferencias entre variables y señales.
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".
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.
process: case .. when
Se trata de una estructura de ejecución en serie. Es parecido al with ... select, pero más
general, puesto que en cada caso no sólo se puede hacer una asignación, sino que después de
cada "=>" puede escribirse una sentencia o un conjunto de ellas.
library ieee;
use ieee.std_logic_1164.all;
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.
begin
variable i: integer range 1 to 4;-- Al ser un indice de un loop -- esta definicion es optativa
begin
for i in dum'range loop
dum(1)<=a(1 downto 0); dum(2)<=a(3 downto 2); dum(3)<=a(5 downto 4); dum(4)<=a(7
downto 6);
end archcuatromux2to1;
Este programa es poco práctico, sólo se escribe para mostrar, además de los bucles, el uso de
los arrays. Hemos definido un array de vectores de std_logic. Es en definitiva una matriz:
type lineas is array (1 to 4) of std_logic_vector(1 downto 0);
Hay que hacer notar también el uso del for loop, así como del atributo 'range. Este atributo
da el rango de un objeto. En nuestro caso es de 1 a 4, puesto que hemos definido el tipo
lineas como un array (1 to 4).
La señal dum está definida como de tipo lineas. Entonces dum(1) es el primer elemento del
array. Como cada elemento es un std_logic_vector, dum(1) es un std_logic_vector, y
dum(1)(1) es su primer elemento. Una definición alternativa de un array con el mismo
número de elementos es:
type lineas is array (1 to 4, 1 downto 0) of std_logic;
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:
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
Descripción estructural.
Este tipo de descripción es cercano a una net-list de otras herramientas CAD. La descripción
estructural es especialmente interesante para la incorporación de elementos de biblioteca al
diseño y la realización de diseños jerárquicos a partir de componentes. Consideremos dos
entidades, and_de_3 y or_de_n:
library ieee;
use ieee.std_logic_1164.all; entity and_de_3 is port(
a,b,c: in std_logic;
y: out std_logic);
end and_de_3;
architecture archand_de_3 of and_de_3 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.
-- 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:
u1: and_de_3 port map (r,s,t,x);
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.
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.
library ieee;
use ieee.std_logic_1164.all;
entity celdasumadora is port(
a,b,cin:in std_logic;
s, cout: out std_logic); end
celdasumadora;
-- Programa principal
library ieee;
use ieee.std_logic_1164.all;
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.
entity full_add is port(
a,b,carry_in: in bit; sum,
carry_out: out bit);
end entity;
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;
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
("package s"). 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.
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:
Las librerías existentes en MaxPlus II son las siguientes, según la ayuda on-line del propio
programa:
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
clklock pll ntsc
Ejemplos de VHDL
Daremos a continuación unos ejemplos de VHDL que cubran aspectos no tratados
anteriormente, pero que se encuentran a menudo en la síntesis de circuitos.
Flip-flop D síncrono
library ieee;
use ieee.std_logic_1164.all;
entity biestD is port( clk,d: in
std_logic; q: out std_logic);
end entity;
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;
Contadores
Contador ascendente-descendente con carga paralelo síncrona y reset asíncrono:
library ieee;
use ieee.std_logic_1164.all; use
ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity contador is port( clk,load,up,reset: in
std_logic;
d: in std_logic_vector(7 downto 0);
q: buffer std_logic_vector(7 downto 0));
end entity;
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.
“AÑO DE LA PROMOCIÓN DE LA INDUSTRIA Y DEL CAMBIO CLIMÁTICO”
NOMBRE :
SANTOS FERNANDEZ MILTON DANIEL
CURSO :
CIRCUITOS DIGITALES I
PROFESOR :
ING. JUAN MANUEL JACINTO SANDOVAL
CICLO :
V
AÑO :
2014