Você está na página 1de 16

Hola.

En las próximas sesiones nos dedicaremos a hacer una introducción a los lenguajes de
descripción y modelado de hardware. Los llamados hardware description languages o HDLs. En
especial veremos el VHDL y en esta primera sesión introduciremos el léxico, la sintaxis y la
estructura de base de este lenguaje. Antes de entrar en materia veamos qué son y para qué se
utilizan estos lenguajes de descripción de hardware. Son lenguajes estructurados, de alto nivel de
abstracción, y orientados a describir y modelar el funcionamiento del hardware. Tienen una
sintaxis y una semántica formales para evitar las ambigüedades de los lenguajes naturales y de los
dibujos de bloques que acompañan las especificaciones escritas por y para los humanos. Son
fáciles de entender tanto para los ordenadores como para las personas y como veremos se
parecen a, o incluso se derivan de, los lenguajes de programación de software como pueden ser el
C, el C++, Ada, etc. Pero incorporan particularidades para modelar el hardware, especialmente la
gestión del tiempo y la concurrencia inherentes al hardware. ¿Para qué se utilizan? Bueno
básicamente se utilizan con 3 objetivos, modelado del funcionamiento del hardware sin fijar su
implementación tecnológica, esto es el modelado independiente de la tecnología e incluso
de la estructura final a implementar. También se utilizan para simular los modelos desarrollados
frente a las especificaciones de funcionamiento esperadas a fin de validar que el modelo HDL
realizado cumple dichas especificaciones y por lo tanto es correcto. Y por último también se
utilizan para sintetizar dichos modelos mediante la tecnología de implementación escogida. Un
mismo modelo HDL se puede materializar mediante herramientas de síntesis automática con
distintas tecnologías FPGAs, ASICS u otras. ¿Cuáles son los principales lenguajes de descripción de
hardware? Básicamente dos, el VHDL y el Verilog, aunque hay otros. El VHDL es el Very High
Speeding Integrated Circuit Hardware Description Language. Es un estándar del IEEE, el estándar
1076 que se creó o se estandarizó en el 1987. Y el Verilog es un lenguaje que nació como lenaguaje
de simulación de Cadence en 1985 y que posteriormente se convirtió en un lenguaje de dominio
público y se estandarizó, también por parte del IEEE, con el estándar 1364 en 1995. Hay otros
lenguajes como el C, el C++, el SystemC o el SystemVerilog que son lenguajes de un mayor nivel de
abstracción y orientados a especificar sistemas complejos, hardware-software a nivel funcional. En
esta figura se recogen los conceptos principales entorno a los lenguajes de descripción de
hardware a la vez que se hace una breve comparativa con los lenguajes de desarrollo de software.
En ambos casos se pretenden hacer descripciones funcionales de alto nivel de abstracción, que
serán independientes del procesador que los ejecute o de la tecnología en la
que vayan a materializarse. Así pues vemos que desarrollamos programas software mediante
lenguajes estructurados de alto nivel que una vez compilados se convierten en programas de
código máquina ejecutables por microprocesadores específicos. En el caso del hardware, vemos
que también se trata de lenguajes estructurados de alto nivel que nos permiten generar modelos
HDL a partir de los cuales podemos realizar o bien procesos de simulación o bien procesos de
síntesis para ir a una determinada materialización de ese modelo, o bien procesos de verificación
por simulación presíntesis y post síntesis con comparación posterior de resultados. Obsérvese que
mientras el software el objetivo final es un programa ejecutándose en un determinado procesador,
en hardware el objetivo final puede ser doble, o bien la simulación de nuestros modelos para
comprobar su correcto funcionamiento o bien la síntesis de dichos modelos para proceder a su
materialización física en hardware real. Bien, después de esta breve presentación de los HDLs, de
los lenguajes de descripción de hardware, vamos ya a adentrarnos en el VHDL y para ello
abordaremos la agenda siguiente. Veremos en primer lugar el léxico y la sintaxis de este lenguaje,
luego detallaremos brevemente cómo el VHDL se estructura en en unidades de diseño, y a
continuación seleccionaremos y presentaremos un subconjunto de las sentencias VHDL, tanto las
sentencias secuenciales como de las sentencias concurrentes. Y por último introduciremos muy
brevemente
el flujo de diseño basado en VHDL que aborda las fases de modelado, simulación y síntesis, y
concluiremos como de costumbre con un resumen. Empecemos pues por los elementos léxicos de
este lenguaje que resumimos en palabras reservadas, identificadores, símbolos y literales. Palabras
reservadas del propio lenguaje son todas aquellas que el lenguaje utiliza para fijar la sintaxis y sus
distintas estructuras y sentencias. Los identificadores sirven para dar nombre a los distintos
elementos, estructuras y objetos que este lenguaje puede crear y manejar. Estos identificadores
tienen unas determinadas reglas como estar formados por caracteres alfanuméricos, el primer
carácter tiene que ser alfabético, mayúsculas y minúsculas son indiferentes y las palabras
reservadas no no se pueden utilizar como identificadores. Y aquí tenemos algunos ejemplos. Los
símbolos son conjuntos de 1 o 2 caracteres que tienen un significado especial dentro del lenguaje,
como por ejemplo operadores aritméticos, signos de puntuación, símbolos que forman parte de
sentencias o expresiones o símbolos como este de aquí que es el símbolo de comentario. También
tenemos valores literales que son valores explícitos de cualquier tipo de datos que se pueden
expresar de distintas formas como las que vemos aquí. Aquí tenemos un valor expresado mediante
la indicación de su base y su magnitud, su valor, otro con base hexadecimal, unos dígitos
hexadecimales, o cadenas de caracteres o caracteres individuales, valores de magnitudes físicas,
etcétera. A lo largo de esta introducción y en su posterior uso en el curso, iremos aprendiendo a
manejarnos con este léxico de forma sencilla e intuitiva. Como todos los lenguajes, el VHDL
dispone y maneja objetos que pueden contener y contienen valores. Estos objetos son las
constantes variables, señales y ficheros. Para declarar o definir un objeto esta es la sintaxis, se
define el tipo de objeto, se le asocia un identificador, un nombre a ese objeto, se indica de qué tipo
de datos es ese objeto y por último y opcionalmente se puede dar un valor inicial de ese objeto.
Así pues vemos declaraciones de constantes o constantes declaradas en función de constantes
previamente definidas como era el caso de "TotBits", este que tenemos aquí.
Vemos también declaraciones de variables y uso de variables en expresiones, una expresión que
asigna un valor constante a una variable o bien una expresión que asigna el resultado de una
operación a una variable. También tenemos señales, aquí tenemos la declaración de una señal y
aquí tenemos la asignación de un valor a una señal. Observemos que cuando asignamos a las
señales utilizamos el símbolo de implicación hacia la izquierda (<=) mientras que en la asignación a
constantes o a variables utilizamos el dos-puntos-igual (:=). Esta sería la forma de definir ficheros
pero no entraremos en detalle porque aquí no los vamos a utilizar. El VHDL es un lenguaje
fuertemente tipado, cada objeto debe pertenecer a un único tipo de datos y solo puede recibir y
contener valores de dichos tipos de datos. Asimismo todos los objetos tienen un valor inicial
predeterminado que corresponde al menor valor del tipo de datos al que pertenece cada objeto.
Dicho valor inicial puede modificarse en el momento de de declarar el objeto como hemos visto
hace un momento. En este árbol podéis ver los tipos de datos con los que trabaja el VHDL. Aunque
nosotros en este curso trabajaremos solo con un subconjunto de ellos, los integer, boolean, bit,
character, bit_vector, string, real y time. Todo tipo de datos define un conjunto de valores fijos y
estáticos que no cambiarán a lo largo del tiempo. Además de los tipos de datos predefinidos por el
propio lenguaje, el usuario puede definir otros tipos de datos: enumerados, sub-rangos de los
previamente definidos, tipos compuestos, tipos récord, etcétera. Estos tipos de datos vienen
predefinidos en packages que son unidades de diseño del VHDL, para facilitar la reutilización del
código allí definido en otros módulos o unidades de diseño del VHDL. Y aquí tenemos los tipos de
datos predefinidos por el lenguaje en el paquete "standard package". Vemos tipos de datos
boolean,
tipo bit, el tipo character, los integer, real, y así sucesivamente. Estos son los tipos de datos por
defecto del lenguaje VHDL. Dentro de los llamados tipos de datos físicos, tenemos como
predefinido el tipo time, que en este caso tiene una relevancia especial porque es el tipo de datos
que nos permitirá expresar y manejar valores temporales, y manejar el tiempo dentro de nuestros
modelos y de nuestras simulaciones. Esta es la forma de definir este tipo. La unidad mínima de
tiempo es femtosegundo, y a continuación se van definiendo las unidades superiores picosegundo,
nanosegundo, microsegundo, y así sucesivamente hasta la hora, siempre en función de la unidad
inmediata anterior. A partir de estos tipos de datos básicos, el usuario diseñador podrá definir sus
propios tipos de datos, así como operaciones o funciones entre todos ellos. Veamos ahora los
operadores y expresiones que podemos utilizar en VHDL para describir ya sea funciones,
ecuaciones, o condiciones. Los operadores son símbolos que identifican operaciones específicas.
Los tipos de operaciones más habituales son las relacionales, lógicas, aritméticas o de
concatenación, con los operadores que aquí se muestran para cada uno de estos tipos de
operaciones. Los operadores y funciones junto con los literales y objetos, constantes, variables o
señales, se combinan entre sí para dar lugar a distintos tipos de expresiones como son las
siguientes, de tipo aritmético como ésta que tenemos aquí donde aparece una función sqrt y
distintos operadores aritméticos; de tipo relacional donde se establecen comparaciones cuyo
resultado será de tipo booleano "true" o "false" (verdad o falso) U operadores de tipo lógico, o
bien de concatenación de cadenas de bits o cadenas de caracteres. Las expresiones se pueden
utilizar en cláusulas condicionales para evaluar una determinada condición y obrar en
consecuencia; o a la derecha de una o a la derecha de una asignación a constante, variable o señal
como vemos aquí.
El lenguaje VHDL se estructura en unidades de diseño. Hay cinco unidades de diseño que son la
entidad, la arquitectura, el package declaration, el package body y la configuration. De todas ellas,
en esta breve introducción, solo trataremos con tres, que serán la entidad, la arquitectura y el
package. La entity o entidad identifica un módulo definiendo su nombre y el conjunto de señales o
puertos de entrada salida del mismo, pero sin dar ningún tipo de información sobre su
funcionalidad. La arquitectura siempre está asociada a una entidad, tiene acceso a los puertos de
entrada y salida de dicha entidad y describe o modela la funcionalidad que esta arquitectura asocia
a la citada entidad. Por lo tanto en la arquitectura sí que definimos la función de dicho bloque.
Puede haber más de una arquitectura para una misma entidad pero todas comparten el mismo
conjunto de puertos de entrada/salida declarados en la entidad. En general todas las arquitecturas
de una misma entidad comparten todas las declaraciones realizadas en dicha entidad. En la
mayoría de ejemplos que usaremos en este curso tendrán una única arquitectura para cada
entidad. Pero a veces puede ser útil describir distintas soluciones arquitecturales para una misma
entidad o arquitecturas de distintos niveles de abstracción para una misma entidad. La tercera
unidad de diseño que veremos es el package o paquete que contiene declaraciones y definiciones
de tipos de datos, objetos y funciones para facilitar su uso compartido en distintos códigos VHDL.
En la parte declarativa del paquete se declaran los tipos de datos, objetos y funciones mientras que
en el cuerpo del paquete, en el package body, se definen, por ejemplo, los valores de las
constantes y se describen las funciones, luego el package body siempre va asociado a un package
declaration. En este curso apenas utilizaremos el package body pues todo lo que necesitamos
declarar y definir lo podemos hacer en el package declaration. Veamos con un poco más de detalle
estas unidades de diseño. La entidad es como una caja negra que solo define la interfaz de cada
módulo con el exterior. Es decir su nombre y las entradas/salidas del mismo, los puertos de
entrada/salida, mientras que no proporciona ninguna información sobre su comportamiento o
funcionalidad. En la sintaxis de esta unidad de diseño podemos distinguir el nombre o identificador
de la entidad, los parámetros genéricos en base a los cuales podemos tener definidos distintos
aspectos de nuestro módulo, por ejemplo, el tamaño de los buses o de los puertos de
entrada/salida. Y que nos permitirán personalizar e inyectar valores distintos cada vez que usemos
o instanciemos el módulo utilizando estos parámetros genéricos. Los puertos de entrada/salida
que definen la interface del módulo con
su entorno y declaraciones globales y sentencias pasivas que no serán utilizadas en este curso.
Aquí tenemos un ejemplo que define la entidad correspondiente al módulo de la figura de abajo a
la derecha. El nombre de la entidad es MUX21, tiene tres entradas, A, B y Ctrl de tipo bit y una
salida Z de tipo bit, y de momento de esta entidad no sabemos nada sobre su funcionalidad. Si
ahora observamos esta otra entidad vemos que hemos utilizado un parámetro genérico para poder
personalizar el número de bits de los buses de entrada/salida, de los puertos de entrada/salida.
Primero definimos un parámetro genérico N con un valor entero al que damos como valor por
defecto el número 8. Y a continuación utilizamos dicho parámetro "n" para definir el tamaño de los
puertos de entrada/salida A, B y Z. De esta forma cada vez que instanciemos al componente
MUX21n podremos hacerlo con un valor de "n" distinto, esto es con un número de bits distinto
para los buses de entrada/salida y así podremos utilizar este componente en distintos contextos
sin necesidad de retocar su descripción. Ahora que hemos visto cómo una entity define una caja
negra con entradas y salidas veamos cómo se define la función que esta entity contiene o realiza.
Esto lo hacemos con una unidad de diseño llamada arquitectura, architecture, cuya sintaxis
podemos ver aquí donde hay un identificador que es el nombre de la arquitectura, otro apuntador
a la entidad de la que depende esta arquitectura. Y a continuación viene la zona de declaraciones
locales de esta arquitectura de tipos de datos constantes, variables, señales o componentes
propios de esta arquitectura. Y después entre el "begin" y el "end" es donde viene la descripción
propiamente dicha de esta arquitectura que se hace mediante sentencias concurrentes, entre las
que destacamos asignaciones concurrentes, instancias de componentes y procesos. Los bloques
tampoco los veremos en este curso. Recuperemos la entidad MUX21 y vamos a definir una
arquitectura que defina la función de esta entidad. La arquitectura se llama "functional", como
tenemos aquí, y apunta a la entidad MUX21. Dentro de la arquitectura entre el "begin" y el "end"
es donde está descrita esta arquitectura y vemos que hay un único proceso que utiliza las señales
A, B, Ctrl y Z que se habían definido como puertos de nuestra entidad. Dentro del proceso hay una
construcción simple: if_then_else_endif en donde si la señal "Ctrl = 0" la salida Z tomará el valor de
A y si no la salida Z tomará el valor de B. Este comportamiento corresponde al de un multiplexor de
entrada de control Ctrl con dos entradas de datos A y B y una salida de datos Z, que es lo que
tenemos dibujado aquí abajo. Si ahora analizamos la otra entidad que hemos definido con un
parámetro genérico MUX21n, el parámetro genérico N, vemos que dicha entidad es muy distinta
de la anterior al intervenir dicho parámetro en el dimensionado de los buses de entrada/salida. Sin
embargo si nos fijamos en su arquitectura observamos que la única e importante diferencia es que
esta arquitectura apunta a MUX21n, en lugar de apuntar al MUX21 del ejemplo anterior. El resto
de esta arquitectura es exactamente idéntica a la arquitectura del primer caso, por lo tanto
también hemos definido un multiplexor pero ahora las entradas/salidas no son señales de un bit
sino buses de "n" bits. Las dos arquitecturas que hemos visto hasta ahora nos describían el
multiplexor a nivel funcional, pero el VHDL nos permite realizar descripciones a otros niveles de
abstracción como por ejemplo, una arquitectura de tipo estructural basada en puertas lógicas que
responderá a este esquema de aquí. En este caso la arquitectura se basa en puertas lógicas, que
definimos como componentes en la parte de declaraciones de esta arquitectura. Después en la
zona de descripción propiamente dicha de la arquitectura realizamos las instancias convenientes a
estos componentes y les conectamos sus puertos de entrada salida a las señales que también
habíamos definido en esta arquitectura. En este caso todas estas instancias son también sentencias
concurrentes y pueden intercambiar su orden sin alterar la funcionalidad. Los nombres U0, U1, U2
y U3 son simples etiquetas para identificar cada una de estas instancias. Hemos visto dos
arquitecturas de una misma entidad con niveles de abstracción muy distintos, funcional y
estructural. Pero entre ambos hay otras posibles descripciones intermedias como la que responde
a esta figura donde vemos distintas funciones lógicas, En forma de grafo que va respondiendo al
flujo de datos que les llega por sus entradas primarias. Esta arquitectura podría describirse tal
como vemos aquí, en una arquitectura que hemos llamado "DataFlow", del mismo MUX21, dentro
de la cual hemos definido una serie de señales propias suyas. Y a continuación en la zona de
descripción arquitectural ponemos cuatro asignaciones concurrentes, a señal que modelan el grafo
de funciones lógicas que habíamos visto aquí arriba. Estas asignaciones son sentencias
concurrentes y no importa en qué orden se realicen. Estas dos ordenaciones son 100%
equivalentes. Hemos visto pues una única entidad con varias arquitecturas diferentes que
responden a descripciones equivalentes, pero de distintos niveles de abstracción. Teniendo todas
ellas en común los puertos de entrada o salida primarios definidos en la entidad. Abordamos ahora
la última unidad de diseño que veremos, el paquete o package. Esta entidad es una forma de
empaquetar definiciones de tipos de datos, objetos, funciones y procedimientos para
facilitar su reutilización en distintos códigos VHDL. De hecho son dos unidades, el package
declaration y el package body con la sintaxis siguiente. Aquí tenemos el package declaration que
tiene un nombre del paquete y una serie de declaraciones. Por su parte el package body también
tiene un nombre del paquete que coincidirá con el nombre del package declaration
correspondiente y contendrá la definición de los valores asociados a las constantes o la descripción
de las funciones y procedimientos o bien otras informaciones. Para utilizar dentro de un
determinado código el contenido de un package utilizaremos esta sentencia use, que tenemos aquí
abajo al principio de dicho código. El usuario puede también definir sus propios package y a su vez
utilizar unos estándares que dentro de los sistemas digitales son los paquetes más habituales. El
STANDARD y el TEXTIO son los paquetes por defecto del propio lenguaje VHDL. El
Standard_logic_1164 define un conjunto de valores y una serie de funciones y el
Standard_logic_arithmetic se basa en el anterior y define una serie de operaciones aritméticas. A
partir de estos paquetes el diseñador puede definir sus propios paquetes que le ayudaran a
simplificar los trabajos de modelado del VHDL y reutilización de código para permitir el desarrollo
colaborativo de proyectos. Aquí tenemos un ejemplo de un paquete definido por el diseñador y
que corresponde a un paquete que utilizaremos en el desarrollo en VHDL del procesador sencillo
asociado a este curso de sistemas digitales. Antes de iniciar la descripción del paquete indicamos
qué paquetes estándar del IEEE queremos utilizar. Y a continuación empieza la declaración del
paquete que hemos llamado main_parameters, que hemos definido como paquete de usuario.
Como vemos este paquete utiliza tipos de datos y funciones que están definidos en los paquetes
previamente identificados del IEEE y vemos que para cada objeto tenemos indicado el tipo de
objeto, su identificador, el nombre, el tipo de datos al que pertenece y el valor asociado a esa
constante. Por último comentar que el símbolo de los dos guiones medios indica comentario,
desde este símbolo hasta el final de la línea se considera comentario. En este caso no necesitamos
package body puesto que tanto la declaración de constantes como la definición de sus valores se
ha realizado en el propio package declaration. Por último un breve comentario en relación con
algunos de los paquetes estándar del IEEE. Aquí tenemos el paquete Standard 1164 que define una
lógica multivaluada de hasta 9 valores lógicos distintos y no solo el "0" y "1",
tiene el valor "U" como no inicializado, el "X" como desconocido fuerte, el "0" como valor lógico
"0" fuerte, el "1" como valor lógico 1 fuerte, la "Z" es alta impedancia o desconectado, la "W"
desconocido débil, la "L" es el "0" débil, la "H" el "1" débil. Y el guión medio "-" es el término
redundante o indistinto, significa que donde está ese guión puede tomar cualquiera de los otros
valores indistintamente. Un comentario es que todos estos valores de esta lógica multivaluada son
necesarios para poder simular los circuitos digitales con una mayor precisión, más allá del 0 y 1
binarios puros y así poder manejar las distintas situaciones en las que se puede encontrar una
señal digital dentro de una simulación. Este paquete también tiene algunos subtipos más sencillos,
tiene algunas operaciones aritmético lógicas definidas contra estos tipos de datos y después
también contiene algunas funciones de conversión de datos de integer a lógica multivaluada o al
revés. También tenemos otro paquete estándar importante que es el standard arith que define
operaciones y funciones más complejas de las que teníamos aquí arriba, pero también para el
Std._1164 y también funciones de conversión un poco más elaboradas. Hay otros paquetes como
son los Standard_logic_signed y unsigned, en los que se definen funciones para facilitar el trabajo
con números con signo y números sin signo. Todos estos paquetes estan definidos y estandarizados
por el IEEE son reconocidos y utilizados de forma óptima por las herramientas de modelado,
simulación y síntesis, lo que facilita los procesos correspondientes cuando los modelos a tratar
utilizan estos paquetes. Por lo tanto estos paquetes estándar del IEEE son de uso muy
recomendado y recomendable. Bueno y como resumen de esta primera sesión de VHDL hemos
presentado el concepto y los objetivos principales de los lenguajes de descripción de hardware que
son el modelado, la simulación y la síntesis de hardware. Hemos introducido el léxico y la sintaxis
del VHDL abordando los objetos de lenguaje: constantes, variables y señales, los tipos de datos de
tales objetos y los operadores y expresiones del lenguaje. También hemos visto cómo se estructura
el lenguaje en unidades de diseño básicamente entidad, arquitectura y paquete, y todo ello se ha
ilustrado con pequeños ejemplos de las distintas unidades de diseño. Y esto es todo por hoy,
saludos y hasta la próxima. [AUDIO_EN_BLANCO]

[MÚSICA] Hola de nuevo. En esta sesión abordaremos una selección de las principales sentencias
del lenguaje VHDL y empezaremos por las sentencias secuenciales. Antes de empezar haremos
algunos comentarios en torno al comportamiento secuencial versus el concurrente. En el mundo
hardware las puertas y dispositivos físicos funcionan de forma concurrente a lo largo del tiempo,
mientras que los lenguajes software tradicionales, los programas, son descripciones funcionales
secuenciales que se materializan ejecutando una sentencia después de otra. En el caso del VHDL
conviven ambos mundos, el concurrente y el secuencial, donde las descripciones se realizan con
sentencias diferenciales, sentencias secuenciales y sentencias concurrentes. Las sentencias
secuenciales son sentencias similares a los lenguajes de software del tipo if, case, loop, exit, return,
y se interpretan secuencialmente, es decir, el orden de ejecución es importante para los resultados
obtenidos. Estas solo se usan en funciones, procedures, y processes, los processes serán uno de los
bloques más importantes de este lenguaje. Las sentencias concurrentes están orientadas a
describir o modelar hardware donde componentes, bloques y procesos trabajan simultáneamente
a lo largo del tiempo. Sentencias seleccionadas: los procesos, asignaciones a señal e instanciación a
componentes serán las más importantes. Estas sentencias solo aparecen en la unidad de diseño
llamada arquitectura. Y algunas sentencias secuenciales tienen su equivalente en las concurrentes.
La simulación del VHDL se basa únicamente en procesos, y el process es una sentencia
concurrente. Para ello cada sentencia concurrente se convierte a su proceso equivalente para
simular todas las sentencias concurrentes se convierten a sus procesos equivalentes, y la
simulación dirigida por eventos del VHDL solo gestiona un conjunto de procesos concurrentes.
Veamos también cuál es la estructura general de un modelo VHDL antes de ir al detalle de las
sentencias. Como ya hemos dicho, todo módulo VHDL está definido por una entity o entidad,
donde fundamentalmente se define la interface con su entorno, principalmente los puertos de
entrada/salida. Toda entidad tendrá al menos una arquitectura asociada donde se definirá la
funcionalidad del módulo. Dicha funcionalidad se describe entre el begin y el end, y en esta parte
solo pueden aparecer sentencias concurrentes. Así por ejemplo en el cuadro de la derecha
podemos ver sentencias de asignación concurrente a señal o diversos procesos que son
concurrentes entre sí. El orden de escritura de todas las sentencias de una arquitectura es
irrelevante, pues todas son concurrentes y como tales se simulan. En cambio dentro de los
procesos, se aglutinan sentencias secuenciales y estas sí que se ejecutan o simulan según el orden
en que se han escrito. Luego aquí el orden es importante. Por lo tanto diremos que en una
arquitectura se describe de forma concurrente, modelos de hardware concurrente, mientras que
en un proceso se describe mediante una secuencia de instrucciones y de forma más algorítmica
una determinada funcionalidad. Así pues el proceso como tal es una sentencia concurrente que
aglutina código secuencial que define la función de dicho proceso. Después de estas aclaraciones
previas, veamos algunas sentencias secuenciales que pueden aparecer en procesos, funciones y
procedimientos. En este curso nos centraremos en los procesos, aunque haremos uso de alguna
función predefinida en los paquetes estándar. Las principales sentencias secuenciales que
detallaremos a continuación serán las asignaciones a variable y señal, la sentencia wait, el if then
else, el case, el loop, y por último muy brevemente funciones, procedimientos y la sentencia
assert. La asignación a variable es exactamente igual a una asignación a variable en software, es
decir el valor asignado se instala inmediatamente como contenido de la variable en cuestión. Aquí
tenemos algunos ejemplos de variables que reciben o valores literales o resultados de expresiones
aritméticas o expresiones en las que interviene alguna función. Veamos ahora la asignación a
señal. En este caso, en lugar de producir un cambio inmediato en el valor de la señal, lo que se
hace es proyectar a futuro un nuevo evento en el driver asociado a cada señal. Pero, ¿qué es un
evento? Un evento es una pareja de datos tiempo-valor cuyo significado es que en el tiempo ti
indicado la señal toma el valor vi asociado. ¿Y qué es el driver de una señal? A diferencia de las
variables, que solo son posiciones de memoria con un nombre concreto, que cambian de valor de
forma inmediata cuando hay una asignación y lo conservan hasta que les afecta otra asignación,
una señal requiere de una estructura de memoria más compleja, pues debe ser capaz de
almacenar
distintos valores de futuro. Por lo tanto valores que irá asumiendo en distintos instantes de tiempo.
Visto así, el driver de una señal es una cola de eventos pares tiempo-valor, como vemos aquí. Si
observamos la sintaxis de una asignación a señal, vemos que estas llevan asociada una cláusula
temporal, un after, que indica en qué instante de tiempo o con qué retardo ese nuevo valor debe
aparecer en la señal, luego con qué retardo dicho valor debe figurar en el driver de la señal. Si no
se indica retardo alguno, se supone que es con retardo "0", y en ese caso el nuevo valor se
instalaría al principio de la cola de eventos del driver. Pero seguiría siendo un valor proyectado a
futuro, aunque este sea inmediato. Este tipo de respuesta ideal con retardo "0" en VHDL se conoce
como delta-delay o retardo-delta. En VHDL tienes dos modelos de retardo, el transporte y el
inercial, que no vamos a analizar en detalle pero que definiremos diciendo que el retardo inercial
es el retardo asociado a una asignación y se considera el retardo tal que si sobre la señal asignada
se producen pulsos inferiores o iguales a dicho retardo, estos se filtran y no se proyectan sobre la
señal asignada. El otro modelo de retardo es el transport y en este caso, cualquier pulso que se
envíe a una señal
con este modelo, se transfiere a la misma con el retardo indicado pero sin filtrar los pulsos
inferiores al mismo. En este ejemplo de aquí observamos en primer lugar que cargamos el driver
de la señal con esta sentencia, y a continuación realizamos asignaciones de la señal A con distintos
modelos de tiempo sobre las señales B1, B2 y B3. En general utilizaremos el retardo inicial o bien el
retardo inercial con pulso mínimo, que corresponden a los casos B2 y B3 de este ejemplo. En el
caso B1 es un retardo con modelo transport y aquí dejaremos pasar a la salida B1 cualquier cambio
en la señal A. En el B2 vemos que el retardo es un retardo inicial y se considera que el tiempo
mínimo que tiene que estar estable el valor que se asigna es el correspondiente al retardo de la
cláusula after, que hemos dicho que eran 10 nanosegundos. Y por ello en B2 se eliminan los pulsos
de entrada inferiores a 10 nanosegundos. Esto es lo que vemos en la señal B2. En cambio en B3,
tenemos un retardo inercial con pulso mínimo de 5 nanosegundos y el retardo de 10, y es por ello
que en B3 solo se eliminan aquellos pulsos que son iguales o inferiores a 5, este sería este caso de
aquí. La sentencia wait es muy importante para sincronizar el funcionamiento de los procesos
concurrentes en VHDL. Y ya sabemos que todas las sentencias concurrentes se convierten a sus
procesos equivalentes antes
de proceder a su simulación. Luego cuando se simula solo se simulan procesos concurrentes. Esta
sentencia indica el punto donde el proceso en cuestión debe suspenderse o detenerse así como las
condiciones para su reactivación. En un proceso puede haber más de una sentencia wait. Y aquí
tenemos la sintaxis de esta sentencia pero veamos directamente algunos ejemplos sobre sus
distintas posibilidades. El wait sin ninguna otra explicación es un wait sin condición de reactivación.
En este caso el proceso se suspende indefinidamente. El wait on a, b, el proceso se detiene al llegar
a este wait y se reactivará si y solo si hay algún evento en las señales a o b. El wait for 10
nanosegundos, una vez detenido el proceso en este wait, se dejan transcurrir 10 nanosegundos y
entonces el proceso se reactiva y sigue en secuencia. Y por último el wait until clock igual a 1, este
wait detiene el proceso y se queda sensible a evento en la señal clock y que se cumpla una
condición,
que clock sea 1. De hecho que haya un evento en la señal clock y que clock sea 1, es equivalente a
decir que hemos detectado un flanco de subida en la citada señal clock. Por lo tanto esta
estructura
es una de las típicas para identificar flancos de subida en una determinada señal. [AUDIO EN
BLANCO]
Ahora que hemos visto la señal "wait", hagamos un pequeño inciso sobre la simulación dirigida por
eventos del VHDL que nos ayudará a entender la importancia de esta sentencia para facilitar la
comunicación de procesos a través de señales y a partir de ello el funcionamiento del ciclo de
simulación del VHDL. Una simulación dirigida por eventos significa que la simulación de procesos
solo se realiza en aquellos instantes en los que se ha producido algún evento en alguna señal y en
ese caso solo se evalúan aquellos procesos que se ven activados por los eventos de dicho instante.
El tiempo transcurrido entre dos eventos consecutivos no generará ninguna tarea de simulación. Si
vemos el ciclo de simulación del VHDL, observamos que tiene 3 fases, primero inicializamos las
señales, luego se van sucediendo las fases de ejecución de procesos y actualización de señales. Se
vuelven a ejecutar procesos, se vuelven a actualizar señales y así hasta que se completa la
simulación. Si lo vemos con un poco más de detalle, en primer lugar se inicializan las señales de
todos los drivers, luego la primera vez se ejecutan o se evalúan todos los procesos obtenidos a
partir de un modelo bajo simulación, estos procesos proyectarán eventos a futuro sobre distintas
señales. Y cuando todos los procesos en ejecución se han detenido, han llegado a una sentencia
wait, y están todos detenidos, pasamos a la fase de actualización de drivers y de señales. Una vez
actualizados los drivers de las señales hay que ver cuál es el instante de tiempo más cercano en el
que hay algún evento y avanzamos el tiempo de simulación hasta dicho instante. Entonces, se
vuelve a la ejecución de procesos pero ahora reactivando solo aquellos que en este instante de
tiempo, hay algún evento que los despierta o los reactiva. Y vamos repitiendo este bucle de
ejecución y actualización hasta que no queden eventos en ninguna cola, de ningún driver o bien se
haya finalizado el tiempo previsto de simulación. En el diagrama de la derecha podemos ver un
ejemplo de evolución de estas simulaciones dirigidas por eventos. Hay eventos en los tiempos 0, 1,
3, 4 y 5. Y por lo tanto en estos instantes es cuando se ejecutan los procesos que se hayan activado
debido a los correspondientes eventos. Entre los tiempos 1 y 3 no hay eventos y por lo tanto no se
evalúan procesos. Cuando en una fase de actualización de drivers y de tiempos se detecta que ha
habido uno o más eventos nuevos con retardo "0" o retardo-delta, se volverán a evaluar los
procesos afectados por tales eventos pero sin avanzar el tiempo de simulación. Esto es lo que se
llama ciclo delta de simulación. Y serían estas marcas que tenemos en los distintos instantes de
tiempo de simulación. Los retardos delta consumen ciclos de simulación, pero no hacen avanzar el
tiempo de simulación. Después de este inciso sobre la simulación dirigida por eventos del VHDL,
volvemos a las sentencias secuenciales. La siguiente sentencia es la if_then_else_endif, con esta
sintaxis, que puede tener ifs anidados y que puede estar incompletamente especificada, es decir
tener sentencias if_then pero sin necesidad de else. Vamos a ver algunos ejemplos para entender
cómo funciona. Este esquema de abajo a la izquierda ya lo conocíamos y modela la funcionalidad
de un multiplexor a través de
esta sentencia if_then_else, en este caso completamente especificada. El buffer triestate en este
caso se parece a un multiplexor, pero cuando no se cumple la condición, en este caso de aquí, lo
que asignamos a la salida es "Z", es decir un valor de alta impedancia, que es equivalente a tener la
salida desconectada. Luego esto es un buffer cuya salida puede tener 3 estados distintos, el valor
de entrada, que podría ser "0" o "1", y el de "Z" o alta impedancia. El siguiente ejemplo es un latch,
en este caso tenemos un if incompletamente especificado, es un if_then, pero no está la cláusula
else. ¿Cómo se interpreta este código? Pues si se cumple la condición de load="1", entonces la
entrada D se envía a la salida Q. Pero si no se cumple la condición, no se especifica qué es lo que
hay que hacer y por lo tanto se interpreta que debemos guardar el último valor que había en Q.
Más adelante veremos que este código describe el funcionamiento de un elemento de memoria al
que llamaremos latch. En este caso vemos un if anidado, if_then_else_if_then, y este último if,
if_then incompletamente especificado. Si se cumple la primera condición, la señal Q toma el valor
"0", pero si no se cumple, entonces abrimos un nuevo if con una extraña condición que es
(clk'even y clk="1"), que significa que se ha producido un evento en la señal clk, y clk ha tomado el
valor 1. Ello corresponde a una detección de flanco de subida o positivo de la señal clk, y cuando se
da esa condición se debe poner el valor de D en Q. Pero este if está incompletamente especificado,
es decir no hay parte else, y por lo tanto cuando no se cumpla la condición de flanco de subida de
clk, habrá que guardar en Q el último valor que le habíamos enviado. Esto es un elemento de
memoria que
cambia por flanco activo de la señal clk y como el caso anterior, más adelante veremos que
estamos describiendo el funcionamiento de un elemento de memoria al que llamaremos flip flop,
y estas son algunas de las posibilidades de la sentencia if_then_else. La sentencia secuencial
siguiente es la case. Con la sintaxis que aquí se muestra, y donde vemos que los distintos casos de
la condición se pueden expresar no solo como valores explícitos sino también como valores
alternativos, como valores dentro de un rango de valores, o como el resto de valores no tratados
hasta ese momento. Cuando un case no está completamente especificado y no tiene la cláusula
"when others", significa que para todos los casos no especificados se deben mantener los valores
asociados anteriormente
a las variables o señales afectadas. Luego esto dará lugar a algún elemento de memoria tipo latch.
Y aquí tenemos algunos ejemplos muy sencillos de la sentencia case. En base a una serie de
definiciones previas de tipos de datos y señales, tenemos un caso de uso de la sentencia case. Y en
esta parte de aquí tenemos otro ejemplo basado en señales y variables de tipo entero. Y vemos
que hablamos de valores explícitos, valores alternativos, valores dentro de un rango o del resto de
valores. Veamos ahora un ejemplo un poco más elaborado y combinando estas dos sentencias, if y
case. Queremos modelar en VHDL el esquema LatMux de la figura, y para ello lo que vamos a
hacer es declaramos una entidad y una arquitectura. En la entidad vemos que hemos definido las
entradas/salidas de la caja grande, de la caja LatMux, y a continuación definimos una arquitectura
que hemos llamado TwoProc de LatMux, donde declaramos una señal X que nos servirá en esta
arquitectura para conectar la salida del multiplexor con la entrada del latch. Y ya dentro de la
arquitectura entre el begin y el end, vemos que hay dos procesos para modelar dos sub-bloques. El
primer proceso modela el multiplexor con una sentencia case, Y el segundo proceso, modela el
Latch con una sentencia if incompletamente especificada, que como ya hemos visto era una de las
formas de modelar un Latch. Y estos dos procesos son procesos concurrentes entre sí, aunque
cada uno de ellos aglutina sentencias secuenciales. Veamos ahora un pequeño ejercicio. Se trata
de describir una nueva arquitectura para este mismo módulo y con la misma entidad del ejemplo
anterior, pero con un único proceso, ¿lo podríais intentar? La idea será fusionar los dos bloques
anteriores multiplexor y Latch en un solo proceso y aquí lo tenemos. Un único proceso que
modelará la funcionalidad completa. Dentro del proceso empezamos con la estructura if
incompletamente especificada. Solo tiene la cláusula then pero no tiene la else, que modelará
Latch. Y dentro de este if, cuando se cumple la condición de load = "0", en la parte then hemos
incluido el case que modela el multiplexor. De esta manera hemos encapsulado de forma
algorítmica, y en un solo proceso la funcionalidad completa del módulo LatMux. Una nueva
sentencia secuencial, el loop o bucle. Esta sentencia encierra un conjunto de sentencias
secuenciales que se repiten según el tipo y condiciones de dicho loop. Hay tres tipos de bucles o
loops, como vemos en la sintaxis de esta sentencia: el controlado por una condición "mientras se
cumpla la condición", el controlado por un índice de repetición, un control de repeticiones o bien
el loop infinito. Si no hay, si no tiene sentencia while ni for, este loop es un loop infinito. Como
ejemplo, veamos un sumador de n bits. Aquí tenemos la parte de la entidad de "n" bits que se
recorren desde el bit "0" hasta el bit "n-1" mediante esta sentencia for. Como ya conocéis el
sumador podéis comprobar que este algoritmo o esta descripción modela ciertamente
un sumador de "n" bits. Existen sentencias para romper la secuencia normal de iteración de un
bucle. Son la "exit" y la "next". La "exit" finaliza la ejecución del bucle cuando se cumple una
determinada condición y la "next" no finaliza la ejecución del bucle, sino que salta a la siguiente
iteración de ese bucle. Y por último veamos las funciones y procedimientos que siguen las mismas
pautas que sus equivalentes en software. Las funciones operan con parámetros de entrada y
retornan un valor a través de la sentencia "return", y habitualmente las funciones se definen en los
packages, tal como ya indicamos al hablar de dicha unidad de diseño. Y aquí vemos algunos
ejemplos de declaración de funciones y uso de funciones. Los procedimientos, al igual que sus
homónimos de software, operan con parámetros de entrada y retornan valores a través de sus
parámetros de salida, y aquí tenemos algunos ejemplos también de declaración y uso de estos
procedimientos. Y por último vemos la sentencia "Assert", que sirve para comprobar situaciones
que podemos identificar en forma de condiciones. Esta es la sintaxis cuando la expresión booleana
es "false", entonces se imprime el string de caracteres y se indica el tipo problema que hemos
detectado, que puede ser solo una anotación, un warning, una atención, un error o un error grave.
Por lo contrario, si la expresión booleana es "true", no se hace nada. Y aquí vemos algunos
ejemplos muy sencillos e intuitivos de cómo se puede utilizar esta sentencia "Assert". Por último el
"Report" es una simplificación del "Assert" y reporta un mensaje y se realizan las acciones previstas
según el nivel de "severity" que se haya indicado. Es como una sentencia "Assert", pero con la
expresión booleana fija a "false". Bueno, y como resumen de esta sesión, diremos que hemos
identificado las principales características y diferencias entre el mundo secuencial y el concurrente
entre en VHDL. Hemos detallado un subconjunto de todas las sentencias secuenciales disponibles
en este lenguaje y también hemos presentado cómo se gestiona el tiempo en VHDL, introduciendo
a su vez los elementos fundamentales de la simulación temporal dirigida por eventos en la que se
basa este lenguaje, y todo ello aderezado con ejemplos sencillos para facilitar su comprensión. Esto
es todo por hoy. Saludos y hasta la próxima. [AUDIO EN BLANCO]
[MÚSICA] En esta sesión vamos a seguir con las sentencias básicas del lenguaje VHDL abordando
en este caso las sentencias concurrentes de este lenguaje. Recordemos en primer lugar la
estructura básica del modelo en VHDL donde ya habíamos identificado que la principal unidad de
diseño donde solo aparecen sentencias concurrentes son las arquitecturas. Recordemos también
que una de las
sentencias concurrentes más importantes es el process que aglutina descripciones estrictamente
secuenciales basadas en las sentencias secuenciales que ya vimos en la sesión anterior. Las
sentencias concurrentes como su nombre indica se deben evaluar simultáneamente. Y por lo tanto
el orden en que se escriben dentro del módulo que las aloja es totalmente irrelevante. Dicho de
otra forma, cualquiera que sea el orden de un mismo conjunto de sentencias concurrentes, estas
deben acabar produciendo el mismo resultado. ¿Dónde pueden utilizarse dentro de una
descripción VHDL? Pues las sentencias concurrentes pueden utilizarse en distintas partes del VHDL;
en las entities que es la parte de las sentencias pasivas, en los blocks que son grupos de sentencias
concurrentes o en las arquitecturas. Todas las sentencias que hay entre el "begin" y el "end" son
concurrentes. Normalmente en esta introducción al lenguaje solo veremos ejemplos de uso de
sentencias concurrentes dentro de las arquitecturas. De todas las sentencias concurrentes del
VHDL en esta introducción solo analizaremos una selección de ellas. El "process" que es una
descripción mediante sentencias secuenciales de una determinada funcionalidad, la "asignación a
señal" que puede ser asignación directa de un valor o el resultado de una expresión a una señal,
una "asignación condiciona"l en función del resultado de una determinada condición se asigna uno
u otro valor a una señal, o bien "asignación con selección" el valor a asignar se selecciona según el
resultado de una expresión, o el valor de una señal o de una variable. Otras sentencias importantes
son los "componentes" para facilitar el diseño jerárquico mediante el uso de módulos, entity o
arquitectura,
predefinidos en VHDL y disponibles en alguna biblioteca del propio usuario, y el "generate" que
sirve para generar de forma sencilla estructuras repetitivas dentro del código. Como paso previo a
cualquier elaboración posterior para la simulación todas y cada una de las sentencias concurrentes
se convierten a su proceso secuencial equivalente. Luego cuando simulamos un código VHDL, solo
se simulan procesos concurrentes entre sí. La primera sentencia concurrente que veremos es
precisamente el "process" o proceso que como ya hemos visto aglutina sentencias secuenciales
que definen una determinada funcionalidad. Los procesos se comunican entre sí mediante señales
y son sensibles a eventos o condiciones en dichas señales que les pueden reactivar cuando están
parados. La lista de sensibilidad del principio de un proceso muestra un conjunto de señales cuyos
cambios reactivarán la ejecución o evaluación de dicho proceso. Un proceso es un bucle infinito
que debe contener al menos una condición de paro o espera, esto es una sentencia wait o una lista
de sensibilidad. Como podemos ver la equivalencia entre los dos procesos que hay al pie de esta
transparencia, uno de ellos con lista de sensibilidad al principio, y el mismo proceso con un "wait
on" lista de sensibilidad al final. Pues estas dos formas de procesos son completamente
equivalentes. Asimismo observamos el concepto de bucle infinito que solo se detiene por la lista
de
sensibilidad o su wait on equivalente. En cualquier parada del proceso, en cualquier wait se
pueden especificar los eventos, señales o condiciones que reactivarán la ejecución de dicho
proceso. Y los procesos pueden contener declaraciones locales, constantes, variables, señales, que
solo son visibles y útiles dentro del propio proceso. La sentencia de asignación concurrente a señal
tiene una sintaxis idéntica a su equivalente secuencial, pero ahora cuando tengamos una serie de
asignaciones a señal como estas, todas ellas son concurrentes, luego su orden no es importante
importante y se deben simular simultáneamente por parte del simulador. Asimismo cada una de
las asignaciones
concurrentes a señal dará lugar a su proceso equivalente antes de proceder a su simulación. Podéis
comprobar fácilmente que estas tres sentencias concurrentes modelan a un sumador completo de
2 bits de entrada más 1 bit de acarreo, y generan dos señales de salida, la suma y el acarreo de
salida. Y esta expresión que vemos aquí con distintos valores asignados a una señal en distintos
momentos de tiempo, es una manera de cargar una determinada forma de onda sobre una señal o
lo que es lo mismo, cargar el driver de dicha señal tal como vemos abajo a la derecha. Esta nueva
forma de asignación condicional a señal significa que el valor de asignar a una señal depende de
alguna condición. Veamos un par de ejemplos. En este caso de aquí, si "sel" tiene el valor "0",
entonces lo que asignamos a la señal S es el valor de la señal A. Mientras que si "sel" no tiene el
valor "0", lo que asignamos a S es el valor de la señal B. Esto en el ámbito secuencial sería una
estructura del tipo if then else, que de hecho es la forma en que esta sentencia concurrente de
asignación condicional a señal se traducirá a su proceso secuencial equivalente como vemos aquí.
En este segundo ejemplo, vemos una estructura condicional más compleja, que de hecho
responde a una serie de ifs anidados, y así es como se traduce a su proceso secuencial equivalente.
La palabra reservada "unnafected", significa que no hay que modificar el valor actual de la señal S
en cualquier caso distinto de los dos primeros que se habían evaluado. Cuando la señal de
selección valía "00"
y "11". El concepto de no modificar el valor de una señal normalmente significa guardar su valor
actual inalterado, y para ello se necesitará un elemento de memoria tipo latch. Si en lugar de este
"unnafected", hubiésemos puesto por ejemplo E3, en ese caso, en su proceso equivalente, en lugar
de la sentencia "null" también habríamos puesto la asignación "S asignación a señal E3". Otra
forma de asignación es la asignación selectiva a señal que es muy parecida a la anterior pero con
una estructura sintáctica que nos recuerda más a la sentencia selectiva o condicional de tipo
"case". En el ejemplo que vemos aquí, según el valor de la señal "operation" que es de tipo
"opcode" que tenemos definido aquí abajo, lo que se asigna a la señal "Result" es el valor
resultante de una u otra operación, y como ya habíamos intuido el proceso equivalente se arma
mediante una sentencia secuencial "case" como vemos aquí. El uso de componentes es la forma
más habitual de crear estructuras jerárquicas para definir bloques que se podrán utilizar o
referenciar en distintos códigos VHDL. Los componentes se declaran primero con su identificador,
sus puertos de entrada, salida y sus parámetros genéricos si los hubiera, es lo que mostramos en la
parte declarativa. Para su uso, los debemos instanciar o referenciar indicando el identificador y
asignándoles una etiqueta y asociando o conectando sus puertos de entrada, salida. y pasándole
valores a los parámetros genéricos si los tuviere. Múltiples referencias a un mismo componente, se
distinguen por su etiqueta y por las conexiones de sus puertos de entrada y salida. Aquí vemos un
ejemplo de todo ello, con la parte de declaración del componente aquí a la izquierda y dos formas
equivalentes de instanciación. Estas dos que tenemos aquí. En este caso los puertos se asocian
posicionalmente por posición y en este de aquí se asocian nominalmente asociando nombres. Las
descripciones de hardware basadas en componentes, reproducen listas de componentes
interconectados. Lo que se conoce como "netlist", que dan lugar a una determinada funcionalidad.
Veamos ahora un ejemplo sencillo de uso de componentes básicos para formar un módulo más
complejo. La arquitectura llamada "structural", de la entidad "FullAdder", se basa en el uso de dos
componentes, el "HalfAdder", un semi sumador cuya función se muestra en la tabla de verdad que
tenemos aquí, y en el "OrG", que es una puerta lógica de tipo "or". Ambos componentes los
declaramos en la zona declarativa de la arquitectura. Estos son los dos componentes, así com las
señales internas de esta arquitectura. En la descripción de la arquitectura entre el "begin" y el
"end", hemos puesto tres instancias concurrentes a componentes, dos de ellas al "HalfAdder" y
una a la puerta "OrG". Estableciendo una determinada conectividad entre ellos, que es la que se
muestra gráficamente en la figura de la derecha. Esta conectividad, da lugar a un sumador
completo de dos bits X e Y más un acarreo de entrada CIn para obtener un bit de resultado Sum y
otro de acarreo de salida Cout. Observemos que cuando se instancia un componente lo
conectamos con su entorno asociando señales a sus puertos de entrada/salida. Y todo ello, como
ya hemos dicho, lo podemos hacer de forma posicional o nominal. Otra sentencia es la "generate",
que sirve para aglutinar sentencias concurrentes que bajo distintos criterios, se repetirán dando
lugar a funciones hardware repetidas con las correspondientes conexiones. Como podemos ver en
la síntaxis de esta sentencia, se pueden utilizar cláusulas "for" y cláusulas "if", para especificar el
número de repeticiones en un caso y combinándolas con la cláusula "if" para establecer
condiciones sobre distintas repeticiones. En el ejemplo, definimos una entidad "register", cuya
arquitectura llamada "structural", se define en base a la repetición de instancias del componente
DFF. Estableciendo en cada repetición las conexiones adecuadas para construir, en este caso, un
registro de N bits de entrada/salida en paralelo, tal como el que muestra el esquemático que
tenemos aquí a la izquierda. Por último vemos tras sentencias concurrentes assert, llamada a
procedimiento y llamada a función, responden a la misma sintaxis que sus equivalentes
secuenciales, pero ahora utilizadas en un contexto concurrente. Las llamadas a funciones, se
pueden incluir en cualquier expresión que forme de una sentencia concurrente como
este ejemplo que tenemos aquí, donde la función llamada Check_Timing se utiliza para hacer una
comprobación temporal dentro de una sentencia assert, que aquí vemos como concurrente pero
que la misma forma podría aparecer en el contexto secuencial de un "process". Dado que estamos
en contexto concurrente, una observación importante es que una llamada a procedimiento es
equivalente a poner un proceso con los parámetros de un procedimiento puestos en forma de
señales de sensibilidad de dicho proceso. Veamos cómo modelar procesos mediante
procedimientos en el ámbito concurrente. En este ejemplo vemos como el contenido funcional del
"process" es el mismo que el de la "procedure". Pero los parámetros de la "procedure", deben
contener todas las señales de entrada y de salida del "process". En el caso del "process", si lo
definimos y usamos simultáneamente, tendremos que utilizar la técnica del cortar y pegar para
usos repetidos del mismo. Por su parte, en el caso del procedimiento o "procedure", estas
"procedures" se declaran en "packages" y se accede a dichos "packages" mediante el uso de la
sentencia "Use". Para utilizar los procedimientos o "procedures", se hace por referencia o
instanciación conectando las señales adecuadas como parámetros actuales. Para proceder a su
simulación, también se convierten a sus procesos equivalentes y visto así, parece un uso más fácil y
más cercano a las descripciones funcionales. pero totalente equivalente a las correspondientes
instancias de componentes.

Você também pode gostar