Relojes MMCM en FPGAs

Cuando se diseñan sistemas digitales síncronos, una de las reglas de oro es usar un sólo dominio de reloj para todo el circuito. Esto quiere decir que sólo usaremos una única señal de reloj conectada a todos los elementos síncronos del circuito. Por lo tanto debemos evitar una práctica bastante habitual que es usar la salida de un elemento del sistema, como por ejemplo un contador, para obtener la señal de reloj de otro elemento cualquiera. De hecho, aquí lo hicimos (mal) cuando diseñamos nuestro reloj digital (y usamos la salida del contador de 1Hz como señal de reloj para el contador de segundos). Aunque siempre en aras de la simplicidad y la pedagogía 🙂

Así que si queremos que nuestro diseño no tenga problemas de sincronización, el mismo reloj debe ser usado por todos y cada uno de los elementos síncronos. Sin embargo, si nuestro circuito es complejo y sobre todo, si ocupa una gran superficie de la FPGA, la señal de reloj puede sufrir pequeños retardos al distribuirse entre elementos distantes dentro de la FPGA y puede haber un pequeño desfase entre los flancos activos. Para minimizar al máximo este problema, las FPGAs disponen de bloques de reloj dedicados que nos ofrecen módulos PLL y MMCM. Los MMCM (Mixed-Mode Clock Manager) usan conexiones especiales para repartir la señal de reloj por toda la superficie de la FPGA. Vamos a crear un diseño bastante simple en el que veremos cómo usar el reloj MMCM y de paso aplicaremos buenas prácticas para diseñar un sistema digital con un sólo dominio de reloj. En este caso usaremos una FPGA Artix 7 de XILINX (en la placa Basys 3 de Digilent) y el entorno de desarrollo Vivado. El resultado será un contador binario de 8 bits que se mostrará en los LEDs de la placa, con una velocidad de 1Hz (una cuenta por segundo).

Utilizando el módulo MMCM

Para empezar, crearemos un nuevo proyecto en Vivado y también nuestro reloj MMCM. Vamos a utilizar un core IP del catálogo de Vivado, así que pulsamos sobre IP Catalog en  el Flow Navigation de la izquierda del GUI. Hacemos doble click en Vivado Repository > FPGA Features and Design > Clocking > Clocking Wizard.

Core IP clock wizard

Seleccionamos la primitiva MMCM. en la siguiente imagen vemos qué opciones deben estar marcadas.

Opciones MMCM 1

En la pestaña Output Clocks, en el campo Output Freq (MHz) Requested ponemos el valor 50.00 (es decir, usaremos un reloj de 50MHz). Marcamos las casillas Reset y Locked.

MMCM Clock 2

Pulsamos OK. En la siguiente ventana marcamos Out of context per IP y pulsamos generate.

MMCM Clock 3

Cuando aparezca la ventana indicando que se ha generado el core, pulsamos OK.

En el proyecto, añadimos el archivos de restricciones siguiente, en el que se han configurado los pines de la FPGA que vamos a usar (en este caso, el fichero se llama config_GPIO.xdc).

 

## LEDs

set_property PACKAGE_PIN U16 [get_ports {LED[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}]
set_property PACKAGE_PIN E19 [get_ports {LED[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[1]}]
set_property PACKAGE_PIN U19 [get_ports {LED[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[2]}]
set_property PACKAGE_PIN V19 [get_ports {LED[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[3]}]
set_property PACKAGE_PIN W18 [get_ports {LED[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[4]}]
set_property PACKAGE_PIN U15 [get_ports {LED[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[5]}]
set_property PACKAGE_PIN U14 [get_ports {LED[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[6]}]
set_property PACKAGE_PIN V14 [get_ports {LED[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[7]}]
set_property PACKAGE_PIN V13 [get_ports {LED[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[8]}]
set_property PACKAGE_PIN V3 [get_ports {LED[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[9]}]
set_property PACKAGE_PIN W3 [get_ports {LED[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[10]}]
set_property PACKAGE_PIN U3 [get_ports {LED[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[11]}]
set_property PACKAGE_PIN P3 [get_ports {LED[12]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[12]}]
set_property PACKAGE_PIN N3 [get_ports {LED[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[13]}]
set_property PACKAGE_PIN P1 [get_ports {LED[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[14]}]
set_property PACKAGE_PIN L1 [get_ports {LED[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LED[15]}]

##7 segment display

## segmento A -> W7
set_property PACKAGE_PIN W7 [get_ports {CAT[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[0]}]
## segmento B -> W6
set_property PACKAGE_PIN W6 [get_ports {CAT[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[1]}]
## segmento C -> U8
set_property PACKAGE_PIN U8 [get_ports {CAT[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[2]}]
## segmento D -> V8
set_property PACKAGE_PIN V8 [get_ports {CAT[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[3]}]
## segmento E -> U5
set_property PACKAGE_PIN U5 [get_ports {CAT[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[4]}]
## segmento F -> V5
set_property PACKAGE_PIN V5 [get_ports {CAT[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[5]}]
## segmento G -> U7
set_property PACKAGE_PIN U7 [get_ports {CAT[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[6]}]
## segmento DP -> V7
set_property PACKAGE_PIN V7 [get_ports {CAT[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {CAT[7]}]

## Anodo 0 diplay -> U2
set_property PACKAGE_PIN U2 [get_ports {AN[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[0]}]
## Anodo 1 diplay -> U4
set_property PACKAGE_PIN U4 [get_ports {AN[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[1]}]
## Anodo 2 diplay -> V4
set_property PACKAGE_PIN V4 [get_ports {AN[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[2]}]
## Anodo 3 diplay -> W4
set_property PACKAGE_PIN W4 [get_ports {AN[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AN[3]}]

## Reloj CLK conectado a W5
set_property IOSTANDARD LVCMOS33 [get_ports {CLK}]
set_property PACKAGE_PIN W5 [get_ports {CLK}]
create_clock -period 10.000 -name CLK -waveform {0.000 5.000} [get_ports CLK]

En la última línea se definen las restricciones para el reloj externo a la FPGA, que es de 100MHz en el caso de la placa Basys 3 (a partir del cual el MMCM generará los 50MHz que hemos configurado).

El código VHDL del proyecto es el siguiente (nombre del fichero mmcm_top.vhd).

 

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.all;

entity mmcm_top is

    Generic ( 
        -- reloj a 50Mhz: 50000000 ciclos en un segundo.
        N : NATURAL := 50000000
    );
    
    Port ( CLK : in STD_LOGIC;
        AN : out STD_LOGIC_VECTOR (3 downto 0);
        LED : out STD_LOGIC_VECTOR (15 downto 0));
end mmcm_top;

architecture Behavioral of mmcm_top is    
    -- contador de 1hz
    signal clk_1hz : NATURAL;
    
    -- señal de habilitación para contador binario
    signal cont_ena : STD_LOGIC;
    
    -- señales para el MMCM
    signal reset_in: STD_LOGIC;
    signal MCLK: STD_LOGIC;
    signal locked: STD_LOGIC;
    
    -- señal de reset global
    signal mrst: STD_LOGIC;
    
    -- registro binario 8 bits para contador
    signal reg_bin: UNSIGNED (7 downto 0);
    
    alias leds_contador is LED (7 downto 0); 
     
begin

    -- apagamos displays de 7 seg. (no lo usaremos)
    AN <= "1111";
    
    -- mostrar contador en los leds
    leds_contador <= std_logic_vector(reg_bin);
    
    -- Control reset global del sistema.
    -- La señal locked se pone a 1 cuando el reloj es estable.
    -- Es entonces cuando lo podemos usar.
    -- Por eso aquí lo usamos como reset global del sistema.
    mrst <= not locked;
    
    -- reset del reloj mmcm no activo
    reset_in <= '0'; 
    
    -- Módulo MMCM (Core IP)
    MMCM_clk: entity WORK.clk_wiz_0 port map ( 
        clk_in1     => CLK,
        reset       => reset_in,
        clk_out1    => MCLK,
        locked      => locked );
        
  
    -- contador 1Hz
    process(MCLK)
    begin
        if (MCLK'event and MCLK='1') then
            if clk_1hz < N then 
                clk_1hz <= clk_1hz + 1;
            else
                clk_1hz <= 0;
            end if;
        end if;
    end process;
    
    -- señal de fin de cuenta 1Hz. Pulso para contador binario,
    cont_ena <= '1' when clk_1hz=N else '0';
    
    -- contador binario (sincrono)
    process(MCLK)
    begin
        if (MCLK'event and MCLK='1') then
            if (mrst = '1') then
                -- reset síncrono. 
                -- msrt se pone a 0 cuando el reloj es estable.
                reg_bin <= (others => '0');
             elsif (cont_ena = '1') then
                reg_bin <= reg_bin + 1;
            end if;
        end if;    
    end process;
                     
end Behavioral;

Si todo está correcto, en la jerarquía del proyecto debemos ver que MMCM_clk está subordinado a mmcm_top.

Jerarquía proyecto

Si observamos que hay un error como el de la figura siguiente no le hacemos caso, parece ser un fallo del entorno, ya que todo está correcto.

Error vivado

Analicemos el código. El contador de 1Hz ya nos debe ser familiar, pero ahora lo hemos modificado un poco. La señal de reloj del contador es MCLK, que es generado por el módulo MMCM.

    -- contador 1Hz
    process(MCLK)
    begin
        if (MCLK'event and MCLK='1') then
            if clk_1hz < N then 
                clk_1hz <= clk_1hz + 1;
            else
                clk_1hz <= 0;
            end if;
        end if;
    end process;
    
    -- señal de fin de cuenta 1Hz. Pulso para contador binario,
    cont_ena <= '1' when clk_1hz=N else '0';

Cuando el contador llega a su fin de cuenta (lo que ocurre con una frecuencia de 1Hz) se pone a 1 la señal cont_ena, que es la señal de habilitación que está conectada al contador binario, cuyo código es el siguiente (en realidad debería ser cuándo llegara a N-1, pero no vamos a perdernos en detalles nimios 🙂 ).

    -- contador binario (sincrono)
    process(MCLK)
    begin
        if (MCLK'event and MCLK='1') then
            if (mrst = '1') then
                -- reset síncrono. 
                -- msrt se pone a 0 cuando el reloj es estable.
                reg_bin <= (others => '0');
             elsif (cont_ena = '1') then
                reg_bin <= reg_bin + 1;
            end if;
        end if;    
    end process;

Nuestro contador binario es totalmente síncrono y, como vemos, usa la misma señal de reloj MCLK que el contador de 1Hz (ambos contadores usan la misma señal de reloj). La señal msrt está conectada mediante un inversor a la salida locked del MMCM. Esta señal se mantiene a 0 mientras se estabiliza el reloj, y cuando éste está estable se pone a 1, así que lo usamos como señal de reset global para el sistema. Así mantenemos el contador binario parado hasta que el reloj se estabilice, y después ya podemos empezar a contar de forma segura. Cada vez que la señal cont_ena (señal de habilitación) se pone en alto, el contador binario suma 1. Recordemos que en el reloj digital que diseñamos en un artículo anterior usábamos la salida del contador de 1Hz como señal de reloj para el segundero. Es decir teníamos dos dominios de reloj diferentes, que como hemos visto, en un circuito real puede ser una fuente de problemas inesperados.
El contador binario, que es un registro de 8 bits, está conectado directamente a los LEDs de la placa.

     -- mostrar contador en los leds
    leds_contador <= std_logic_vector(reg_bin);

Así pues, nuestro diseño sólo tiene una fuente de reloj y, además, al utilizar un módulo de reloj MMCM nos aseguramos de que minimizaremos al máximo los problemas derivados de un posible desfase en los flancos activos en las diferentes partes del circuito.

CompartirShare on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

Sé el primero en comentar en "Relojes MMCM en FPGAs"

Deja un comentario.

Tu dirección de correo no será publicada.


*