Práctica 6. Diseño Lógico Digital mediante VHDL

Anuncio
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
Práctica 6. Diseño Lógico Digital mediante VHDL
6.1. Objetivos
Aprender a diseñar circuitos lógicos digitales mediante el uso de lenguajes de
descripción de hardware como VHDL.
6.2. Introducción al VHDL
El VHDL (Very high speed Hardware Description Language) es un lenguaje
orientado a la descripción o modelado de hardware, pero que hereda muchos
conceptos de los lenguajes de programación de alto nivel como el C o el
PASCAL. A pesar de esta cierta similitud con los lenguajes de programación, el
programador en VHDL debe tener muy presente que el código desarrollado
será implementado finalmente en algún tipo de dispositivo lógico programable
y que las características de VHDL buscan facilitar esa labor pero no eximen al
programador de intentar que su código sea “sintetizable”, es decir, que éste
puede ser implementado en algún dispositivo final. Por ese motivo, es una
buena idea siempre tener en mente la estructura de aquello que se desea
desarrollar a fin de que los distintos componentes estén adecuadamente
interconectados y sincronizados entre sí para un buen funcionamiento del
diseño total.
En VHDL, la interfaz del dispositivo a desarrollar con el exterior recibe el
nombre de entidad (entity) y la descripción de su funcionalidad es lo que se
denomina su arquitectura (architecture). La interfaz del dispositivo tiene como
objetivo definir qué señales son visibles desde el exterior, los denominados
puertos (ports). En la arquitectura se definen las acciones que se realizan sobre
los datos introducidos a través de los puertos de entrada a fin de obtener los
datos que serán transmitidos hacia los puertos de salida.
El VHDL incorpora el concepto de componente (component) a fin de utilizar
elementos ya definidos en descripciones estructurales de un nuevo diseño.
Asimismo, cualquier elemento básico puede definirse por lo que se denomina
un proceso (process). Un proceso puede entenderse como un conjunto de
sentencias que describen el comportamiento de un determinado elemento, de
tal forma que el código que contiene dicho proceso se ejecuta de manera
secuencial. Sin embargo, todos los procesos contenidos en una descripción
VHDL se ejecutarán de forma paralela. Así, una descripción VHDL puede
considerarse como una amalgama de procesos ejecutándose simultáneamente
de manera paralela y es aquí donde reside la mayor diferencia con los lenguajes
de programación de alto nivel.
Los procesos ejecutándose de manera paralela deben poder comunicarse entre
sí. El elemento básico de comunicación entre procesos es la señal (signal). Todo
proceso tiene una serie de señales a las que es sensible, lo que significa que cada
vez que una de estas señales vea alterado su valor, el proceso se ejecutará hasta
1/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
que encuentre una secuencia de suspensión del proceso (wait). Por ejemplo,
supongamos que queremos describir mediante VHDL el circuito lógico de la
figura. Un posible código sería el siguiente:
AND2: process
begin
c <= a and b;
wait on a,b;
end process AND2;
OR2: process
begin
e <= c or d;
wait on c,d;
end process OR2;
El primer proceso (AND2) se ejecutará siempre y cuando cambie alguna de las
dos entradas a o b a dicho proceso, realizándose en dicho caso la operación “Y
lógica” entre ambas, y llevando a continuación al proceso al modo de espera
hasta que alguna de las dos entradas vuelva a cambiar. De igual forma, el
segundo proceso (OR2) se ejecutará siempre que varíe la salida de la puerta
AND anterior o la entrada d, ejecutándose en dicho caso una operación “O
lógica” sobre las mismas.
6.2.1. Unidades básicas de diseño
La declaración de una entidad permite definir la visión externa del dispositivo
que dicha entidad representa, es decir, la interfaz con su entorno. La sintaxis
VHDL para la declaración de una entidad es la siguiente:
entity identificador is
[genéricos]
[puertos]
[declaraciones]
[begin sentencias]
end [entity] [identificador];
El identificador es el nombre que va a recibir la entidad y servirá para poder
referenciarla más tarde. Excepto la primera y última líneas de la declaración,
todas las demás son opcionales. Sin embargo, normalmente suelen definirse al
menos los puertos de comunicación con el exterior. Por ejemplo, supongamos
que tenemos un multiplexor 2 a 1, como se muestra en la figura. Una posible
declaración de una entidad que implemente un multiplexor de dos bits sería la
siguiente:
entity Mux21 is
port (a
: in bit;
b
: in bit;
ctrl : in bit;
z
: out bit);
end;
Las señales de entrada y de salida son de tipo bit, lo que quiere decir que
pueden tomar los valores lógicos ‘0’ ó ‘1’. Existe un tipo de dato, definido en la
librería std_logic_1164, que es el std_logic que, además de los valores lógicos ‘0’
2/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
y ‘1’, permite que la señal tome valores no inicializados (‘U’), desconocido (‘X’),
alta impedancia (‘Z’), etc. Para poder utilizar este tipo de datos es necesario
incluir al inicio del código VHDL, las dos sentencias siguientes:
library IEEE;
use IEEE.std_logic_1164.all;
La arquitectura define la funcionalidad de la entidad y su sintaxis es la
siguiente:
architecture identificador of identificador_entidad is
[declaraciones]
begin
[sentencias concurrentes]
end [architecture] [identificador];
Existen diferentes estilos para definir la arquitectura de una entidad. El estilo
algorítmico define la funcionalidad del dispositivo mediante un algoritmo
ejecutado secuencialmente, de forma similar a como se haría con un lenguaje de
alto nivel. Un ejemplo de estilo algorítmico para el multiplexor de dos bits sería
el siguiente:
architecture Algoritmico of Mux21 is
begin
process (a,b,ctrl)
begin
if (ctrl = ‘0’) then
z <= a;
else
z <= b;
end if;
end process;
end Algoritmico;
Otra posibilidad es la definición de la funcionalidad mediante un conjunto de
ecuaciones ejecutadas concurrentemente, que determinan un flujo que van a
seguir los datos entre módulos encargados de implementar las operaciones.
Para el caso del multiplexor anterior, un posible código mediante flujo de datos
sería el siguiente:
architecture FlujoDatos of Mux21 is
signal ctrl_n,n1,n2 : bit;
begin
ctrl_n <= not(ctrl) after 1 ns;
n1 <= ctrl_n and a after 2 ns;
n2 <= ctrl and b after 2 ns;
z <= (n1 or n2) after 2 ns;
end FlujoDatos;
Obsérvese que se han definido tres señales internas para definir la
interconexión de las distintas señales. Asimismo, se ha incluido un retardo a las
señales de salida de cada operación intermedia realizada mediante la cláusula
after. En todo caso, el uso de dicha cláusula no suele ser conveniente pues puede
dar problemas de síntesis, por lo que normalmente es preferible no incluirla en
la definición de sentencias concurrentes.
3/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
Una última posibilidad sería definir la arquitectura mediante un estilo
estructural que consista de un conjunto de componentes interconectados entre
sí. Un posible ejemplo estructural para el multiplexor de dos bits sería:
architecture Estructural of Mux21 is
signal ctrl_n,n1,n2 : bit;
component INV
port (y : in bit;
z : out bit);
end component;
component AND2
port (x,y : in bit;
z : out bit);
end component;
component OR2
port (x,y : in bit;
z : out bit);
end component;
begin
U0: INV port map (ctrl,ctrl_n);
U1: AND2 port map (ctrl_n,a,n1);
U2: AND2 port map (ctrl,b,n2);
U3: OR2 port map (n1,n2,z);
end Estructural;
Cuando se tiene que una misma entidad está descrita mediante diferentes
arquitecturas, se hará uso de la configuración para definir cuál de ellas se desea
utilizar. La sintaxis para definir una configuración es la siguiente:
configuration identificador of identificador_entidad is
for identificador_arquitectura
{ for (ref_componente {, …} | others | all) : id_componente
use entity id_entidad[(id_arquitectura); |
use configuration id_configuracion;]
end for; }
end for;
end [configuration] [identificador];
El identificador es el nombre que recibe la configuración y servirá para poder
referenciarla más tarde. En el caso de que la arquitectura sea jerárquica, habrá
que definir las entidades y arquitecturas que van a utilizarse para los
componentes de más bajo nivel o identificar la configuración a utilizar para ese
componente. Por ejemplo, la configuración de la entidad para la arquitectura de
flujo de datos, se definiría mediante el siguiente código:
configuration Mux21_cfg of Mux21 is
for FlujoDatos
end for;
end Mux21_cfg;
Para el caso de una configuración mediante un modelo jerárquico de tipo
estructural, la definición de la configuración podría tomar la siguiente forma:
configuration Mux21_cfg of Mux21 is
for Estructural
for U0 : INV use work.entity INV(Algoritmico); end for;
for all : AND2 use work.entity AND2(Algoritmico); end for;
for U3 : OR2 use work.entity OR2(Algoritmico); end for;
end for;
4/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
end Mux21_cfg;
Un elemento interesante de VHDL son los paquetes. Un paquete permite
agrupar un conjunto de declaraciones para que puedan ser usadas por varios
dispositivos sin tener que ser repetidas en la declaración de cada uno de ellos.
Normalmente en un paquete se suelen definir constantes, tipos y subtipos de
datos, subprogramas y componentes. La definición de un paquete se divide en
dos unidades de diseño diferenciadas: la declaración y el cuerpo (body). La
sintaxis VHDL para declarar un paquete es:
package identificador
[declaraciones]
end [package] [identificador];
Para el cuerpo del paquete, la sintaxis VHDL es la siguiente:
package body identificador is
[declaraciones cuerpo]
end [package body] [identificador];
Como podemos observar, la sintaxis es muy parecida tanto en la declaración del
paquete como en la definición de su cuerpo, lo único que cambia es la
naturaleza de las declaraciones en cada uno de ellos. Así, normalmente suelen
declararse las constantes y funciones en la declaración del paquete, y en su
cuerpo los valores de éstas y la estructura funcional de las funciones declaradas.
Otro aspecto importante en VHDL son las librerías, que nos permiten
almacenar diseños anteriores para utilizarlos en nuevos diseños. Para ello,
habría que declarar la librería al inicio del código VHDL e identificar el paquete
o paquetes que se desean utilizar de la misma:
library BibliotecaEjemplo;
use BibliotectaEjemplo.PaqueteEjemplo.all;
Las bibliotecas work y std son excepciones en el sentido de que siempre son
visibles, y no requieren de la sentencia library.
6.2.2. Objetos, tipos de datos y operadores
En VHDL existen cuatro clases distintas de objetos: las constantes, las variables,
las señales y los ficheros. Una constante es un objeto que mantiene siempre su
valor inicial, de modo que no puede ser modificada una vez ha sido creada. La
sintaxis para declarar una constante es la siguiente:
constant identificador {, …} : tipo [:= expresión];
El identificador dará nombre a la constante y servirá para referenciarla más
tarde, el tipo indica la naturaleza del valor que contiene y la expresión sirve
para inicializar la constante. A diferencia de las constantes, las variables pueden
cambiar su valor una vez han sido declaradas mediante las sentencias de
asignación. Una variable no tiene ninguna analogía directa en hardware, por lo
5/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
que normalmente se utilizan en el estilo algorítmico para almacenar valores
intermedios de un proceso. La sintaxis para declarar una variable es la
siguiente:
variable identificador {, …} : tipo [:= expresión];
Obsérvese que salvo la palabra reservada, la sintaxis para declarar una variable
es análoga a la de una constante. Para modificar el valor de una variable se
utilizan sentencias de asignación, que para el caso de las variables toman la
forma siguiente:
identificador := expresión;
Una señal es un objeto que, al igual que una variable, puede modificar su valor
dentro de los posibles valores de su tipo pero, a diferencia de ésta, tiene una
analogía directa en hardware, ya que se puede considerar como una abstracción
de una conexión física o bus. Al contrario que las variables, no está restringida a
un proceso sino que sirve para interconectar componentes de un circuito y para
sincronizar la ejecución y suspensión de procesos. La sintaxis en VHDL para
declarar una señal es muy parecida a la requerida para constantes y variables:
signal identificador {, …} : tipo [:= expresión];
A diferencia de las variables, una señal no se declarará en la parte declarativa
de un proceso sino en la arquitectura del dispositivo.
Los puertos de una entidad son señales que se utilizan para interconectar el
dispositivo con otros dispositivos. Su declaración es un poco especial, pues
aparte de determinar un identificador y un tipo de datos es necesario indicar la
dirección de la señal respecto a la entidad. La sección de declaración de puertos
de una entidad tiene la siguiente sintaxis:
port ({identificador {, …} : dirección tipo [:= expresión];} );
En este caso, la expresión opcional se utilizará en el caso que el puerto esté
desconectado.
Por último, el fichero es un objeto que permite comunicar un diseño VHDL con
un entorno externo, de manera que un modelo puede escribir y leer datos del
mismo. Un uso bastante común de los ficheros es el de almacenar estímulos de
simulación que se quieran aplicar al modelo en un fichero de entrada y salvar
los resultados de simulación en un fichero de salida para su posterior estudio.
La sintaxis para declarar un fichero es la siguiente:
file identificador {, …} : tipo_fichero [is dirección “nombre”;]
El tipo de datos es un concepto fundamental en VHDL, ya que cada objeto debe
ser de un tipo concreto que determinará el conjunto de valores que puede
asumir y las operaciones que se podrán realizar con ese objeto. La declaración
de un nuevo tipo de datos toma la siguiente forma:
6/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
type identificador is definición_tipo;
La parte de definición de tipo sirve para indicar el conjunto de valores del tipo.
Dos posibles ejemplos serían los siguientes:
type DiaMes is range 1 to 31;
type PuntosCardinales is (norte,sur,este,oeste);
Una vez definido un tipo de datos, se pueden declarar objetos de este tipo. Los
tipos existentes de datos escalares se pueden clasificar en enteros y reales,
físicos y enumerados. La sintaxis para declarar a los primeros de ellos es:
type identificador is range literal to | downto literal;
Dependiendo de si se escriben literales enteros o en punto flotante, el tipo de
datos declarado será de tipo entero o real. Las palabras reservadas to y downto
se utilizan para indicar un rango creciente o decreciente. Los tipos físicos sirven
para representar medidas del mundo real y su sintaxis de declaración es la
siguiente:
type identificador is range literal to | downto literal
units
identificador;
{identificador = literal_físico;}
end units [identificador];
Un ejemplo para definir un tipo físico de tiempo sería el siguiente:
type time is range 0 to 1E20
units
fs;
ps = 1000 fs;
ns = 1000 ps;
us = 1000 ns;
ms = 1000 us;
sec = 1000 ms;
min = 60 sec;
hr = 60 min;
end units;
Por último, el tipo enumerado define un conjunto específico de posible valores
mediante una lista donde se definen todos y cada uno de los valores posibles.
La sintaxis para declarar un tipo enumerado es la siguiente:
type identificador is ( identificador | carácter {, …});
Algunos ejemplos de tipos enumerados podrían ser los siguientes:
type
type
type
type
type
comandos is (izquierda,derecha,arriba,abajo,disparo);
teclas is (‘a’,‘d’,‘w’,‘x’,‘’);
mezcla is (‘a’,izquierda,‘d’,derecha);
boolean is (false,true);
bit is (‘0’,‘1’);
Dado un tipo de datos, se puede definir un subtipo del mismo mediante la
siguiente cláusula:
7/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
subtype identificador is id_tipo [range literal to | downto literal];
Por ejemplo, el tipo natural o el tipo positive son subtipos del tipo integer:
subtype natural is integer range 0 to integer’high;
subtype positive is integer range 1 to integer’high;
Existen también los denominados tipos de datos compuestos constituidos por
los vectores y los registros. En los primeros de ellos, tenemos un conjunto de
objetos del mismo tipo ordenados mediante uno o más índices que indican la
posición de cada objeto dentro del vector. Para declarar un vector se tendrá que
crear un tipo que básicamente determine el tipo de los objetos que formarán el
vector y el rango de los índices que siempre será de un tipo discreto, es decir,
entero o enumerado. La sintaxis para declarar un vector es la siguiente:
type identificador is array (rango {, …}) of tipo_objetos;
El identificador da nombre al vector y servirá para referenciarlo, los rangos
pueden describirse explícitamente en la declaración o bien se puede dar
directamente un nombre de tipo o subtipo que ya incluya una restricción de
rango y, finalmente, el tipo indicará el conjunto de valores posibles que pueden
tomar los objetos del vector. Algunos ejemplos de declaración de vectores son
los siguientes:
type Byte is array (0 to 7) of bit;
subtype Decimal is character range ‘0’ to ‘9’;
type Byte2 is array (Decimal range ‘0’ to ‘7’) of bit;
type PuntosCardinales is (norte,sur,este,oeste);
type Estado is array (PuntosCardinales range norte to este) of integer;
A continuación podrían declararse objetos de los tipos anteriores:
variable operador1 : Byte;
variable opeardor2,operador3 : Byte2;
variable EstadoActual : Estado;
Las siguientes sentencias de asignación son posibles con los vectores:
operador1 := “10010101”;
operador1(3) := ‘1’;
operador1(3 to 6) := “1001”;
operador2(‘5’) := ‘1’;
operador3 := operador2;
EstadoActual(norte) := 35;
También es posible crear vectores de más de una dimensión. Por ejemplo:
type Memoria is array (0 to 7, 0 to 63) of bit;
variable RamA,RamB : Memoria;
Posibles sentencias de asignación serían las siguientes:
RamA := RamB;
RamA(4,7) := ‘1’;
Cuando se desea declarar un tipo de vector no restringido la sintaxis es:
8/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
type identificador is array (tipo_índice range <> {, …}) of tipo_objeto;
Un ejemplo sería la definición de una cadena como una sucesión de caracteres:
type string is array (positive range <>) of character;
La otra clase de objeto compuesto es el registro que, a diferencia de los vectores,
está formado por unidades atómicas de distinto tipo, que reciben el nombre de
campos. La sintaxis para declarar un registro es:
type identificador is record
identificador {, …} : tipo;
{…}
end record [identificador];
Un ejemplo sería el siguiente:
type Fecha is record
Dia : integer range 1 to 31;
Mes : integer range 1 to 12;
Anyo : integer range 0 to 2100;
end record;
Por último, trataremos el tema de los operadores que pueden utilizarse para
generar expresiones en VHDL. Los operadores básicos definidos en VHDL se
muestran en la siguiente tabla.
Operador
**
abs
not
*
/
Descripción
potencia
valor absoluto
negación
multiplicación
división
mod
rem
+
+
&
módulo
resto
más unario
menos unario
suma
resta
concatenación
=
/=
igual que
diferente que
Tipo Operandos
entero op entero
real op entero
numérico
bit, booleano, vector bits
entero op entero
real op real
físico op entero
físico op real
entero op físico
real op físico
entero op entero
real op real
físico op entero
físico op real
físico op físico
entero op entero
entero op entero
numérico
numérico
numérico op numérico
numérico op numérico
vector op vector
vector op elemento
elemento op vector
elemento op elemento
no fichero op no fichero
no fichero op no fichero
9/21
Resultado
entero
real
ídem operando
ídem operando
entero
real
físico
físico
físico
físico
entero
real
físico
físico
entero
entero
entero
ídem operando
ídem operando
ídem operandos
ídem operandos
vector
vector
vector
vector
booleano
booleano
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
<
>
<=
>=
and
or
menor que
mayor que
menor o igual que
mayor o igual que
y lógica
o lógica
nand
y lógica negada
nor
o lógica negada
xor
o exclusiva
xnor
o exclusiva negada
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
no fichero op no fichero
no fichero op no fichero
no fichero op no fichero
no fichero op no fichero
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
bit, booleano, vector bits
op
booleano
booleano
booleano
booleano
ídem operandos
op
ídem operandos
op
ídem operandos
op
ídem operandos
op
ídem operandos
op
ídem operandos
6.2.3. Sentencias secuenciales
Las sentencias secuenciales son aquellas que nos permiten modelar la
funcionalidad de un componente. Las podemos clasificar en sentencias de
asignación (a variable o a señal), sentencias condicionales (if, case), sentencias
iterativas (loop, exit, next), otras sentencias (wait, assert, null) y llamadas a
subprogramas. A continuación se resume la sintaxis de cada una de ellas.
La sentencia wait se utiliza para suspender la ejecución de un proceso. Su
sintaxis es:
[etiqueta:] wait [on señal {, ...}]
[until expresión_booleana]
[for expresión_tiempo]
La asignación a señal como sentencia secuencial presenta la siguiente sintaxis:
[etiqueta:] nombre_señal <= valor [after expresión_tiempo];
La forma en que se comporte la asignación a señal dependerá básicamente del
modelo de retardo elegido. VHDL permite escoger entre dos tipos de retardo: el
transporte (transport) y el inercial (inertial). El modelo de transporte propaga
cualquier cambio que se produzca, mientras que el inercial filtra aquellos
cambios de duración inferior a un mínimo. El modelo de transporte es el que
refleja una línea de transmisión ideal, mientras que el modelo inercial es el que
rige el comportamiento de una puerta lógica.
Por defecto, VHDL supone que las asignaciones a señal siguen el modelo
inercial. Para usar el modelo de transporte debe especificarse en la asignación:
[etiqueta:] nombre_señal <= [transport] valor [after expresión_tiempo];
10/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
La asignación a variable reemplaza el valor actual de la variable con el valor
especificado por una expresión. Su sintaxis es la siguiente:
[etiqueta:] nombre_variable := expresión;
La sentencia if se utiliza para escoger en función de alguna condición qué
sentencias deben ejecutarse. Su sintaxis es la siguiente:
if condicion then sentencias_secuenciales
{elsif condicion then sentencias_secuenciales}
[else sentencias_secuenciales]
end if;
Las condiciones deben ser de tipo booleano, de tal forma que devuelvan
verdadero (true) o falso (false) a fin de ver si se ejecutan las sentencias
secuenciales indicadas.
La sentencia case se utiliza para escoger qué grupo de sentencias deben
ejecutarse entre un conjunto de posibilidades, basándose en el rango de valores
de una determinada expresión de selección. Su sintaxis es la siguiente:
[etiqueta:] case expresión is
when valor => sentencias_secuenciales;
{when valor => sentencias_secuenciales;}
end case;
Puede utilizarse la palabra clave others en valor para especificar todos los demás
rangos no declarados específicamente. En ese caso, hay que especificar esta
opción la última, después de los demás casos.
La sentencia loop se utiliza para ejecutar un grupo de sentencias secuenciales de
forma repetida. El número de repeticiones puede controlarse en la misma
sentencia usando alguna de las opciones que ésta ofrece. Su sintaxis es:
[etiqueta:] [while condición_booleana | for control_repetición] loop
sentencias_secuenciales;
end loop [etiqueta];
Podemos usar la sentencia loop sin ningún tipo de control sobre el número de
repeticiones del bucle, de forma que se provoque la ejecución infinita del grupo
de sentencias secuenciales especificadas.
La sentencia exit está relacionada con la sentencia loop, y ofrece una forma de
terminar la ejecución del bucle. Su sintaxis es:
[etiqueta:] exit [etiqueta_loop] [when condición_booleana];
La sentencia next, por contra, se utiliza para detener la ejecución de una
sentencia loop y pasar a la siguiente iteración de la misma. Su sintaxis es:
[etiqueta:] next [etiqueta_loop] [when condición_booleana];
11/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
La sentencia assert permite reportar mensajes en función de si una determinada
condición se cumple o no. También permite interrumpir la simulación en
función de dicha condición. Su sintaxis es:
[etiqueta:] assert expresión_booleana [report cadena_caracteres]
[expresión_severidad];
El valor de la expresión de severidad debe ser note, warning, error o failure. En
caso de no especificar el nivel de severidad, por defecto es error. Normalmente
el simulador permite al usuario determinar para qué nivel de severidad debe
interrumpirse la simulación.
Los subprogramas (procedimientos o funciones) que tengamos definidos en
alguna parte del código pueden ser llamados para su ejecución en cualquier
parte de un código secuencial. La sintaxis de llamada a un procedimiento
(procedure) es:
[etiqueta:] nombre_procedimiento [{parámetros, ...}];
La sintaxis de llamada a una función (function) es:
nombre_función [{parámetros, ...}];
La diferencia radica en que una llamada a una función forma parte de una
expresión de asignación o es la asignación en sí misma, mientras que la llamada
a un procedimiento es una sentencia secuencial por sí sola.
La sentencia return se utiliza para termina la ejecución de un subprograma. Su
sintaxis general es:
[etiqueta:] return [expresión];
En un procedimiento no se devuelve expresión alguna, mientras que en una
función se devuelve el valor a asignar.
La sentencia null se usa para indicar que no se debe realizar ninguna acción. Su
sintaxis general es:
[etiqueta:] null;
Es útil, por ejemplo, en sentencias case para indicar que en determinados casos
(opciones) no se haga nada.
6.2.4. Sentencias concurrentes
Las sentencias concurrentes son aquellas que se ejecutan en paralelo, por lo que
no están incluidas en ningún proceso, sino que aparecen en la arquitectura del
modelo.
12/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
Un proceso es una sentencia concurrente que define un comportamiento a
través de sentencias secuenciales. Cualquier sentencia concurrente o secuencial
en VHDL tiene su proceso equivalente. La sintaxis general de un proceso es:
[etiqueta:] process [(nombre_señal {, ...})] [is]
declaraciones;
begin
sentencias_secuenciales;
end process [etiqueta];
Las asignaciones a señal pueden darse en el mundo concurrente. Su sintaxis es
muy similar a la asignación secuencial:
[etiqueta:] nombre_señal <= [transport] forma_onda;
La asignación concurrente condicional es una forma compacta de expresar las
asignaciones a señal usando la sentencia if secuencial. Su sintaxis es:
[etiqueta:] nombre_señal <= [transport]
{forma_onda when expresión_booleana else}
forma_onda [when expresión_booleana];
La asignación concurrente con selección es una forma compacta de expresar las
asignaciones a señal usando la sentencia case secuencial. Su sintaxis es:
[etiqueta:] with expresión select
nombre_señal <= [transport]
{forma_onda when valor,}
forma_onda when valor;
La sentencia assert puede darse también en el mundo concurrente. Su sintaxis
es:
[etiqueta:] assert expresión_booleana [report expresión]
[expresión_severidad];
La llamada concurrente a un subprograma toma la siguiente forma para un
procedimiento:
[etiqueta:] nombre_procedimiento [{parámetros , ...}];
Para una función, se tendrá:
nombre_función [{parámetros, ...}];
6.2.5. Sentencias estructurales
VHDL proporciona una serie de sentencias dedicadas a la descripción
estructural de hardware. Son también sentencias concurrentes pues se ejecutan
en paralelo con cualquier otra sentencia concurrente de la descripción y
aparecen en la arquitectura de un modelo fuera de cualquier proceso.
La declaración de un componente puede aparecer en una arquitectura o en un
paquete. Si aparece en la arquitectura, entonces se pueden hacer copias del
componente en dicha arquitectura; si aparece en un paquete, se pueden hacer
13/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
copias del componente en todas aquellas arquitecturas que usen ese paquete. La
sintaxis de la declaración de un componente es:
component nombre_componente [is]
[generic (lista_genéricos);]
[port (lista_puertos);]
end component [nombre_componente];
Una vez declarado un componente, se pueden hacer referencias al mismo
dentro de la arquitectura. La sintaxis de referencia a un componente es:
[etiqueta_referencia:] nombre_componente
[generic map (lista_asociación);]
[port map (lista_asociación)];
También es posible referenciar un componente sin necesidad de haberlo
declarado antes. La sintaxis de referencia es en este caso:
[etiqueta_referencia:] entity nombre_entidad[(nombre_arquitectura)]
[generic map (lista_genéricos);]
[port map (lista_puertos)];
Una forma habitual de describir estructuras en el hardware es la realización de
múltiples copias de elementos iguales (o parecidos). Estas descripciones
estructurales podrían realizarse con la copia o referencia a componente que se
ha descrito anteriormente, pero VHDL ofrece una forma más cómoda y
compacta para realizar descripciones que se basen en la repetición de la misma
estructura: la sentencia generate. Su sintaxis es:
[etiqueta_generate:] {[for especificación_for | if condición]} generate
{sentencias_concurrentes}
end generate;
Otra posible sintaxis para la sentencia generate que permite la inclusión de una
parte declarativa es la siguiente:
[etiqueta_generate:] {[for especificación_for | if condición]} generate
[{parte_declarativa}
begin]
{sentencias_concurrentes}
end generate;
En la parte declarativa puede aparecer cualquier elemento que pueda aparecer
en la parte declarativa de una arquitectura (constantes, tipos, subtipos,
subprogramas y señales).
Como hemos visto antes, la configuración de un diseño permite escoger cuál de
las posibles arquitecturas de una entidad va a ser utilizada. La configuración de
un diseño puede aparecer en una arquitectura, entonces se llama especificación
de configuración, o puede aparecer como una unidad de diseño independiente,
entonces se llama declaración de configuración. Si dentro de la misma
arquitectura, en la cual se usa una referencia a otros componentes, se quiere
indicar qué arquitectura se quiere utilizar para cada uno de los componentes
14/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
referenciados, se puede usar la especificación de configuración. La sintaxis
general para la especificación de configuración es la siguiente:
for (ref_componente {, ...} | others | all) : id_componente
use entity id_entidad[(id_arquitectura); |
use configuration id_configuación;]
Cuando la configuración se utiliza como un diseño independiente, la sintaxis de
esta declaración de configuración es:
configuration identificador of identificador_entidad is
for identificador_arquitectura
{for (ref_componente {, ...} | others | all): id_componente
use entity id_entidad[(id_arquitectura); |
use configuration id_configuración;]
end for; }
end for;
end [configuration] [identificador];
Tal como hemos visto anteriormente, VHDL ofrece la posibilidad de generalizar
un modelo añadiendo unos parámetros llamados genéricos (generics) en la
definición de la entidad del modelo. Si queremos desarrollar un modelo
genérico, debemos incluir en la entidad del mismo la cláusula generic, e incluir
los parámetros genéricos del modelo. Para dar valores concretos a un módulo
con genéricos cuando es usado como componente debe usarse la cláusula
generic map.
La sentencia concurrente block es una forma de reunir o agrupar sentencias
concurrentes, además de permitir compartir declaraciones de objetos que serán
visibles solamente para las sentencias englobadas en la sentencia block. La
sintaxis de esta sentencia es la siguiente:
etiqueta: block [expresión_guarda] [is]
[generic (lista_genéricos);
[generic map (lista_asociación_genéricos);]]
[port (lista_puertos);
[port map (lista_asociación_puertos);]]
{parte declarativa}
begin
{sentencias_concurrentes};
end block [etiqueta];
Los subprogramas se usan para escribir algoritmos reutilizables. Los
subprogramas constan de dos partes: la definición del subprograma y la
definición del cuerpo del subprograma. Para el caso de las funciones, la sintaxis
de definición es:
function nombre_función [(lista_parámetros)] return tipo_retorno;
La sintaxis de definición del cuerpo de una función es:
[pure | impure] function nombre_función [(lista_parámetros)]
return tipo_retorno is
{parte_declarativa}
begin
{sentencias_secuenciales};
15/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
end [function] [nombre_función];
Una función se considera pura (pure) si dado un conjunto de valores de sus
parámetros de entrada siempre retorna el mismo resultado, mientras que una
función impura (impure) puede romper estar regla.
Para el caso de los procedimientos, la sintaxis de definición es:
procedure nombre_procedimiento [(lista_parámetros)];
La sintaxis para la definición del cuerpo del procedimiento es:
procedure nombre_procedimiento [(lista_parámetros)] is
{parte_declarativa}
begin
{sentencias_secuenciales};
end [procedure] [nombre_procedimiento];
Los parámetros formales de un procedimiento pueden ser de tres tipos: in, out e
inout, y por defecto se consideran de tipo in. En las funciones, normalmente los
parámetros serán todos de tipo in.
Por último, hay que destacar que VHDL permite la sobrecarga de
subprogramas, es decir, se puede definir una misma función (con el mismo
nombre) que varíe su funcionalidad según sean los tipos de sus parámetros de
entrada. Así, es posible definir una misma función para diferentes tipos de
argumentos. Un ejemplo sería la sobrecarga de un operador (que debe
especificarse entre comillas dobles), por ejemplo el operador suma o el
operador “y lógica”, para unos tipos definidos por el usuario:
function “+” (a: MiTipo; b: MiTipo) return MiTipo;
function “and” (a: MiTipo; b: MiTipo) return MiTipo;
6.3. Tarjeta de desarrollo de la Spartan-3A
La tarjeta de desarrollo que vamos a utilizar
recibe el nombre de “Spartan-3A/3AN Starter
Kit Board”. Dicha tarjeta dispone de una
FPGA Spartan-3A XC3S700A, además de
varios LED, displays, interruptores (switches),
pulsadores, conversores A/D y D/A que
ofrecen múltiples posibilidades de desarrollo
al usuario. Cada uno de los dispositivos está
convenientemente conectado a la FPGA a
través de alguno de sus puertos. A
continuación se da una lista de los puertos de
conexión de los interruptores, pulsadores y
LED que vamos a utilizar en esta práctica.
16/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
Nombre
SW0
SW1
SW2
SW3
BTN_NORTH
BTN_SOUTH
BTN_EAST
BTN_WEST
LED0
LED1
LED2
LED3
LED4
LED5
LED6
LED7
Tipo
Interruptor
Interruptor
Interruptor
Interruptor
Pulsador
Pulsador
Pulsador
Pulsador
LED
LED
LED
LED
LED
LED
LED
LED
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
Puerto de conexión
V8
U10
U8
T9
T14
T15
T16
U15
R20
T19
U20
U19
V19
V20
Y22
W21
Siempre que queramos hacer uso de alguno de estos dispositivos habrá que
indicárselo en el archivo UCF correspondiente al diseño que estemos
realizando. Un posible código de ejemplo para usar los ocho LED sería:
NET
NET
NET
NET
NET
NET
NET
NET
"LED<7>"
"LED<6>"
"LED<5>"
"LED<4>"
"LED<3>"
"LED<2>"
"LED<1>"
"LED<0>"
LOC
LOC
LOC
LOC
LOC
LOC
LOC
LOC
=
=
=
=
=
=
=
=
"W21"
"Y22"
"V20"
"V19"
"U19"
"U20"
"T19"
"R20"
|
|
|
|
|
|
|
|
IOSTANDARD
IOSTANDARD
IOSTANDARD
IOSTANDARD
IOSTANDARD
IOSTANDARD
IOSTANDARD
IOSTANDARD
=
=
=
=
=
=
=
=
LVTTL
LVTTL
LVTTL
LVTTL
LVTTL
LVTTL
LVTTL
LVTTL
|
|
|
|
|
|
|
|
SLEW
SLEW
SLEW
SLEW
SLEW
SLEW
SLEW
SLEW
=
=
=
=
=
=
=
=
QUIETIO
QUIETIO
QUIETIO
QUIETIO
QUIETIO
QUIETIO
QUIETIO
QUIETIO
|
|
|
|
|
|
|
|
DRIVE
DRIVE
DRIVE
DRIVE
DRIVE
DRIVE
DRIVE
DRIVE
=
=
=
=
=
=
=
=
4
4
4
4
4
4
4
4
;
;
;
;
;
;
;
;
6.4. Realización práctica
En esta primera práctica sobre diseño en VHDL, vamos a familiarizarnos con la
tarjeta de desarrollo de la Spartan-3A y con el lenguaje VHDL. Para ello, vamos
a diseñar un sistema que nos permita realizar distintas operaciones sobre unas
entradas controladas a través de interruptores. Un pulsador nos permitirá
seleccionar el tipo de operación a realizar, mientras que los LED actuarán como
dispositivos de visualización de los resultados obtenidos.
1. En primer lugar, diseñar un paquete que incluya las funciones de cálculo
de la operación y selección de la misma. A continuación se da un posible
ejemplo:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.ALL;
package paquete is
constant N : natural := 2;
constant M : natural := 3;
constant cuenta_max : natural := 2**10-1;
type operacion is (suma,resta,multiplicacion,op_xor,op_and,op_or);
function op_sig(operacion_actual : operacion) return operacion;
17/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
function calcular(a,b : signed(N-1 downto 0); operacion_actual :
operacion) return signed;
function tipo(operacion_actual : operacion) return std_logic_vector;
end package paquete;
package body paquete is
function op_sig(operacion_actual : operacion) return operacion is
variable operacion_nueva : operacion;
begin
case operacion_actual is
when suma => operacion_nueva := resta;
when resta => operacion_nueva := multiplicacion;
when multiplicacion => operacion_nueva := op_xor;
when op_xor => operacion_nueva := op_and;
when op_and => operacion_nueva := op_or;
when op_or => operacion_nueva := suma;
when others => operacion_nueva := suma;
end case;
return operacion_nueva;
end function op_sig;
function calcular(a,b : signed(N-1 downto 0); operacion_actual :
operacion) return signed is
variable resultado : signed(N-1 downto 0);
begin
case operacion_actual is
when suma => resultado := a + b;
when resta => resultado := a - b;
when multiplicacion => resultado := a * b;
when op_xor => resultado := a xor b;
when op_and => resultado := a and b;
when op_or => resultado := a or b;
when others => resultado := (others=>'1');
end case;
return resultado;
end function calcular;
function tipo(operacion_actual : operacion) return
std_logic_vector is
variable resultado : std_logic_vector(M-1 downto 0);
begin
case operacion_actual is
when suma => resultado := "000";
when resta => resultado := "001";
when multiplicacion => resultado := "010";
when op_xor => resultado := "011";
when op_and => resultado := "100";
when op_or => resultado := "101";
when others => resultado := "111";
end case;
return resultado;
end function tipo;
end paquete;
Obsérvese que se han creado dos funciones op_sig y calcular, la primera
de ellas para determinar cuál es la función siguiente a seleccionar y la
segunda para devolver el resultado de la operación correspondiente.
Asimismo, se ha creado una función tipo para que devuelva un vector de
tres bits que determine el tipo de operación seleccionada actualmente.
Esta función es importante únicamente a efectos de visualizar mediante
los LED qué función está activa actualmente. Obsérvese también que se
define una constante N para determinar el número de bits de los
18/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
operandos (en este caso, 2 bits) y otra constante M para definir el número
de bits requeridos para definir el tipo de operación seleccionada en ese
momento. Igualmente se ha creado un tipo enumerado denominado
operacion que puede tomar una serie de valores representativos de
distintas operaciones aritméticas o lógicas.
2. A continuación, crear un módulo VHDL que contenga el diseño de todo
el sistema que haga uso del paquete definido anteriormente. Un posible
código sería:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.paquete.ALL;
entity sistema is
Port ( a,b : in
SIGNED (N-1 downto 0);
reset,selector : in std_logic;
modo : out std_logic_vector(M-1 downto 0);
salida : out SIGNED (N-1 downto 0));
end sistema;
architecture Comportamental of sistema is
signal estado : operacion := suma;
begin
process(reset,selector)
begin
if reset='1' then
estado <= suma;
elsif selector = '1' then
estado <= op_sig(estado);
end if;
end process;
salida <= calcular(a,b,estado);
modo <= tipo(estado);
end Comportamental;
Obsérvese que el sistema final es muy sencillo, pues hace uso
directamente de las funciones implementadas. En primer lugar, se crea
una entidad con dos entradas a y b de tipo signed de N bits cada una (dos
bits en este caso), una entrada de reset y un selector, ambos de un bit
(std_logic), un vector de salida denominado modo de M bits
(std_logic_vector) y otro denominado salida de N bits y tipo signed.
Cuando se activa el reset (mediante algún pulsador), el estado o modo de
operación se establece a suma. En cualquier otro caso, si se pulsa el
selector se pasa al modo de operación siguiente mediante la función
op_sig. De igual forma, existen dos sentencias concurrentes que se
ejecutarán siempre que cambie estado o alguna de las entradas a ó b,
realizándose la operación seleccionada mediante la función calcular y
estableciendo el modo adecuado en los LED mediante la función tipo.
19/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
3. Establecer los puertos de conexión de la FPGA con los pulsadores, LED e
interruptores mediante las sentencias correspondientes en el archivo
UCF. Un posible código sería el siguiente:
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
"selector" CLOCK_DEDICATED_ROUTE = FALSE;
"reset" CLOCK_DEDICATED_ROUTE = FALSE;
"salida<0>" LOC = R20;
"salida<1>" LOC = T19;
"reset" LOC = U15;
"selector" LOC = T15;
"a<0>" LOC = U8;
"a<1>" LOC = T9;
"b<0>" LOC = V8;
"b<1>" LOC = U10;
"modo<0>" LOC = V20;
"modo<1>" LOC = Y22;
"modo<2>" LOC = W21;
Las dos primeras líneas se han incluido para eliminar posibles errores de
implementación del software debido a que los pulsadores no son líneas de
reloj dedicadas. Obsérvese que estamos utilizando los pulsadores
BTN_WEST y BTN_SOUTH para las líneas de reset y selector, respectivamente,
mientras que los LED LED0 y LED1 los utilizamos para mostrar el
resultado, y los LED LED5 a LED7 se utilizan para indicar el modo (tipo de
operación activa). La lectura de los valores de entrada a y b se toma de
los interruptores SW0 a SW3.
4. Programar la Spartan-3A con el diseño y probar su correcto
funcionamiento. A la hora de interpretar los resultados, tener en cuenta
que la notación signed para dos bits es la siguiente:
Valor decimal
0
1
-1
-2
Representación en binario
00
01
11
10
5. Obsérvese el efecto de rebote del pulsador a la hora de seleccionar la
operación a realizar. Intercambiar el código de la entidad sistema por el
que se indica a continuación, el cual ha sido diseñado para eliminar el
efecto del rebote mediante la lectura del valor del pulsador selector
únicamente cada 167,7 ms aproximadamente, para un reloj del sistema
de 50 MHz (cada 223 ciclos de reloj).
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.paquete.ALL;
entity sistema is
Port ( a,b : in
SIGNED (N-1 downto 0);
reset,selector,reloj : in std_logic;
modo : out std_logic_vector(M-1 downto 0);
20/21
PRÁCTICA 6:
DISEÑO LÓGICO DIGITAL
MEDIANTE VHDL
salida : out
end sistema;
POP Tecn. Electrónicas y Comun.
SISTEMAS DE COMUNICACIONES
DIGITALES
SIGNED (N-1 downto 0));
architecture Comportamental of sistema is
signal estado : operacion := suma;
signal reloj2 : std_logic := '0';
begin
process(reset,reloj2)
--variable pulsador_activo : std_logic := '0';
begin
if reset='1' then
estado <= suma;
elsif reloj2'event and reloj2 = '1' then
if selector = '1' then
estado <= op_sig(estado);
end if;
end if;
end process;
process(reset,reloj)
variable contador : natural range 0 to (2**23) := 0;
begin
if reset='1' then
contador := 0;
reloj2 <= '0';
elsif reloj'event and reloj='1' then
contador := (contador + 1) mod (2**23);
if contador = 0 then
reloj2 <= not(reloj2);
end if;
end if;
end process;
salida <= calcular(a,b,estado);
modo <= tipo(estado);
end Comportamental;
6. Obsérvese que se ha incluido una línea de entrada más al sistema, la
línea reloj que provendrá del oscilador de la tarjeta (50 MHz). Modificar
el archivo UCF para que quede de la siguiente manera:
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
NET
"selector" CLOCK_DEDICATED_ROUTE = FALSE;
"reset" CLOCK_DEDICATED_ROUTE = FALSE;
"salida<0>" LOC = R20;
"salida<1>" LOC = T19;
"reset" LOC = U15;
"selector" LOC = T15;
"reloj" LOC = E12;
"a<0>" LOC = U8;
"a<1>" LOC = T9;
"b<0>" LOC = V8;
"b<1>" LOC = U10;
"modo<0>" LOC = V20;
"modo<1>" LOC = Y22;
"modo<2>" LOC = W21;
7. Compilar nuevamente el programa y cargarlo en la FPGA de la tarjeta.
Comprobar el nuevo funcionamiento del selector de operación tras la
mejora introducida.
21/21
Descargar