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