En este artículo vamos a explicar cómo crear un contador en VHDL. Para ello vamos a trabajar con el simulador EDA Playground, tal y como os hemos explicado en el video simuladores gratis para VHDL.

Después de varios videos y artículos sobre las FPGA Zynq vamos a volver a lo básico. En el blog intentamos dar contenido para todos los niveles, tanto para gente que empieza con VHDL como para gente que lleva ya muchos años y quiere aprender cosas nuevas sobre FPGA.

¿Que es un contador digital?

Un contador digital es un circuito que realiza una cuenta ascendente o descendente entre un valor inicial y otro final.

Un contador es un circuito secuencial, es decir contiene elementos de memoria (FLIP-FLOPS) . Estos Flips-Flops almacenan el valor de la cuenta. Cada ciclo de reloj leemos el valor y lo incrementamos en uno, volviéndolo a guardar en el mismo registro. Podemos ver el contador como una maquina de estados cuyos estados son el 1,2,3,4,5,..

Existen diferentes tipos de contadores y en cada aplicación debemos utilizar el que mas nos convenga. A continuación vamos a ver tres ejemplos de contadores para que podáis entender cómo funcionan y adaptarlos a vuestra aplicación.

Contador ascendente basico

El primer contador vhdl que veremos es un contador simple de 8 bits. Cuando se resetea pone el valor de la cuenta a 0 y cada ciclo de reloj la incrementa en uno. Al llegar al final de la cuenta, vuelve a cero y genera una señal de fin de cuenta.

El código que os mostramos es sencillo, pero hay un par de detalles a tener en cuenta.

El primer detalle es que la señal cuenta, que será donde se almacene el valor actual de la cuenta la declaramos como integer. Los integer en VHDL se utilizan para poder operar con ellos con facilidad. Con los std_logic_vector también se puede operar pero es mas lio. Si intentamos sumarle 1 nos dará un error. Para poder trabajar con los enteros utilizamos la librería numeric_std.

En el proceso, cada ciclo de reloj incrementamos en uno el valor de la cuenta y si hemos llegado al final volvemos a cero. La otra clave está en la asignación donde ponemos el valor de cuenta en la salida. Para ello tenemos que convertir el integer a un std_logic_vector. Eso lo haremos mediante la sentencia std_logic_vector(to_unsigned(cuenta, 8)). En esa linea usando la librería numeric, pasamos de entero a una representación de 8 bits sin signo y de ahi a std_logic_vector.

Finalmente tenemos también el testbench necesario para poder simular el circuito.

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.numeric_std.all;

entity contador is
port(
    clk : in std_logic;
    reset : in std_logic;
    count_o : out std_logic_vector(7 downto 0);
    end_o   : out std_logic
);
end entity;

architecture rtl of contador is
    signal cuenta : integer range 0 to 255;

begin
    process(clk,reset)
    begin
    	if (reset='1') then
            cuenta <= 0;
            end_o <= '0';
        elsif(rising_edge(clk)) then
            end_o <= '0';
            if(cuenta=255) then
                cuenta <= 0;
                end_o <= '1';
            else    
                cuenta <= cuenta + 1;
            end if;    
        end if;
    end process;
    
    count_o <= std_logic_vector(to_unsigned(cuenta, 8));

end architecture;

library IEEE;
use IEEE.std_logic_1164.all;

entity test is
end entity;

architecture prueba of test is
    signal clk : std_logic :='0';
    signal reset : std_logic;
    signal count_o : std_logic_vector(7 downto 0);
    signal end_o   : std_logic;
begin

    clk <= not(clk) after 10 ns;
    reset <= '1', '0' after 20 ns;

    contador_0 : entity work.contador(rtl) port map(clk,reset,count_o,end_o);

end architecture;

El resultado de la simulacion es el siguiente. El valor de la salida se incrementa de uno en uno (esta representado en hexadecimal) y al llegar a FF se activa la señal de fin de cuenta. Os recordamos que hemos usado edaplayground para la simulacion.

Contador ascendente con carga paralela

Otro ejemplo de contador es el contador con carga paralela. En este caso tenemos una señal de carga load y un valor que se cargará en el registro de cuenta cuando esa señal se active. Esto nos permite comenzar la cuenta en el valor que queramos.

Para ello añadiremos en el proceso una condición que mire si se ha activado la señal de carga y en ese caso mediante una nueva conversion de tipo almacenamos la entrada de datos en la señal de cuenta.

En el testbench podéis ver como conectamos la señal de fin de cuenta a la señal de carga por lo que el contador al llegar a FF (en hexadecimal) volverá a cargar el valor de la entrada paralela y tendremos un contador entre ese valor y FF.

El código es el siguiente:

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.numeric_std.all;

entity contador_paralelo is
port(
    clk : in std_logic;
    reset : in std_logic;
    load : in std_logic;
    data_i : in std_logic_vector(7 downto 0);
    count_o : out std_logic_vector(7 downto 0);
    end_o   : out std_logic
);
end entity;

architecture rtl of contador_paralelo is
    signal cuenta : integer range 0 to 255;

begin
    process(clk,reset)
    begin
    	if (reset='1') then
            cuenta <= 0;
            end_o <= '0';
        elsif(rising_edge(clk)) then
            end_o <= '0';
            if(load='1') then
              cuenta <= to_integer(unsigned(data_i));
            else
              if(cuenta=255) then
                  cuenta <= 0;
                  end_o <= '1';
              else    
                  cuenta <= cuenta + 1;
              end if;
            end if;  
        end if;
    end process;
    
    count_o <= std_logic_vector(to_unsigned(cuenta, 8));

end architecture;

library IEEE;
use IEEE.std_logic_1164.all;

entity test is
end entity;

architecture prueba of test is
    signal clk : std_logic :='0';
    signal reset : std_logic;
    signal data_i : std_logic_vector(7 downto 0);
    signal count_o : std_logic_vector(7 downto 0);
    signal end_o   : std_logic;
begin

    clk <= not(clk) after 10 ns;
    reset <= '1', '0' after 20 ns;
    data_i <= "11000011";

    contador_0 : entity work.contador_paralelo(rtl) port map(clk, reset, end_o, data_i,count_o, end_o);

end architecture;

El resultado de la simulación es el que esperabamos, al llegar a FF se activa la señal de fin de cuenta conectada a la señal de carga y volvemos a empezar en C3.

Contador ascendente descendente

Este último caso es similar al primero, pero dependiendo de una señal de control sumaremos uno o restaremos uno. Dependiendo del modo en el que estemos la señal de fin de cuenta se activará al llegar a cero o a 255.

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.numeric_std.all;

entity contador_updown is
port(
    clk : in std_logic;
    reset : in std_logic;
    up_down : in std_logic;
    count_o : out std_logic_vector(7 downto 0);
    end_o   : out std_logic
);
end entity;

architecture rtl of contador_updown is
    signal cuenta : integer range 0 to 255;

begin
    process(clk,reset)
    begin
    	if (reset='1') then
            cuenta <= 0;
            end_o <= '0';
        elsif(rising_edge(clk)) then
            end_o <= '0';
            if(up_down='1') then
              if(cuenta=255) then
                  cuenta <= 0;
                  end_o  <= '1';
              else    
                  cuenta <= cuenta + 1;
              end if;
            else
              if(cuenta=0) then
                  cuenta <= 255;
                  end_o <= '1';
              else    
                  cuenta <= cuenta-1;
              end if;
            end if;  
        end if;
    end process;
    
    count_o <= std_logic_vector(to_unsigned(cuenta, 8));

end architecture;

library IEEE;
use IEEE.std_logic_1164.all;

entity test is
end entity;

architecture prueba of test is
    signal clk : std_logic :='0';
    signal reset : std_logic;
    signal up_down : std_logic;
    signal count_o : std_logic_vector(7 downto 0);
    signal end_o   : std_logic;
begin

    clk <= not(clk) after 10 ns;
    reset <= '1', '0' after 20 ns;
    up_down <= '1', '0' after 50000 ns;

    contador_0 : entity work.contador_updown(rtl) port map(clk, reset, up_down, count_o, end_o);

end architecture;

Como se puede ver en la simulación contador va creciendo y cuando cambiamos la señal comienza a decrecer.