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]