Módulos VHDL

Contadores en VHDL

Figura 1: El contador binario de cuatro bits en acción.

Hoy vamos a ver un simple componente: un contador.

Sí, ya lo hemos utilizado.

Y sí, ya ha sido descrito hasta el cansancio.

Pero, ¿no es una lata que sólo haya componentes simples?

Como siempre, sólo se explica lo más fácil y se deja como tarea algo complejo más allá del entendimiento humano.

Bueno, hoy cerraremos un poco esa brecha entre lo académico y lo práctico.

Definiciones…

Primero que nada, contadores hay muuuuuuuuuuchos.

Pero, olvidándonos de todos los colores y sabores, concentrémonos inicialmente en el sabor vainilla.

Ya saben, el que todos usamos siempre, el más común.

Es algo así como el “contador digital binario síncrono ascendente”.

¿A que no sabían que podía tener un nombre tan largo?

De hecho, lo podemos complicar un poco más, diciendo “contador digital binario síncrono ascendente de cuatro bits”.

De aquí en adelante, “el contador”:

Vale, vale.

Ligera confusión.

Ahora sí.

¿Qué significa todo esto?

  • Contador: mecanismo o sistema que indica el resultado de una sucesión numérica (no, en serio, así se define).
  • Digital: que lo estamos haciendo en FPGA, un dispositivo que trabaja con señales digitales.
  • Binario: aunque en teoría debería ser que trabaja con sistema numérico base dos, no es así. Bueno, sí trabaja en base dos. Aquí nos referimos a que es binario a secas, sin interpretación especial. Que no es BCD o alguna otra codificación arcana.
  • Síncrono: Sometido a una señal periódica, conocida generalmente como “señal de reloj”.
  • Ascendente: Que va para arriba, duh.
  • De cuatro bits: La resolución, o hasta qué tanto podemos contar.

Así que, nuestro “contador digital binario síncrono ascendente de cuatro bits” en realidad quiere decir “cuatro LEDs en nuestra tarjeta con FPGA que indican un número del 0 al 15 (en binario), que se incrementa automáticamente cada cierto tiempo”.

Como ven, a nosotros los ingenieros nos gusta poner nombres bonitos a lo cotidiano.

(La verdad, es para ahorrar palabras, ya saben lo poco que nos gusta hablar con los mortales)

Un primer modelo en VHDL

Como dije, vamos a empezar por la versión más básica de todas.

Tómense 6:40 para ver la explicación del código en este video:

El código del contador en el video es el siguiente:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity contador_v1 is
    PORT (
        clk    : IN  STD_LOGIC;
        cnt_out: OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
    );
end contador_v1;

architecture Behavioral of contador_v1 is
    -- Señal temporal para el contador.
    signal cnt_tmp: STD_LOGIC_VECTOR(3 DOWNTO 0) := "0000";
begin
    proceso_contador: process (clk) begin
        if rising_edge(clk) then
            cnt_tmp <= cnt_tmp + 1;
        end if;
    end process;

    cnt_out <= cnt_tmp;
end Behavioral;

Y todo el módulo completo, para su descarga a Basys2:

Descargar codigo ejemplo contador

Y de ahí el segundo, el tercero…

Ahora que ya tenemos la versión más simple, procedemos a incluir las demás señales que puede tener el contador.

La más obvia de ellas, el reset.

Claro, la cosa también se puede complicar un poco, porque puede tener un reset síncrono y otro reset asíncrono.

De momento, vamos con el reset asíncrono:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity contador_v2 is
    PORT (
        clk    : IN  STD_LOGIC;
        areset : IN  STD_LOGIC;
        cnt_out: OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
    );
end contador_v2;

architecture Behavioral of contador_v2 is
    -- Señal temporal para el contador.
    signal cnt_tmp: STD_LOGIC_VECTOR(3 DOWNTO 0) := "0000";
begin
    proceso_contador: process (areset, clk) begin
        if areset = '1' then
            cnt_tmp <= "0000";
        if rising_edge(clk) then
            cnt_tmp <= cnt_tmp + 1;
        end if;
    end process;

    cnt_out <= cnt_tmp;
end Behavioral;

Para incluir el reset asíncrono se necesitan cuatro líneas más:

  1. Incluímos la señal de reset como entrada al contador.
  2. Añadimos dicha entrada a la lista de sensibilidad del proceso (si la usamos sin declararla, el sintetizador se queja).
  3. Si se recibe un valor en alto (un ‘1’),
  4. se reinicia el contador a cero.

Debido a que el regreso a cero ocurre antes (y fuera) de recibir la señal de reloj en la línea 20, el reset es asíncrono.

Esta segunda versión se basó en el módulo descrito en la XST User Guide [1], de Xilinx, Inc.

Y una vez se tiene el reset, ¿por qué no precargar cualquier otro número?

De nuevo, la señal para precargar otro dato al contador puede ser síncrona o asíncrona…

Vamos a hacer un mecanismo de precarga asíncrono.

La implementación es similar al reset: nuevas señales, actualización de la lista de sensibilidad, y nuevo if fuera del reloj:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity contador_v3 is
    PORT (
        clk    : IN  STD_LOGIC;
        areset : IN  STD_LOGIC;
        aload  : IN  STD_LOGIC;
        data   : IN  STD_LOGIC_VECTOR(3 DOWNTO 0);
        cnt_out: OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
    );
end contador_v3;

architecture Behavioral of contador_v3 is
    -- Señal temporal para el contador.
    signal cnt_tmp: STD_LOGIC_VECTOR(3 DOWNTO 0) := "0000";
begin
    proceso_contador: process (aload, areset, clk, data) begin
        if areset = '1' then
            cnt_tmp <= "0000";
        elsif aload = '1' then
            cnt_tmp <= data;
        elsif rising_edge(clk) then
            cnt_tmp <= cnt_tmp + 1;
        end if;
    end process;

    cnt_out <= cnt_tmp;
end Behavioral;

Y los nuevos cambios son:

  1. Nueva entrada aload, para determinar si se debe precargar un dato en el contador.
  2. Nueva entrada data, que contiene los cuatro bits que se usarán como “valor inicial”.
  3. Se actualiza la lista de sensibilidad para agregar ambas entradas (tanto aload como data).
  4. Nuevo if para determinar si se debe precargar un dato,
  5. y si es así, asignarle data a nuestro contador.

Nótese que la precarga va después de la verificación del reset.

Es decir: no importa qué hagamos, primero va el reset asíncrono.

Y, aún falta ver cuando tiene reset síncrono, precarga síncrona.

Y que vaya hacia abajo.

O que vaya hacia arriba y hacia abajo.

No obstante, para éso es mejor la guía oficial de Xilinx, la XST USer Guide, que describe los contadores en sus páginas 43 a 59.

No obstante, hay otra versión del contador que sí nos interesa: un contador con módulo diez.

¿Módulo 10? ¿acaso dividimos?

El módulo es una operación matemática fea, que indica el sobrante de una división.

Si pienso en algo como “4 módulo 3”, tendría que hacer una división primeramente.

Vale.

El tres cabe una vez en el cuatro, y sobra uno.

¿Qué tiene que ver eso con mi contador?

¿Y por qué hablo de divisiones?

¿Y qué tiene que ver el módulo?

¿Y por qué carajo lo querría utilizar?

Primero que nada, analicemos las posibilidades del módulo con el mismo ejemplo del cuatro:

Operación Cabe N veces completas… Lo que sobra…
4 % 1 0 1
4 % 2 0 2
4 % 3 0 3
4 % 4 1 0
4 % 5 1 1
4 % 6 1 2
4 % 7 1 3
4 % 8 2 0
4 % 9 2 1
4 % 10 2 2

Para nuestros propósitos, la columna que importa es Lo que sobra… que va de 0 a 3.

O, de manera genérica, de 0 a N-1 (en el caso del 4, de 0 a 4-1, o 0 a 3).

¿Por qué importa ésto?

Porque de allí sale el nombre de este contador.

Nosotros no vamos a hacer divisiones en ningún momento.

Pero, sí vamos a utilizar el concepto de módulo.

Como ya vimos, el módulo de un número N puede ir desde 0 hasta N - 1.

Por lo tanto, nuestro súper contador de cuatro bits que puede contar hasta quince…

O, de 0 a 15…

… podría convertirse de “contador blah-blah” a un “contador blah-blah módulo dieciséis”.

Por supuesto, un contador módulo dieciséis es absurdo.

¡Hasta allí puede llegar y contar!

¡Y luego se regresa!

¿Por qué expresar lo obvio?

¡Duh!

Pero… ¿y si dejaramos espacios sin utilizar?

¿Por qué contar de 0 a 15?

Nosotros, los humanos, no leemos e interpretamos números hexadecimales fácilmente.

Al menos, yo no puedo.

Así que, prefiero ser normal y evitar la fatiga contando decimales (sólo de cero a nueve).

¿Para qué desperdiciar mi cerebro en conversiones?

Mejor que el FPGA desperdicie unas cuantas posiciones en mi beneficio.

Así pues, nuestro contador de 0 a 15 no nos sirve para propósitos prácticos.

Decidimos que un contador de 0 a 9 es mejor, por nuestra salud mental.

Y resulta que ese “de 0 a 9” es igual a un rango producido por “el residuo tras una división entre 10”.

Y, como ambos conjuntos de datos son iguales… pues, total.

Nos robamos el nombre de las matemáticas.

Y es conciso.

Así que, “contador módulo N” sólo significa que estamos deliveradamente desperdiciando algunos espacios potenciales en favor de una interpretación que nos convenga más.

Contador módulo 10, el código

Ahora sabemos que un “contador módulo 10” es un “contador que llega hasta nueve”.

Y aprendimos también que, para impresionar, “contador que llega hasta nueve” no suena interesante en una plática aunque claro, no es como si algo de lo que hacemos sonara interesante en una plática….

Así que, con cuatro líneas más de código sobre el listado tres, les presento el contador módulo 10:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity contador_v4 is
    PORT (
        clk    : IN  STD_LOGIC;
        areset : IN  STD_LOGIC;
        aload  : IN  STD_LOGIC;
        data   : IN  STD_LOGIC_VECTOR(3 DOWNTO 0);
        cnt_out: OUT STD_LOGIC_VECTOR(3 DOWNTO 0)
    );
end contador_v4;

architecture Behavioral of contador_v4 is
    -- Señal temporal para el contador.
    signal cnt_tmp: STD_LOGIC_VECTOR(3 DOWNTO 0) := "0000";
begin
    proceso_contador: process (aload, areset, clk, data) begin
        if areset = '1' then
            cnt_tmp <= "0000";
        elsif aload = '1' then
            cnt_tmp <= data;
        elsif rising_edge(clk) then
            if cnt_tmp = "1001" then
                cnt_tmp <= "0000";
            else
                cnt_tmp <= cnt_tmp + 1;
            end if;
        end if;
    end process;

    cnt_out <= cnt_tmp;
end Behavioral;

El cambio aquí está en la parte síncrona del proceso, donde anteriormente simplemente se incrementaba en uno el contador.

Ahora, esa línea única a evolucionado a cinco líneas (25 a 29) que distinguen dos posibilidades:

  1. Si llega a nueve sabemos que no tiene que llegar a diez, por lo que regresa a cero.
  2. La que ya existía, donde simplemente sigue contando como si nada pasara.

¿Y qué sigue de aquí?

Hasta ahora, cubrimos un poco más de lo que los académicos nos dicen.

Pero, la verdad, no mucho.

En la próxima entrega veremos cómo incrementar un contador de manera “automática” mediante el reloj, o de manera “manual” gracias a un botón.

Mientras tanto, ¿qué dudas siguen teniendo sobre el contador en VHDL?

Referencias

  1. ^ XST User Guide. (2008). Consultada el 29 de octubre de 2016, de https://www.xilinx.com/itp/xilinx10/books/docs/xst/xst.pdf

You Might Also Like

8 Comentarios

  • Responder
    luk-lokote
    junio 19, 2017 at 10:16 am

    hola,primero que nada es muy bueno explicando lo que lo hace un buen mentor segundo ¿ donde puedo seguir leyendo la continuación a el pequeño curso??..
    ¿tiene mas tutoriales referentes a vhdl?

    • Responder
      Carlos Ramos
      junio 26, 2017 at 9:43 am

      Gracias por tu comentario :D. El curso lo tengo disponible en esta página. Allí puedes ver lo de los contadores utilizados para el reloj digital. Saludos.

  • Responder
    Marcos Machorro
    octubre 30, 2017 at 11:37 pm

    Muy buena explicacion, me gusto la manera en que explica y las bromas o comentarios que pone

    • Responder
      Carlos Ramos
      noviembre 1, 2017 at 6:10 pm

      Marcos, muchas gracias por tu comentario :D. Por comentarios así es que no pierdo el interés en el tema :).

  • Responder
    Jafeht B.I.
    mayo 14, 2018 at 3:15 pm

    Me encanta. Actualmente soy estudiante de ingeniería y esta explicación del tema me puso en onda otra vez.

    • Responder
      Carlos Ramos
      junio 7, 2018 at 1:25 pm

      Muchas gracias por comentar, Jafeht. Es bueno saber que el contenido te ha gustado :D.

  • Responder
    Jesus Warrior
    septiembre 25, 2018 at 1:25 am

    Hola, estoy buscando (en la madrugada) un contador síncrono y con una máquina de estados en vhdl, ya tengo uno pero por alguna razón (maquiavélica) me corre bien en Quartus II pero en ISE no, igual me reí con tus bromas.
    Saludos.

    • Responder
      Carlos Ramos
      noviembre 7, 2018 at 9:38 pm

      Ah, diferencias entre fabricantes, ¿quién no las ama? Un gusto hacer ameno este tema tan árido :D.

    Deja tu comentario