Sie sind auf Seite 1von 24

VHDL: MODELADO Y SINTESIS DE CIRCUITOS DIGITALES

I INTRODUCCION
El presente texto tiene por objeto presentar los principios del diseño de sistemas digitales
de baja y mediana complejidad por medio del lenguaje de diseño VHDL. Está dirigido a
estudiantes y profesionistas de Ingeniería Eléctrica, Electrónica y de Computación, que
cuenten con conocimientos básicos de circuitos lógicos, y de programación con algún
lenguaje de alto nivel (Java, C, Fortran, etc.).

Se pretende que el material permita al estudiante comprender y utilizar el lenguaje como


una herramienta, de una manera rápida y eficiente. A diferencia de otros textos sobre el
mismo tema, no se presenta una descripción completa del lenguaje, pero se enfatiza la
relación entre el código y el circuito sintetizado. Además, se presenta con claridad, desde
un principio, la naturaleza concurrente de las instrucciones de VHDL, que la distingue de
otros lenguajes de programación.

La letra V de VHDL significa en inglés “Very High Speed Integrated Circuit”; HDL
significa “Hardware Description Language”. El lenguaje se diseñó justamente para
simplificar el diseño de los complejos circuitos que emplean las computadoras, los
sistemas de comunicación, los aparatos domésticos inteligentes, etc. Pero puede utilizarse
también como un vehículo de estudio de los circuitos lógicos, pues permite modelar y
simular el comportamiento de cualquier sistema digital. Es claro así que VHDL se
convierte en una herramienta poderosa para apoyar cursos de diseño lógico y arquitectura
de computadoras; permite implementar circuitos en un dispositivo programable en un corto
período de tiempo, sin tener que alambrar múltiples circuitos integrados en una tablilla.

Los ejemplos presentados se han programado utilizando el software Quartus II V7.2 de


Altera; sus correspondientes circuitos se han implementado en una tablilla DE2 del mismo
fabricante, que incluye un FPGA Cyclone

II. ASPECTOS GENERALES

Antes de proseguir, presentamos algunos aspectos generales e invariantes del lenguaje:

1. Es indistinto usar letras mayúsculas o minúsculas. Por ejemplo, las 2 instrucciones


siguientes son semejantes:

Dsal<=A and b;
dSAl<= a And B;

dsAl<= a AND B;

1
2. Los espacios o tabulaciones no alteran el significado. Por ejemplo, la instrucción
siguiente es semejante a las 2 anteriores:

D sa L< = a And B;

3. Es importante comentar el código. Usar 2 guiones sucesivos para indicar que el texto
siguiente es un comentario. Ejemplo

S<= A xor B xor Cin; -- suma de A y B

4. Toda instrucción termina con el caracter “;” (punto y coma).

5. Identificadores. Son los nombres que asignamos a señales, variables, funciones, etc.
Siguen las reglas siguientes:
a) No hay límite para el número de caracteres (aunque no se recomiendan identificadores
demasiado largos).
b) Los caracteres son letras (A-Z y a-z), dígitos (0-9) y el guión bajo “_”,
c) Empiezan con un caracter alfabético, y terminan con un caracter alfabético o numérico.
d) No se permiten 2 guiones bajos consecutivos.
e) No deben coincidir con palabras reservadas del lenguaje (ver Apéndice A).

6. Instrucciones if, loop, case. Como se verá más adelante, VHDL incluye estas
instrucciones, y deben respetarse las reglas siguientes:
a) Toda instrucción if debe terminarse con end if.
b) Cada instrucción if incluye una componente then.
c) Cada instrucción case termina con end case.
d) Cada instrucción loop termina con end loop

7. El tipo de una señal debe ser igual al de cualquier expresión que se le asigne.

III. ESTRUCTURA DE UN PROGRAMA VHDL


Un programa VHDL consta fundamentalmente de 2 partes, o unidades declarativas. La
primera representa a cualquier circuito como una “caja negra”, y se denomina como entity
(entidad), que posee ciertos puertos de entrada y salida. Se declara como
entity nombre_de_la_entidad is
[cláusula de puertos]
end nombre_de_la_entidad;

La cláusula de puertos, a su vez , es la lista de puertos de la entidad, que representan


señales o interfaces con el mundo externo, separados por punto y coma, de la forma
port ( nombre_de_puerto: modo tipo_de_dato;
nombre_de_puerto: modo tipo_de_dato;
………………… ….
nombre_de_puerto: modo tipo_de_dato);

El modo indica si la señal es de entrada (in), de salida (out) o que actúa como entrada y
salida (inout y buffer). Existen diversos tipos de datos, y utilizaremos por ahora los más
2
comunes: bit y bit_vector. En el caso en el que varios puertos posean el mismo modo y
tipo, es posible juntar sus nombres en una sola línea. Cada puerto posee un nombre único.
Por ejemplo, la entidad de un circuito sumador que posee 2 entradas A0 y B0, un acarreo
de entrada C0, una salida SUM0 y otra salida C1, puede declarase como

entity sumador is
port(A0, B0, C0: in bit;
SUM0, C1: out bit);
end sumador;

entity sumador is

port(A0, B0, C0: in std_logic;

end sumador;
La segunda parte de un programa VHDL se denomina architecture, y describe la
estructura interna de la entidad; visto desde otro punto de vista, describe lo que hace el
circuito. La descripción no suele ser única; pueden existir varias arquitecturas que
describen a la misma entidad; además, existen varios estilos para la descripción. Estos se
denominan como: flujo de datos, funcional, estructural, o híbrido si se emplean varios de
estos estilos.

Como ejemplo de una declaración de arquitectura, describimos ahora la arquitectura de tipo


flujo de datos (por ecuaciones booleanas) del circuito correspondiente a la entidad sumador

Architecture uno of sumador is


begin
C1<= (A0 and B0) or (A0 and C0) or (B0 and C0);
SUM0<= A0 xor B0 xor C0;
end uno;

Se observa que la declaración posee un nombre arbitrario (en este caso “uno”); la palabra
begin se antepone a las instrucciones que describen la lógica del sumador, y la declaración
finaliza con end uno.
3
El operador “<= “ utilizado en las 2 instrucciones es un operador de asignación para
señales. Los operadores xor, and, or son los operadores lógicos conocidos.

IV INSTRUCCIONES CONCURRENTES
VHDL difiere significativamente de otros lenguajes de programación, por la razón de que
sus instrucciones se ejecutan concurrentemente. En el ejemplo del sumador anterior, no se
evalúa primero C1 y después SUM0, sino que se evalúan simultáneamente, cada vez que
cambia cualquiera de las señales A0, B0, o C0. Esto es necesario, puesto que dichas señales
producen cambios simultáneos en el circuito físico del sumador, y el lenguaje que modela
el circuito debe reflejar dicha situación. En la Figura 1 se muestra el circuito, tal como se
sintetiza por el programa; en la Figura 2 se muestra su simulación, con diversos valores de
las entradas. Nótese como varían las salidas simultáneamente con cada cambio de las
entradas A0, B0, C0, establecidas cada intervalo de 10 ns.

Figura 1. Circuito sumador sintetizado por el programa

Figura 2. Simulación del sumador

a) Un primer tipo de instrucción concurrente es el que ya se discutió: La asignación


simple, que tiene la forma

señal_destino <= expresión

La expresión incluye otras señales y operadores. Podemos pensar que la señal destino es
uno de los puertos de salida, o la salida de un dispositivo interno, como una compuerta OR,
por ejemplo. Es obvio que, a diferencia de otros lenguajes de programación, no es posible

4
incluir 2 o más instrucciones de asignación simple al mismo destino; ello equivaldría a
un corto circuito. (Salvo en buses y el uso de la función RESOLVED, que no se trata aquí).

Ejemplo 1: Escribir el código (identidad y arquitectura) para implementar la función


expresada en la siguiente tabla de verdad:
ABC F
0 0 0 0
0 0 1 1
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 0
1 1 0 1
1 1 1 1

Solución: De la tabla, tenemos que F=A´∙B´∙C+A´∙B∙C+A∙B∙C´+A∙B∙C=A´∙C+A∙B. Luego

entity ejemplo1 is
port(A, B, C: in std_logic;
F: out std_logic);
end ejemplo1;
architecture uno of ejemplo1 is
begin
F<= (not A and C) or (A and B);
end uno;

b) Un Segundo tipo de instrucción concurrente es la asignación condicional, de la forma

señal_destino<= expresión1 when condición1 [ else expresión2 when condición2 else…


expresiónn ];

En esta forma, las cláusulas incluidas en el paréntesis rectangular […] son optativas. El
resultado de la instrucción es que se asigna al destino la expresión j cuya condición j es la
verdadera. Usaremos esta instrucción en el ejemplo siguiente, para ilustrar otro estilo de
flujo de datos.

Ejemplo 2: Escribir el código (identidad y arquitectura) para implementar la función


expresada en la tabla del ejemplo1.

Solución: Usamos la misma expresión y entidad, pero determinamos los casos en las que
es cierta o falsa:

Architecture dos of ejemplo1 is


5
begin
F<= ‘1’ when (( A=’0’ and C=’1’) or (A=’1’ and B=’1’)) else ‘0’;
end dos;

Notar que los valores lógicos se escriben entre comillas simples. Notar, asimismo, que
hemos utilizado operadores relacionales (=).

Ilustraremos ahora el uso del tipo bit_vector, que, a diferencia de bit, se refiere a un bus de
2 o más bits, para escribir el código de un multiplexor 4 a 1. En este circuito, un bus SEL
de control selecciona cual de las 4 entradas I aparece en la salida Y.

La señal de control SEL incluye 2 señales, SEL(1) y SEL(0). El bus de entrada I incluye las
señales I(3), I(2), I(1) e I(0). El símbolo de un MUX se muestra en la Figura 1, junto con la
caja negra de la entidad. El bus se resalta con mayor grosor, y se indica el número de
señales o alambres que contiene.

Figura 3. Símbolo de un MUX 4 a 1 y de su entidad

entity mux4_1 is
port( I: in bit_vector(3 downto 0);
SEL: in bit_vector(1 downto 0);
Y: out bit);
end mux4_1;

architecture uno of mux4_1 is


begin
Y<= I(0) when (SEL=”00”) else
I(1) when (SEL=”01”) else
I(2) when (SEL=”10”) else
I(3);
end uno;

6
Nótese que los valores de un vector se encierran entre comillas dobles (“00”, “01”, etc.) a
diferencia de señales std_logic. Un vector puede declararse también en otro orden, como
SEL: in bit_vector(0 to 1);

c) Un tercer tipo de señal concurrente contiene la cláusula with … select. Tiene la forma

with expresión_de_selección select


señal_destino<= expresión1 when selección1, [expresión2 when selección2, … when
selecciónn];

Ilustramos el uso de ésta instrucción para expresar otra arquitectura de flujo de datos del
mux4_1 anterior:

architecture dos of mux4_1 is


begin
with SEL select
Y<= I(0) when “00”, I(1) when “01,
I(2) when “10”, I(3) when others;
end dos;

Hemos introducido en el código anterior el valor de selección “others” que equivale al


conjunto de valores lógicos no cubiertos por los casos anteriores.

d) Un cuarto tipo de instrucción concurrente, que se refiere al estilo funcional, utiliza un


proceso. A diferencia del estilo de flujo de datos, no refleja directamente el modo en el cual
se sintetiza el circuito; modela más bien el comportamiento del circuito frente a sus
entradas. La sintaxis del proceso es la siguiente:

[etiqueta]: process (lista de sensitividad)


begin
instrucción secuencial;
……………………..
instrucción secuencial;
end process [etiqueta];

El uso de una etiqueta para referenciar al proceso es optativo. La lista de sensitividad


incluye las señales que, al cambiar, provocan la evaluación de todas las instrucciones
secuenciales del proceso.

Las instrucciones incluidas dentro del cuerpo del proceso se ejecutan en forma
secuencial, en orden, como en otros lenguajes de programación, cada vez que cambia
alguna de las señales de la lista de sensitividad. Sin embargo, el proceso se ejecuta
concurrentemente con otras instrucciones concurrentes que se encuentren dentro del bloque
de la arquitectura.

Las instrucciones secuenciales que estudiaremos por el momento son: la asignación simple
(que ya conocemos), la instrucción condicional if…then…else, y la instrucción case.
7
La instrucción condicional tiene la forma

If condición1 then conjunto de instrucciones [else conjunto de instrucciones] end if;

En el caso en que no existe la cláusula else, como por ejemplo

if en=’1’ then q<=I;

la señal q toma el valor de I si en=’1’, pero permanece sin cambio en caso en el que
en=’0’; equivale a indicar else q<=q. Esto da lugar a un circuito secuencial denominado
cerrojo (“latch”), que funciona como una memoria implícita. Ver la Figura 4.

Figura 4. Cerrojo resultante de la instrucción if en=’1’ then q<=I;

Cuando sí existe la cláusula else, en el conjunto de instrucciones siguiente se puede escribir


otra instrucción condicional. Para ilustrar esta situación, se muestra a continuación la
arquitectura del mux4_1 mediante un proceso:

Architecture tres of mux4_1 is


begin
process (I,SEL)
begin
if SEL=”00” then Y<=I(0); else
if SEL=”01” then Y<=I(1); else
if SEL=”10” then Y=I(2); else Y=I(3);
end if; end if; end if;
end process;
end tres;

La arquitectura del mux4_1 puede escribirse aún con mayor claridad utilizando la
instrucción case. Esta tiene la forma

case expresión is
when casos => instrucciones secuenciales;
8
when casos => instrucciones secuenciales;
………………………………………….
when others => instrucciones secuenciales;
end case;

Consideremos que el MUX posee una entrada de habilitación en, tal que si en=’0’, la salida
Y es cero. De lo contrario, opera normalmente. Su modelo VHDL resulta entonces:
entity mux4_1 is
port( I: in std_logic_vector(3 downto 0);
SEL: in std_logic_vector(1 downto 0);
en: in std_logic;
Y: out std_logic);
end mux4_1;

architecture cuatro of mux4_1 is


begin
process(en, I, SEL)
begin
if en=’1’ then
case SEL is
when “00” => Y<=I(0);
when “01” => Y<=I(1);
when “10” => Y<=I(2);
when others => Y<= I(3);
end case;
else Y<=’0’;
end if;
end process;
end cuatro;

En general, un proceso se utiliza para la simulación y síntesis de circuitos secuenciales, que


se estudian más adelante, y no se estila para circuitos combinacionales.

V. ESTILO ESTRUCTURAL. COMPONENTES


Hemos visto ya los estilos de flujo de datos y funcional. El estilo estructural hace uso de
circuitos ya compilados (componentes) para estructurar un nuevo circuito. Un ejemplo
clásico es el de un sumador de 2 bits, formado por 2 sumadores de 1 bit como el de la
Figura 1, como se muestra en la Figura 5:

Figura 5. Circuito sumador de 2 bits


9
En el circuito, el bit de acarreo C1 de salida del primer sumador equivale al bit de acarreo
de entrada del segundo. C1 es una señal intermedia, pues no forma parte de la entidad, y
debe declararse, por tanto en la arquitectura. Cada sumador es una componente, que se
declara también en la arquitectura, y se dice que se “instancia” al utilizarla. La componente
debe estar previamente compilada, y añadida al archivo del proyecto que la utiliza.
Mostramos el código de la entidad y la arquitectura a continuación:

entity sumador2bits is
Port(A0,B0,C0,A1,B1: in bit;
S0,S1,C2: out bit);
end sumador2bits;

architecture struct of sumador2bits is


signal C1,cero: std_logic; -- señal intermedia
component sumador is -- componente ya compilada
port(A0,B0,C0: in std_logic;
SUM0,C1: out std_logic);
end component;
begin
cero<=’0’; -- valor para asignar a C0.
paso0: sumador port map ( A0, B0, cero, S0, C1); -- “instanciación” del primer sumador
paso1: sumador port map ( A1, B1, C1, S1, C2); -- “instanciación” del segundo sumador
end struct;

Cuando se usa una componente, se dice que se “instancia” dicha componente. La


“instanciación” se ha efectuado en el programa sustituyendo las señales “reales” por las
“formales” en el orden de la declaración de la componente. Otro método para “mapear” las
señales se muestra a continuación; en éste no importa el orden, sino la correspondencia.
paso0: sumador port map(A0=>A0, B0=>B0, C0=> cero, SUM=>C0, C1=>C1);

VI. PAQUETES, OPERADORES Y TIPOS ARITMETICOS. OTROS TIPOS

Una vez que hemos estudiado los distintos estilos de arquitecturas, y que hemos utilizado
los tipos bit y bit_vector, así como operadores lógicos y relacionales, enfocamos nuestra
atención a otros tipos de señales y operadores.

Además de los tipos bit y bit_vector, VHDL provee también los tipos integer, natural,
positive, boolean, string, character, real, time. Además, pueden definirse tipos
adicionales (tipos enumerados); estos se definen en la arquitectura, junto con señales de
dicho tipo Por ejemplo

10
type estado is (S1,S2,S3);
signal edo: estado;

type dia_habil is (lunes, martes, miércoles, jueves, viernes);


signal cita: dia_habil;

Los operadores soportados por VHDL, en orden inverso de precedencia, se muestran en la


tabla de la Figura 6.
Logico And Or Nand Nor Xor Xnor Not
Relacional = /= < <= > >=
Desplazamiento sll srl sla sra rol ror
Suma + -
Unario + -
Multiplicación * / Mod rem
Y División
Otros ** abs &
Figura 6. Operadores VHDL
Los operadores que requieren una explicación son los siguientes: los de desplazamiento son
de 3 tipos: lógicos, aritméticos y rotaciones. Funcionan como sigue:

En los lógicos, se introduce ‘0’ por la derecha o la izquierda al vector. En los aritméticos
también, salvo que no se altera el bit de signo, y éste se replica al desplazar a la derecha.
Las rotaciones introducen el bit de un extremo al otro (Figura 7).

Figura 7. Desplazamientos

El operador & indica concatenación. Por ejemplo, si a y b son vectores de 5 bits, y c es un


vector de 10 bits, se puede escribir c<=a&b. En este caso el vector a incluye los 5 bits más
significativos de c.

Con el operador & es posible también efectuar desplazamientos. Por ejemplo, si a es un


vector declarado como std_logic_vector(15 downto 0), la instrucción

y<=”00”&a(15 downto 2)
produce un desplazamiento lógico de 2 bits a la derecha.

Los operadores de tipo aritmético (grupos 4,5 6, y ABS, ** del 7) se utilizan de forma
normal con tipos INTEGER Y REAL. Para demostrar el uso de operadores para tipos BIT
y BOOLEAN, supongamos que A=”001”, B=”110”, C=”01110”, D=”11001”; las
instrucciones siguientes resultan entonces como lo indican los comentarios.

11
E<=’1’&A(1 DOWNTO 0); -- E toma el valor “101”
E<=A AND NOT B; --E toma el valor “001”
E<=B SRL 1; -- E toma el valor “011”
E<= A SLL 2; -- E toma el valor “100”
F<=C XOR D ROR 2; -- F toma el valor “10000” ( D ROR 2 =”11110”)
C AND NOT D=”00110”-- Esta expresión es cierta, luego posee el valor TRUE

Es claro que E debe estar definido como un vector de 3 bits, y F como un vector de 5 bits.

Bibliotecas y paquetes para VHDL. Las bibliotecas y paquetes se utilizan para extender la
funcionalidad de VHDL, definiendo tipos, funciones, componentes y operadores
“sobrecargados”. En VHDL estándar no todos los operadores se aplican a todos los tipos
(los operadores aritméticos, por ejemplo, no se aplican a tipos lógicos como BIT o
BIT_VECTOR). Para extender su rango de aplicación, se crean funciones “sobrecargadas”.
El IEEE (Instituto de Ingenieros Eléctricos y Electrónicos) ha desarrollado bibliotecas y
paquetes estándar, tales como el IEEE.std_logic_1164 y el IEEE.numeric_bit o
IEEE.numeric_std.

Paquete STD_LOGIC_1164 de la biblioteca IEEE. El lenguaje VHDL nativo utiliza


lógica de 2 valores (BIT y BIT_VECTOR), pero es también posible utilizar señales
STD_LOGIC, que incluye 9 valores posibles; entre otros, el valor ‘Z’ de alta impedancia
correspondiente a buffers de 3 estados. Esta es una adición del IEEE para mejorar el
lenguaje; su utilización requiere de la declaración inicial de una biblioteca IEEE y un
paquete STD_LOGIC_1164, que antecede a la de entidad:

LIBRARY IEEE;
USE IEEE_STD_LOGIC_1164.ALL;

Paquetes aritméticos. Los paquetes estándar del IEEE son: IEEE.numeric_bit e


IEEE.numeric_std. Estos utilizan vectores tipo signed o unsigned para representar
números binarios con y sin signo en vez de bit_vector o std_logic_vector. Una señal del
tipo signed o unsigned es de hecho un arreglo de bits, donde el más significativo es un
signo en el caso signed (complemento a 2).

Los paquetes incluyen operadores “sobrecargados” del tipo aritmético, relacional, lógico y
de desplazamiento. Los pares de operandos para operaciones aritméticas y relacionales
pueden ser: unsigned con unsigned, unsigned con natural, signed con signed, signed con
integer. Al utilizar operandos de longitud distinta, se añaden ceros a la izquierda del más
corto para igualar sus longitudes; el resultado será de la misma longitud (se descarta el
acarreo final si existe). Para operaciones lógicas (excepto NOT), ambos operandos deben
ser del mismo tipo: signed o unsigned.

Para utilizar operadores sobrecargados en estos paquetes, procedemos a declarar los


vectores como signed o unsigned (según sea el caso), o bien aplicamos las funciones de
conversión que se enloistan más adelante. Otra alternativa es la de utilizar los paquetes
std_logic_signed o std_logic_unsigned, de Synoptics, que utilizan operadores
“sobrecargados” para tipos std_logic_vector (no es necesario entonces declarar vectores

12
signed o unsigned). Otro paquete de Synoptics es el std_logic_arith, que no usaremos en
este texto. Ninguno de los paquetes de Synoptics define operaciones lógicas con vectores,
por lo que se requiere usar también el paquete std_logic_1164 para las operaciones lógicas.

Presentamos a continuación el código de un sumador de 8 bits, que hace uso del paquete
STD_LOGIC_SIGNED. El sumador instanciado incluye acarreo anticipado.

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_SIGNED.ALL,

ENTITY sum8 IS
PORT(x,y: IN STD_LOGIC_VECTOR(7 DOWNTO 0);
s: OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
cout, v: OUT STD_LOGIC);
END sum8;

ARCHITECTURE uno OF sum8 IS


SIGNAL sum: STD_LOGIC_VECTOR (8 DOWNTO 0);
BEGIN
sum<=('0'&x) + ('0'&y); -- resultado de 9 bits
s<=sum(7 DOWNTO 0);
cout<=sum(8);
v<= sum(8) XOR x(7) XOR y(7) XOR sum(7); -- desbordamiento
END uno;
Figura 8. Código de VHDL para un sumador de 8 bits con operadores aritméticos

Hemos definido en el código una señal sum de 9 bits; incluye la suma s de 8 bits, y el
acarreo final cout. Pero si sum se ha definido como de 9 bits, se requiere que, al menos uno
de los operandos sea de 9 bits. Por ello, se ha anexado un bit ‘0’ a A y a B, por medio del
operador de concatenación &, formando los operandos (‘0’&A) y (‘0’&B). Para
determinar si existe o no desbordamiento v, se requiere el valor de C7 (esto es, Cn). El lector
deberá comprobar que este valor resulta de la expresión xn yn sum(n).

En la Figura 9. se muestra la simulación, con valores decimales de x,y,s.

Figura 9.. Simulación del sumador de la Figura 8

De requerirse operaciones numéricas, se recomienda utilizar los paquetes ieee.numeric_std


junto con el ieee.std_logic_unsigned o ieee.std_logic_signed. El paquete ieee.numeric
incluye un conjunto de operadores como funciones, muy extenso. Soporta, en particular,
las funciones shift_left, shift_right, rotate_left, rotate_right, aplicadas a vectores signed

13
o unsigned, con un argumento natural adicional, que indica el número de desplazamientos
a efectuar. Por ejemplo, y<=shift_right(a, 2); donde a se definió como unsigned (0 to 4);
el resultado y debe ser del mismo tipo que a.

El paquete numeric incluye también funciones de conversión entre tipos (recordar que el
lado derecho y el izquierdo de una asignación simple deben ser del mismo tipo), o soporta
conversiones automáticas. Por ejemplo, elementos de signed, unsigned y std_logic_vector
son todos del tipo std_logic. Los paquetes permiten las conversiones siguientes:

UNSIGNED(B) – permite que el compilador trate un bit_vector (std_logic_vector) como


vector unsigned.
BIT_VECTOR(U) --permite que el compilador trate a un vector unsigned como bit_vector
STD_LOGIC_VECTOR(U) -- permite que el compilador trate a un vector unsigned como
std_logic_vector

La conversión entre signed o unsigned a valor integer usa la función to_integer. Por
ejemplo, m <= to_integer( E_u); Esta es muy útil, por ejemplo, para obtener el valor entero
de la dirección de un decodificador o memoria, para poder utilizarlo como índice. Si
partimos de un vector A_slv, requerimos una primera conversión a unsigned, y una
segunda a integer:

m <= to_integer( unsigned ( A_slv));

Es posible también convertir un entero a signed o unsigned, mediante las funciones


to_unsigned o to_signed. Las funciones incluyen un argumento entero que indica el
número de bits. Por ejemplo E_u <= to_unsigned ( m,8). En resumen, tenemos las
funciones

TO_INTEGER(U): -- convierte vector unsigned a integer


TO_UNSIGNED(I,N): -- convierte un entero a un vector unsigned de longitud N

Mencionamos, por otro lado, que las declaraciones del tipo integer, natural o positive se
efectúan normalmente indicando su rango. Por ejemplo q: integer range 0 to 7.

Por último, el tipo std_logic_vector es en realidad un caso particular de un arreglo: array,


con elementos de tipo std_logic. En general es posible declarar un banco de 8 registros de
16 bits, como sigue:

type regis is array (0 to 7) of std_logic_vector(15 downto 0);


signal banco_reg: regis;

VII. CIRCUITOS SECUENCIALES SINCRONOS I


Para escribir código VHDL que represente circuitos secuenciales síncronos como flip-
flops, registros, contadores, etc., se requiere especificar la ocurrencia del flanco de un reloj.
VHDL nos permite hacerlo, con una cláusula condicional dentro de un proceso, como
14
if clk=’1’ and clk’event then…. Esta indica el evento en el cual el flanco creciente del
reloj ocurre. De igual manera, para el flanco decreciente escribimos clk=‘0’ en vez de ‘1’.
Otra cláusula que puede utilizarse es wait until clk=’1’ and clk’event, pero no se establece
en el proceso en este caso la lista de sensitividad.

Ejemplo 4: Escribir el código de un registro de 8 bits con entradas de reloj clk, de puesta a
ceros síncrona rst, de habilitación en, entradas I y salidas Q.
Solución:

library ieee;
use ieee.std_logic_1164.all;

entity reg8 is
port( clk, rst, en: in std_logic;
I: in std_logic_vector(7 downto 0);
Q: out std_logic_vector(7 downto 0));
end reg8;

architecture uno of reg8 is


begin
process(clk, rst, I)
begin
if clk='1'and clk'event then
if rst='1' then Q<="00000000"; else
if en='1' then Q<=I;
end if; end if; end if;
end process;
end uno;

Hemos utilizado en el código el valor la instrucción Q<=”00000000”. Una manera práctica


de asignar ceros a un vector consiste en utilizar others, como sigue:

Q<= (others => ‘0’);

Ejemplo 5 : Escribir el código para modelar un contador mod 16 con carga en paralelo, con
señales de control ld y cnt, y reset asíncrono activo en bajo rstn:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity pc16 is
port( clk, ld, cnt, rstn: std_logic;
I: in std_logic_vector(3 downto 0);
q: out std_logic_vector(3 downto 0));
end pc16;
15
architecture uno of pc16 is
signal y: std_logic_vector(3 downto 0);
begin
process(clk,rstn,ld,cnt,I)
begin
if rstn='0' then y<= (others => '0');
else if clk='1' and clk'event then
if ld='1' then y<= I; else
if cnt='1' then y<=y+1;
end if; end if; end if; end if;
end process;
q<=y;
end uno;

La simulación del circuito se muestra en la Figura 10:

Figura 10. Simulación del contador con carga en paralelo

Ejemplo 6: Escribir código para desplazar 4 bits a la izquierda un registro reg a otro q, con
el control de una señal shft. El bit que se introduce a la derecha es w.
Solución: El programa es como sigue. Su simulación se muestra en la Figura 11.

library ieee;
use ieee.std_logic_1164.all;
entity despl4 is
port( clk,shft,w: in std_logic;
reg: in std_logic_vector(3 downto 0);
q: out std_logic_vector(3 downto 0));
end despl4;
architecture uno of despl4 is
begin
process (clk,w,reg)
begin
if clk='1' and clk'event then
if shft='1' then

16
q(3)<=reg(2); q(2)<=reg(1); q(1)<=reg(0); q(0)<=w;
end if; end if;
end process;
end uno;

Figura 11

VIII. CLAUSULA LOOP. VARIABLES Y CONSTANTES. CLAUSULA GENERIC

Cláusula loop. VHDL posee, como otros lenguajes de programación, una instrucción de
repetición, utilizando la cláusula loop dentro de un proceso; se utilizan varias formas:
for índice in rango loop while condición loop loop
[instrucciones secuenciales]; [instrucciones secuenciales]; exit when condición;
end loop; end loop; [instrucciones secuenciales];
end loop;

Esta cláusula nos permite escribir en forma compacta el código de un registro de


corrimiento grande. Por ejemplo, el desplazamiento a la derecha de un registro de 32 bits
puede efectuarse como sigue. La variable i no requiere declararse dentro del proceso.

for i in 0 to 30 loop
q(i)<=q(i+1);
end loop;
q(31)<=w;

Variables y constantes. VHDL tiene 3 tipos de objetos de datos: Señales, variables y


constantes. Las variables y constantes equivalen a las utilizadas en otros lenguajes de
programación, y se utilizan sólo en procesos. En particular, una variable almacena valores
de diversos tipos, pero no representa un cable o conexión en un circuito, a diferencia de una
señal. La asignación de valores a una variable o constante utiliza el operador “ := “. Este
operador sirve también para asignar valores iniciales a una señal, al declararse.
Presentamos a continuación una tabla con diversos ejemplos de declaraciones y valores
iniciales.

Objeto de datos Ejemplos de declaraciones


Signal Signal indice : integer range 0 to 128:= 0;
Signal bcd_val: unsigned:= “0011”;
Variable Variable k: integer:=0;
Variable dato_logico: Boolean:= false;
Constant Constant valor_max: integer:= 127;
17
Constant sel_critica: std_logic_vector(3 downto 0):=”0110”;

Ilustraremos estos objetos de datos con un programa para normalizar la mantisa de 23 bits
de un número en punto flotante con el formato IEEE 754.

Ejemplo 7: Escribir un programa shftloop para determinar el número n de ceros a la


izquierda del primer ‘1’ de un vector de 23 bits, a la vez que se desplaza el vector n
posiciones a la izquierda. .

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;

entity shiftloop is
port(x: in std_logic_vector(22 downto 0);
y: out std_logic_vector(22 downto 0);
n: out integer range 0 to 22);
end shiftloop;

architecture uno of shiftloop is


signal temp: std_logic_vector(22 downto 0);
signal y1: unsigned (22 downto 0);
signal n1: integer range 0 to 22;
begin
process (x,temp)
variable z: integer :=22;
begin
temp<=x;
loop
exit when (z=0 or temp(z)='1');
z:=z-1;
end loop;
n1<=22-z;
end process;
y1<=shift_left(unsigned (temp),n1);
y<=std_logic_vector(y1);n<=n1;
end uno;

Cláusula GENERIC. Esta cláusula permite parametrizar algún dato, como, por ejemplo, el
tamaño de un arreglo. Ilustramos su uso con una variante del programa del ejemplo 6 que
desplaza a la izquierda un vector de n elementos:

library ieee;
use ieee.std_logic_1164.all;

18
entity despln is
GENERIC (n: integer:=32);
port( clk,shft,w: in std_logic;
reg: in std_logic_vector(n-1 downto 0);
q: out std_logic_vector(n-1 downto 0));
end despln;

architecture uno of despln is


begin
process (clk,w,reg)
begin
if clk='1' and clk'event then
if shft='1' then
for i in n-2 downto 0 loop
q(i+1)<=q(i);
end loop;
q(0)<=w;
end if; end if;
end process;
end uno;

Otras diferencias entre señales y variables. Es sumamente importante entender el concepto


de concurrencia para señales, en contraste con la asignación inmediata de valores a
variables. En el código que se muestra a continuación, las señales s1, s2 , s3 y s4 cambian
simultáneamente cuando y=’1’; sus expresiones toman los valores originales antes del
cambio, de manera que s1=2, s2=1+3=4, s3=2, y s4=1+2+3=6.

architecture uno of ej is
signal y: std_logic;
signal s1: integer :=1; signal s2: integer :=2;
signal s3: integer :=3; signal y: integer;
begin
process (y1)
begin
if y=’1’ then
s1<=s2; s2<=s1+s3; s3<=s2; s4<=s1+s2+s3;
end if;
end process;
end uno;
Figura 12. Código con señales

En el código alterno de la Figura 13, en cambio, trabajamos con variables dentro del
proceso; las instrucciones secuenciales cambian de inmediato. De manera que v1=2,
v2=2+3=5, v3=5, s4=2+5+5=12.

architecture dos of ej is
19
signal y: std_logic; signal s4: integer;
begin
process (y1)
variable v1: integer :=1; vartiable v2: integer :=2;
svariable v3: integer :=3;
begin
if y=1 then
v1:=v2; v2:=v1+v3; v3:=v2; v4<=v1+v2+v3;
end if;
end process;
end dos;
Figura 13. Código con variables.

VII. CIRCUITOS SECUENCIALES SINCRONOS II

Tratamos ahora el modelado de autómatas finitos, o máquinas algorítmicas de estados


(llamadas FSM o ASM). Un diagrama a bloques de una FSM se muestra en la Figura 14.

Figura 14. Modelo de Autómata Finito

En la Figura, DES significa Decodificador de Estado Siguiente (combinacional), DS


significa Decodificador de salidas (combinacional), y REG es el registro de estados. q es el
estado presente, y Q el estado futuro. z representa las salidas. Cuando las salidas z
dependen de las entradas independientes x, como en la Figura, tenemos un autómata tipo
Mealy; en caso contrario, uno tipo Moore.

Proponemos modelar un detector de la secuencia 1011 como una máquina de Mealy; el


diagrama de estados con notaciónj tradicional x/z en los arcos se muestra en la Figura 15.

Figura 15. Diagrama de estados del reconocedor de la secuencia 1011

20
Presentamos 2 formas de modelar al diagrama. En la primera utilizamos un solo proceso,
en el cual se especifican las transiciones de estados con el frente anterior del reloj. Usamos
una señal rst asíncrona para el estado inicial S0.

library ieee;
use ieee.std_logic_1164.all;

entity rec2 is
port(rst,clk,x: in std_logic;
z: out std_logic);
end rec2;
architecture uno of rec2 is
type estado is (S0,S1,S2,S3);
signal edo: estado;
begin
process(clk, rst,x)
begin
if rst='1' then edo<= S0; else
if clk='1' and clk'event then
case edo is
when S0 => if x='1' then edo<=S1; else edo<=S0; end if;
when S1 => if x='1' then edo<=S1; else edo<=S2; end if;
when S2 => if x='1' then edo<=S3; else edo<=S0; end if;
when S3 => if x='1' then edo<=S1; else edo<=S2; end if;
end case;
end if; end if;
end process;
z<='1' when (edo=S3 and x='1') else '0'; -- instrucción de salida z
end uno;

Presentamos ahora la arquitectura de una segunda forma, en la cual utilizamos 2 procesos:


en el primero se establecen las transiciones de estados (como un bloque combinacional), y
en el segundo la asignación del estado futuro (edof) al presente (edop).

library ieee;
use ieee.std_logic_1164.all;

entity rec2 is
port(rst,clk,x: in std_logic;
z: out std_logic);
end rec2;

architecture dos of rec2 is


type estado is (S0,S1,S2,S3);
signal edo,edof: estado;
21
begin
process(x)
begin
case edo is
when S0 => if x='1' then edof<=S1; else edof<=S0; end if;
when S1 => if x='1' then edof<=S1; else edof<=S2; end if;
when S2 => if x='1' then edof<=S3; else edof<=S0; end if;
when S3 => if x='1' then edof<=S1; else edof<=S2; end if;
end case;
end process;

process(clk,rst)
begin
if rst='1' then edo<=S0; else
if clk='1' and clk'event then
edo<=edof;
end if; end if;
end process;
z<='1' when (edo=S3 and x='1') else '0'; -- instrucción de salida z
end uno;

La instrucción para la salida z es del tipo concurrente, y se ha incorporado en ambos casos


al final de la arquitectura. Esta es la forma recomendable; si se incluye dentro del proceso
en el primer caso, se generaría una señal que quedaría encendida permanentemente (a
menos que en otro estado se apagara).

En la Figura 14 se muestra la simulación del reconocedor, que incluye información sobre el


estado. El programa Quartus II nos permite observar también el diagrama de estados, como
se muestra en la Figura 15 si la síntesis se efectúa mediante un flip-flop por estado (one-
hot).

Figura 16. Reconocimiento de la secuencia 1101.

22
Figura 15. Diagrama de estado mostrado por Quartus II

La síntesis de una ASM se efectúa por default con codificación one-hot en un FPGA
cuando se usa un tipo enumerado para las variables de estado. El usuario puede utilizar sólo
log2n flip-flops con la codificación que desee utilizando un atributo al declarar el tipo,
como sigue:

type estado is (S0,S1,S2,S3);


attribute enum_encoding: string;
attribute enum_encoding of estado is “00,01,10,11);

Para terminar, mencionamos que los estados pueden declararse también como enteros
(integer range 0 to 3) en vez de un tipo enumerado. En ninguno de los 2 últimos casos se
mostrará el diagrama de estados.

APENDICE A: LISTA DE PALABRAS RESERVADAS

Abs downto library postponed srl


Access else linkage procedure subtype
Alter elsif literal process then
Alias end loop pure to
All entity map range transport
And exit mod record type
Architecture file nand register unaffected
Array for new reject units
Assert function next rem until
Attribute generate nor report use
Begin generic not return variable
Block group null rol wait
body guarded of ror when

23
Buffer if on select while
Bus impure open severity with
Case in or signal xnor
Component inertial others shared xor
Configuration inout out sla
Constant is package sll
Disconnect label port sra

24

Das könnte Ihnen auch gefallen