Images

Análisis del código VHDL del proyecto LEDA_blog (V): Algoritmo implementado por el código.

Esta es la última entrada de un tutorial que comprende las últimas 5 entradas, este último tutorial (que empieza por lo más básico pues pretendo que los lectores puedan empezar con pocos conocimientos), a su vez se basa en otro tutorial de 12 entradas más enfocado en un tipo de hardware que yo uso (el kit Open3S250E con una FPGA Spartan-3E).

El blog apenas tiene algo más de 2 semanas, es verdad que voy a rachas, pero no se puede decir que no me esté tomando el blog en serio ¿no?.


Antes de nada recordemos que hace exactamente el sistema del que estamos analizando su código VHDL, como este proyecto ya lo probamos en el hardware y dicen que una imagen vale más que mil palabras (y si es en movimiento mejor), dejo el siguiente clip:





Lo que tenemos básicamente un sistema como este:


Es decir una señal de reloj clk -que es la del reloj del Core3S250E- actuando como entrada, y 4 salidas a los LEDs L4, L3, L2, L1 que están en la misma placa Core3S250E.




Y así sucesivamente…

La señal de reloj se usará para ir temporizando el tiempo en que aparece encendido un LED hasta que se apaga y se enciende el siguiente.
Pero este es el momento de coger o manejar la calculadora o de usar el lápiz y el papel.
Si vamos de nuevo a la página que se abre en el siguiente enlace:


En el apartado que pone “What’s on Core3S250E” hay unas figuras dónde se numera los componentes y una lista descriptiva de estos componentes, uno de esos componentes (que está en la parte inferior de la placa) es el “50M Active Crystal Oscilator” numerado como 12. Ese es reloj usado y la frecuencia con la que trabaja es de 50 MHz. Es decir la frecuencia es:

fclk = 50 MHz = 50 x 106


Por otro lado sabemos que el periodo es la inversa de la frecuencia y que los hercios son la inversa de los segundos, 1 Hz = 1 sg-1 (y si no lo sabías o no te acordabas te lo digo yo).

Tclk = 1/fclk

--------------------------------------------------------------------------------------------------------------------------

Nota: la anterior imagen la he tomado de Internet y luego le he incluido cosas. Voy a dejar el link no sólo porque es lo legal, también porque me ha parecido una página muy bien hecha y explicada:


--------------------------------------------------------------------------------------------------------------------------

Si hacemos los cálculos tenemos que Tclk = 0,02 µs = 20 ns, es decir, ¡20 x 10-9 segundos!, ¡eso es mucho más rápido que el pestañeo de una persona!.

Ese tiempo es tan imperceptible para el ojo humano que si hiciéramos uso de esos periodos de tiempo para las transiciones LED encendido => LED apagado => LED siguiente encendido, lo que veríamos seguramente es todos los LEDs encendidos a la vez, o quién sabe si todos apagados, yo desde luego no voy a hacer es prueba y someter los LEDs a esas velocidades de trabajo. 

Es por eso que lo que se hace en este programa es convertir la señal de reloj original clk a otra de mayor período llamada clk1 (esto se hace en el proceso P1 dentro de la arquitectura), y a su vez clk1 en otra de aun mayor período llamada clk2 (en el proceso P3). Las transiciones de encendido-apagado-encendido se harán con la señal clk2 que si trabaja con periodos más perceptibles para las personas.

Es decir, en este programa se hace las siguientes transiciones de unas señales de sincronización a otras:

Clk (reloj del Core3S250E) => clk1 => clk2

Con Tclk2 (periodo de la señal de sincronización con la que trabajamos)  mucho mayor que Tclk (periodo original del reloj del Core3S250E).

Recordemos que la arquitectura (que es dónde realmente se implementa el algoritmo), la estructuramos de la siguiente manera:

Empecemos por el principio (como se suele empezar vaya):


No es de extrañar que las señales clk1 y clk2 se declaren dentro de la arquitectura (ergo no en la entidad), puesto que realmente no son entradas ni salidas del sistema, son parámetros que usamos dentro de “la caja negra” para temporizar las salidas y que los LEDs se enciendan y apaguen y lo podamos ver. 

De nuevo vemos que se declaran como tipo std_logic (con el color que no se nombrar ¿fucsia quizás?), igual que la señal clk de entrada del reloj, como no podía ser de otra forma.


Una vez declarada la arquitectura, para que se empiece a ejecutar el algoritmo que contiene siempre se usa la palabra reservada begin.

Esto veremos que es también extensible a los procesos dentro de la arquitectura, también ahí se usa esta instrucción: begin.

Las señales  LCD_N y LCD_P ya dijimos que son prescindibles (de hecho ya no vuelven a usar en el programa), a mí se me ocurren varias funcionalidades pero no es cuestión de aburrir con mis conjeturas al lector (lector sin género definido por mí).

Y ya nos viene lo más divertido, el primer proceso o process en inglés:

La lista sensible de este proceso es clk, esto significa que este proceso se pondrá en marcha si y sólo hay algún cambio en la señal de reloj. 


De manera que para que clk se modifique tiene que haber en ese instante un flanco de subida o de bajada (ya adelanto que sólo se trabajará con flancos de subida).


A continuación se declara una variable con el sospechoso nombre de count, ¿será una abreviación de counter (contador en inglés)?, veremos que la sospecha no es infundada y que efectivamente hará de contador.

VARIABLE count: INTEGER RANGE  0 TO 9999999;

La variable se declara de tipo entero entre los valores 0 y 9999999 (incluidos), esto implica que  count se irá incrementando (veremos que de uno en uno) hasta llegar a 10.000.000, y justo en esa cifra se reiniciará.  Dicho de otra forma, el contador se incrementará 10.000.000 veces antes de reiniciarse a cero.

No es extraño que la única variable que hasta ahora hemos encontrado se declare dentro de un proceso, porque es dentro de los fragmentos de código que se ejecutan secuencialmente dónde más sentido tiene las variables.

Tras esto se ejecuta el begin de turno y aparece una instrucción que merece ser explicada con un poco de más de profundidad.


Lo que seguramente extrañe más a los que no estén familiarizados con el VHDL es ese clk’EVENT con el apostrofe en medio sorprendiendo.

clk’EVENT significa que se ha producido un cambio en la señal clk, este puede ser un flanco de subida o de bajada. A el “’EVENT” que añadimos justo a continuación de la señal (en este caso clk), es lo que se llama en VHDL un atributo, y en este caso devolverá el valor 1 (true o verdadero) cuando haya un cambio en la señal clk, y 0 (false o falso) cuando no haya un cambio en la señal clk. 

Para que lo entienda mejor el que no esté familiarizado con la programación lo explicaré como sigue:
La sentencia condicional (o también conocida como de bifurcación),

IF clk'EVENT 

está esperando que haya un cambio en clk, por tanto en cada momento habrá  dos opciones:

---------------------------------------------------------------------------------

Opción 1:

SI hay un cambio en clk (de 0 a 1, o de 1 a 0) => clk'EVENT  = 1 lógico  (1 lógico corresponde a verdadero):

    IF clk'EVENT  THEN      
                      
         -- Las instrucciones que hay en medio SI se ejecutan.

    END IF;  

---------------------------------------------------------------------------------

Opción 2:

NO hay un cambio en clk (clk está en 0 o en 1 lógico de forma constante, sin que haya cambio) => clk'EVENT  = 0 lógico (0 lógico corresponde a falso):

    IF clk'EVENT  THEN                     
       
         -- Las instrucciones que hay en medio NO se ejecutan.

    END IF;    

---------------------------------------------------------------------------------

 Sin embargo aquí no tenemos sólo IF clk’EVENT, tenemos:

IF clk'EVENT AND clk='1' THEN …

Esto quiere decir que para que se ejecuten las instrucciones entre el THEN y el END IF, no basta con que haya un cambio de clk, además (Y, o en inglés AND) este cambio tiene que dejar clk con valor 1, o sea, que el cambio debe de ser de 0 a 1.

Decir lo que acaba de leer es lo mismo que decir que  “IF clk'EVENT AND clk='1' THEN”   está esperando a que haya un flanco de subida para que se ejecute el código que viene a continuación del THEN.

Si no, se saltará todas esas instrucciones (las que están tras el THEN) hasta encontrar una instrucción de tipo “ELSE” o “ELSIF”, o simplemente  se continuará el programa con lo que venga detrás del “END IF”.

Entre las instrucciones que se encuentran entre el IF-THEN y el END IF;, pueden haber otros IF-THEN…END IF, también con sus ELSE Y ELSIF. Esto se conoce como sentencias de selección anidadas (unas dentro de otras) y en el caso que nos ocupa de hecho las hay.
Cuando recurra al diagrama de flujo esto se verá más claro.

El trozo de código:

Se puede representar como sigue:




Si nos fijamos bien, cada flanco de subida inicia un periodo que acaba cuando vuelve a haber otro flanco de subida, es por eso que cada vez que contamos un flanco de salida estamos también contando periodos:

Teniendo en cuenta la reflexión anterior, podemos entender lo que realmente hace el proceso P1 para los cual usaremos el siguiente cronograma dónde se compara el comportamiento de las señales clk y clk1.

De la anterior figura se deduce que:

 Tclk1 = (5000000+5000000) x Tclk = 10.000.000 x Tclk = 10 x 106 x Tclk

Por otro lado recordemos que obtuvimos Tclk:

  Tclk = 20 ns = 20 x 10-9 s = 20 x 10-3 x 10-6 segundos

Por tanto:

Tclk1 = 20 x 10 x 10-3  s = 200 x 10-3 = 200 ms = 0,2 segundos

Con esto ya empezamos a tener una señal con un periodo mucho mayor (y por tanto una frecuencia menor), más cercano a lo que puede percibir el ojo humano.

Dado que en hemos profundizado bastante con el proceso P1, con el siguiente escrito en la arquitectura – el P3 – seré más sintético en la explicación.

El código del proceso P3 (bastante más sencillo) es:


Para explicarlo volveré a recurrir a un diagrama de flujo:


Si lo analizamos bien tendríamos los siguientes cronogramas:

De esto se deduce que Tclk2 = 2 x Tclk1.
Como ya calculamos Tclk1:
Tclk1 = 200 ms
Tenemos que:
Tclk2 = 2 x Tclk1 = 400 ms = 0,4 segundos
---------------------------------------------------------------------------------

En el siguiente proceso – el P2 – el programa empieza a procesar las salidas.
Recordemos que habían cuatro salidas led1[0], led1[1], led1[2] y led1[3], definidas en un vector de tipo std_logic_vector que se definió en la entidad como led1. Cada una de estas salidas va a un pin configurado como output, y de este por una pista de la Core3S250E al cátodo de un LED.

La tensión de alimentación de los LEDs es de unos 3,3 voltios, y las salidas en estado alto de los pines de la FPGA de unos 3,3 voltios en estado alto (o 1 lógico) y 0 voltios en estado bajo (o 0 voltios).

Un LED es básicamente un diodo, los diodos conducen cuando la tensión del ánodo es más grande que la del cátodo,  es por ello que, pese a que intuitivamente podamos identificar un 1 lógico -o estado alto- como LED encendido, en realidad los LEDs conducen cuando los pines de salida (gestionados por el vector led1) están en estado bajo o 0 lógico, ya que las salidas están conectadas a los cátodos, no a los ánodos.

De manera que si queremos que sólo se encienda el Led2  – por ejemplo – y que los demás permanezcan apagados, tendríamos que poner led1[1] a 0 lógico, y led1[0], led1[2] y led1[3] a 1 lógico. Es decir, la secuencia 1101 en el vector led1.

Lo que hará el proceso P2, es mediante un contador introducir las secuencias 1110-1101-1011-0111 en el vector led1 sucesivamente, y que al incrementarse 4 veces (contador a 3 si se inicializa a 0) se vuelva a repetir la secuencia 1110-1101-1011-0111 (contador a 0), y así en bucle. Vemos como se implementa el código para hacer esto.

El diagrama de flujo es:

Si puedes abrir una ventana dónde aparezca el código, y otra en la que aparezca el diagrama de flujo, puedes entender bastante bien el código. Incluso si estás introduciéndote en el VHDL puedes así aprender cómo se hacen ciertas cosas en este lenguaje.

And the last but not the least,



"Y con esto y un bizcocho,
Hasta la próxima entrada
Que es la dieciocho."

(Aquí no sólo hablamos de electrónica, hacemos incluso poesía). 
Images

Análisis del código VHDL del proyecto LEDA_blog (IV): Estructuración del código (típica en VHDL).

En las últimas entradas he metido mucha “literatura” en esta iremos más “al grano”.


Los programas de VHDL suelen estructurarse como sigue (y en el caso que nos ocupa, el código VHDL de LEDA_blog es como se hace):

--------------------------------------------------------------------------------------------------------------------------

Nota:
En el anterior esquema se podría haber incluido al inicio un bloque (prescindible), llamado cabecera dónde el programador da datos (en forma de comentarios antecedidos por dos guiones medios --) sobre su nombre o el de la empresa u organización, la versión del software, el nombre del código, una breve descripción de la función del programa…etc.

En nuestro código existe esa cabecera, pero como ya expliqué es ilegible porque está escrito en caracteres que mi PC no reconoce.

--------------------------------------------------------------------------------------------------------------------------


En el caso de nuestro programa la anterior estructura se implementa como sigue:


Ahora iremos explicando de forma breve cada una de estas partes del sistema.

Cabecera del programa:

Como ya comentamos esta parte son comentarios previos sobre el código hechos por el programador para describirlo. No es imprescindible, esta parte no se va a sintetizar (el equivalente a compilar en otros lenguajes de programación), ni se implementará en el hardware.

Llamadas a librerías y bloques de estas:



Las librerías están organizadas en bloques que la componen, que a su vez contienen fragmentos de código que pueden ser muy útiles en muchos casos. Al llamarlas podemos invocar esos códigos que nos facilitará mucho la vida. En nuestro caso llamamos a la librería IEEE y dentro de ella a los bloques: IEEE.STD_LOGIC1161 e IEEE.std_logic_unsigned. Gracias a estas llamadas, podemos hacer uso de los tipos de datos std_logic o std_logic_vector por ejemplo. Y así nos ahorramos tener que definirlos de forma manual.

Entidad (entity):


En la entidad lo que fundamentalmente se define es las entradas y salidas del sistema, no como se procesan las entradas para obtener las salidas esperadas.

Se podría comparar la entidad con una caja negra en la que sólo ves lo que se conecta con el exterior, es por decirlo de alguna manera, "la carcasa" del programa.

En nuestro caso sería:



Sobre las salidas LCD_N y LCD_P solo podría hacer conjeturas de porque están ahí y cuál puede ser su utilidad, lo que si tengo claro es que no son imprescindibles, y si elimináramos todas las instrucción que las usan el comportamiento obtenido (los LEDs encendiéndose secuencialmente), sería exactamente el mismo.

Arquitectura (architecture):



Aquí es donde realmente se implementa la forma en que se gestiona las entradas para obtener las salidas que deseamos (la secuencia de encendido de los LEDs).

En programación no suele haber una y sólo una forma de implementar un logaritmo que funcione y obtenga los resultados que queremos, hay varias, más o menos elegantes, más o menos óptimas, pero se puede tener el mismo resultado con códigos diferentes.

En VHDL, además también depende de la forma en que describas el sistema digital, que puede ser: por flujo de datos, funcional (o también he leído que le llaman algorítmica) y estructural.

En este caso el programador eligió la funcional, y más concretamente implementando las partes del algoritmo mediante procesos. En estos casos, el esquema de la arquitectura suele ser (de forma muy simplificada):

Aquí tenemos que considerar dos cosas, dentro de los procesos la ejecución de instrucciones es secuencial, pero los procesos se pueden ejecutar de forma concurrente. Que los procesos se puedan ejecutar de forma concurrente no significa que necesariamente se ejecuten a la vez. Cada proceso se ejecuta cuando se da una condición que puede ser que se modifique algún parámetro de su lista de sensibilidad, recordemos: 

O bien que se cumpla una condición de tipo  wait, algo con lo que no entraremos en esta ocasión.

Por tanto:

Pero a la vez:


En el código con el que estamos trabajando esta forma de programar mediante procesos se muestra en la siguiente figura. 

El porqué de que el segundo proceso que nos encontramos en el código esté notado como P3 y el siguiente como P2 es algo que se me escapa, en cualquier caso es una cuestión de notación sin importancia.

En la siguiente entrada ya estudiamos el algoritmo implementado por el programa con las nociones básicas que necesitábamos, a fin de cuentas entenderlo bien es el objetivo de este tutorial que forman estas últimas entradas. 

Images

Análisis del código VHDL del proyecto LEDA_blog (III): Breve descripción de la semántica y sintaxis del código.

En esta entrada haremos uso del código del tutorial - que previamente publiqué en 12 entradas - llamado “Introducción al kit Open3S250E”, es decir el código del proyecto creado en XILINX llamado: LEDA_blog.

Con dicho código haremos un breve repaso o introducción (según sean los conocimientos sobre el tema del lector) del léxico y sintaxis básicas de un programa en VHDL con el fin principal de tener los conocimientos necesarios para una futura entrada que tratará del funcionamiento o algoritmo del código en VHDL del proyecto LEDA_blog.


Para ello usaremos el código de colores que usa el editor de XILINX. El programa es el siguiente:


Lo que está en verde en el anterior código, siempre detrás de dos guiones medios “--”, son sólo comentarios que hace el programador para que el código lo puedan entender otros programadores. 

Estos comentarios cuando el programa se sintetice se obvian completamente (podemos decir que en VHDL el proceso de síntesis es equivalente a la compilación en otros lenguajes como el C). Dicho de otra forma, si elimináramos toda la parte del código que está en verde el programa se ejecutaría exactamente igual.

Todo lo que está en verde es completamente prescindible.

Se habrá dado usted cuenta -dama o caballero- que aquí esos comentarios explican poco dado que no son precisamente inteligibles, más bien bastante ilegibles con tantos signos de interrogación... 

¿Por qué?, pues porque con toda probabilidad están escritos con caracteres que mi ordenador (computador o computadora) no entiende, o digamos mejor que el sistema operativo de mi PC  no capta esos caracteres ya que está configurado para el español internacional al igual que el teclado.

Recordemos que este programa en realidad no es mío, ha sido un copia-pega (o copy-paste) de una demo con ligeras modificaciones hechas por mí que se señalan en rojo, a parte de algunos espaciados que he hecho en la escritura de las instrucciones para hacer lo más fácilmente legible.

-------------------------------------------------------------------------------- 

Lo que está en azul si es bastante importante, son las palabras reservadas del VHDL, dicho de otra forma, el léxico que usa el lenguaje. Estás palabras reservadas tienen una funcionalidad muy definida, es por eso que el programador no puede usar esas palabras para otros usos (como identificadores de señales o variables por ejemplo).

También se puede observar que están en mayúsculas, bueno, eso es la preferencia del programador que le gusta así, pero si las hubiéramos escrito en minúsculas el funcionamiento del programa sería igualmente correcto. De hecho verás que en otros códigos que encuentres por ahí, las mismas palabras reservadas están en minúsculas y la ejecución sigue siendo válida. Como reza el dicho en castellano “cada maestrillo tiene su librillo”.

---------------------------------------------------------------------------------

Lo que está en fucsia, morado o rosa oscuro (para ser sincero no se dar nombre a ese color), hace referencia a la biblioteca y los bloques de esa biblioteca que necesitamos en este programa. 

Al principio del código encontramos tres líneas mediante las que se incluyen la librería y los bloques que necesitamos. La librería es la IEEE, y los bloques de ella que usamos son: IEEE.STD_LOGIC1161 e IEEE.std_logic_unsigned.

En este caso IEEE es una biblioteca o librería del VHDL, pero realmente el acrónimo IEEE hace referencia a una asociación de estandarización internacional. 
Para que se haga una idea el lector de que es eso de una asociación de estandarización internacional, hay por ahí un grupo de personas trabajando duro para unificar los criterios de los fabricantes, desarrolladores…etc, y así hacernos la vida más fácil a los demás.

La gente que maneja usualmente la expresión IEEE, no lo suele deletrear como I-E-E-E, sino como I-E-CUBO, me imagino que nos resulta más cómodo decir lo así que deletrear tanta E seguida. Así que si oyes decir a alguien I-E-CUBO ya tienes una idea de a que se refiere.

Para los que hemos dedicado mucho tiempo a esto de la electrónica la expresión IEEE (I-E-CUBO) es algo que no nos suena para nada extraño, más que nada porque encuentras ese acrónimo muchas veces. Pero yo aquí escribo “para todos los públicos” (o al menos lo intento). 

A parte de esas tres primeras instrucciones de al principio, el color fucsia (morado o rosa oscuro), nos lo encontramos más veces en el código, siempre dónde aparece escrito std_logic o std_logic_vector.

Aquí si voy a profundizar un poco más. Estos son identificadores de tipo de datos, y aparecen en el color que no se nombrar debido a que el código que las define está en los bloques IEEE.STD_LOGIC1161 e IEEE.std_logic_unsigned, y estos a su vez dentro de la librería IEEE. 

Esas tres instrucciones -que aparecen al principio del código- están ahí especialmente para que podamos usar estos tipos de datos: std_logic y std_logic_vector.

El std_logic hace referencia a tipos de datos bit, también existe el identificador de tipo de datos nombrado como bit y se podría haber hecho uso de él, pero el programador ha preferido – con buen criterio en mi opinión – hacer uso del std_logic porque es más completo que el tipo bit, y explicaré porqué.

Los datos tipo bit pueden tomar valores 0 o 1, luego el hardware normalmente le asigna un valor de tensión al 1 (que puede ser 3,3 o 5 voltios por ejemplo) y el cero a otro valor de tensión (que puede ser tensión de 0 voltios, pero ojo que no siempre va a ser así, es sólo un ejemplo).

¿Pero qué sucede si resulta que ese punto no está conectado a algún tipo de alimentación (5 o 3,3 voltios) o a tierra (0 voltios)?, ¿y si es un pin que está “al aire”? (a esto normalmente le llamamos estado de alta impedancia). ¿Y si en lugar de obtener un valor de tensión de 3,3 voltios tenemos uno de 2,8 voltios?, ¿y si en lugar de tener 0 voltios tenemos 0,5 voltios?.

Pues bien, para resolver todos estos dilemas se usa el std_logic que cubre este tipo de eventualidades como describe la siguiente tabla:


Descripción
Notación
Sin inicializar.
U
Desconocido.
X
Estado bajo fuerte.
0
Estado alto fuerte.
1
Estado de alta impedancia.
Z
Desconocido débil.
W
Estado bajo débil.
L
Estado alto débil.
H
No importa.
-


El std_logic_vector, es una sucesión de bits (cada uno con las mismas características del std_logic), por eso se suele inicializar como: Std_logic_vector(n-1 downto 0), siendo n el número de bits que quieres que tenga la cadena o vector de bits.

En el caso que nos ocupa se inicializó como std_logic_vector(3 downto 0) porque se pretende que el vector sea de 4 bits. Luego veremos que ese vector se declaró como salida para controlar los 4 LEDs usados en el hardware Open3S250E. 

---------------------------------------------------------------------------------

Lo que está en negro ya es más “cosecha” del programador, e incluye los identificadores que son los caracteres alfanuméricos que uno quiera, por ejemplo count para definir una variable que hará de contador (que también se podría haber escrito en castellano como contador sin ningún problema), operadores de asignación a señales o variables (“:=” o “<=”), operadores aritméticos (+, -, *, /…), relacionales (menor o igual “ <=”, mayor o igual “>=”, igual “=”), atributos del tipo del tipo identificador’event”, como por ejemplo el clk’event -que veremos sirve para detectar un flanco de subida del reloj notado como clk - … y por supuesto los punto y coma “;” que como en otros lenguajes de programación sirven para finalizar las distintas instrucciones (y así definirlas para que el sistema las sintetice/compile bien).

Con esta entrada espero que el lector que se inicia con VHDL se haya quedado con unas ideas básicas (que siempre se puede asentar o completar con otras fuentes como los enlaces que dejé en la penúltima entrada), y quién sabe si puede ser un buen repaso para las personas que tengan ya cierto nivel en VHDL.