Beruflich Dokumente
Kultur Dokumente
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.).
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.
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
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.
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
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.
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.
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í).
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;
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.
Solución: Usamos la misma expresión y entidad, pero determinamos los casos en las que
es cierta o falsa:
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.
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;
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
Ilustramos el uso de ésta instrucción para expresar otra arquitectura de flujo de datos del
mux4_1 anterior:
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
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.
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;
entity sumador2bits is
Port(A0,B0,C0,A1,B1: in bit;
S0,S1,C2: out bit);
end sumador2bits;
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;
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
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.
LIBRARY IEEE;
USE IEEE_STD_LOGIC_1164.ALL;
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.
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;
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).
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:
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:
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.
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;
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;
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
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;
for i in 0 to 30 loop
q(i)<=q(i+1);
end loop;
q(31)<=w;
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.
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;
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 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.
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;
library ieee;
use ieee.std_logic_1164.all;
entity rec2 is
port(rst,clk,x: in std_logic;
z: out std_logic);
end rec2;
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;
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:
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.
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