Sincronización de contador en dos dominios de reloj

1

Me gustaría entender diferentes enfoques para implementar un contador de dominio de reloj cruzado. En todas las siguientes posibilidades tengo:

clk_a : in std_logic;
clk_b : in std_logic;
reset : in std_logic;

-- cross-domain counter
signal frm_cnt : standard_logic_vector(7 downto 0);
-- control signal: increment counter (synced to clk_a)
signal frm_cnt_inc : std_logic;
-- control signal: decrement counter (synced to clk_b)
signal frm_cnt_dec : std_logic;
-- control signal, perform action on the counter
signal frm_cnt_modif : std_logic;

Los enfoques que considero son:

Contador no cronometrado similar a la respuesta en ¿Cómo se implementa este simple contador en un FPGA sin reloj?

frm_cnt_modif <= frm_cnt_inc or frm_cnt_dec; -- can also be xored

frame_counter : process(frm_cnt_modif, reset)
begin
  if reset = RST_ACTIVE then
    frm_cnt <= (others => '0');
  elsif rising_edge(frm_cnt_modif) then
    if frm_cnt_inc = '1' then
      frm_cnt <= incr_vec(frm_cnt);
    elsif frm_cnt_dec = '1' then
      frm_cnt <= decr_vec(frm_cnt);
    end if;
  end if;
end process;

Esto simula que está bien, y Vivado puede sintetizarlo, sin embargo, se queja de utilizar frm_cnt_modif como reloj , lo que causa problemas de tiempo. Además, las señales de control pueden superponerse.

Otro enfoque que he intentado es tener contadores separados en sus respectivos dominios de reloj y realizar una lógica basada en su diferencia. En este caso, me preocupa el desbordamiento del contador, por lo que restablecerlos cuando no se realiza ninguna acción en ambos lados y son iguales parece una buena solución, sin embargo, creo que la señal que controla el restablecimiento estará sujeta a problemas de sincronización similares.

Lo que estoy experimentando ahora es la 'sincronización con el reloj más rápido'. Tengo un:

control_synchronizer : process(clk_b, reset)
begin
  if reset = RST_ACTIVE then
    frm_cnt_inc_sync <= '0';
  elsif rising_edge(clk_b) then
    if frm_cnt_inc_sync = '1' then
      frm_cnt_inc_sync <= '0';
    elsif frm_cnt_inc = '1' then
      frm_cnt_inc_sync <= '1';
    else
      frm_cnt_inc_sync <= '0';
    end if;
  end if;
end process;

Y el incremento y decremento reales del contador se realizan en un proceso sincronizado con clk_b . Esto parece funcionar bien siempre y cuando clk_b > clk_a and clk_b < 2 * clk_a .

¿Alguna otra posible solución y / o mejores prácticas? (Soy consciente de que probablemente debería usar códigos grises para el propio contador).

EDIT1:

Después de la respuesta de Dave y de leer enlace , terminé con lo siguiente:

control_a : process(clk_a, reset)
begin
  if reset = RST_ACTIVE then
    frm_cnt_inc_d <= '0';
  elsif rising_edge(clk_a) then
    if frm_cnt_inc = '1' then
      frm_cnt_inc_d <= '1';
    else
      frm_cnt_inc_d <= '0';
    end if;
  end if;
end process;

control_b : process(clk_b, reset)
begin
  if reset = RST_ACTIVE then
    frm_cnt_inc_sync <= '0';
    frm_cnt_inc_sync_d <= '0';
  elsif rising_edge(clk_b) then
    frm_cnt_inc_sync_d <= frm_cnt_inc_sync;
    -- this check was ensuring 1 to 1 pulse translation before rising edge detection
    --if frm_cnt_inc_sync = '1' then
      --frm_cnt_inc_sync <= '0';
    if frm_cnt_inc = '1' then
      frm_cnt_inc_sync <= '1';
    else
      frm_cnt_inc_sync <= '0';
    end if;
  end if;
end process;

frm_cnt_inc_result <= not(frm_cnt_inc_sync_d) and frm_cnt_inc_sync;

Esto parece seguro para cualquier valor de reloj. Lo que no entiendo, sin embargo, es el propósito del frm_cnt_inc_d FF (el código simula perfectamente bien sin él). ¿Alguien puede explicar si es realmente necesario?

    
pregunta aproxp

1 respuesta

0

¿Estás por casualidad contando marcos de video en un buffer de cuadros? Hago este tipo de cosas todo el tiempo.

Una forma de pasar un pulso de un dominio de reloj a otro es convertirlo en un borde y luego hacer detección de borde en él. En el dominio A, use el pulso para alternar un FF. En el dominio B, sincronice la salida a través de dos FF. Ejecute la salida del sincronizador a través de un detector de bordes (compuerta FF y XOR).

Obtendrás un pulso en el dominio B por cada pulso en el dominio A, siempre y cuando los pulsos no se produzcan más rápido que cualquier reloj.

Esto se necesita con la frecuencia suficiente como para crear un módulo genérico para implementarlo. xd_ es la abreviatura de "dominio cruzado": tengo otros módulos en esta biblioteca que realizan funciones similares.

entity xd_pulse_xfer is
  generic (
    ACTIVE_RESET     : std_logic := '1';   -- active level of reset input
    ACTIVE_IN        : std_logic := '1';   -- active level of input pulse
    ACTIVE_OUT       : std_logic := '1'    -- active level of output pulse
  );
  port (
    reset            : in  std_logic;
    clk_a            : in  std_logic;
    pulse_in         : in  std_logic;
    clk_b            : in  std_logic;
    pulse_out        : out std_logic
  );
end xd_pulse_xfer;

architecture behavior of xd_pulse_xfer is
  signal toggle    : std_logic;
  signal toggle_a  : std_logic;
  signal toggle_b  : std_logic;
  signal toggle_c  : std_logic;
begin
  process (clk_a)
  begin
    if rising_edge(clk_a) then
      if reset = ACTIVE_RESET then
        toggle <= '0';
      elsif pulse_in = ACTIVE_IN then
        toggle <= not toggle;
      end if;
    end if;
  end process;

  process (clk_b)
  begin
    if rising_edge(clk_b) then
      toggle_a <= toggle;        -- synchronizer
      toggle_b <= toggle_a;      -- synchronizer
      toggle_c <= toggle_b;      -- edge detector
    end if;
  end process;

  pulse_out <= (not ACTIVE_OUT) xor (toggle_b xor toggle_c);

end behavior;
    
respondido por el Dave Tweed

Lea otras preguntas en las etiquetas