Electrical – Counter synchronization across two clock-domains

fpgavhdl

I would like to understand different approaches to implement a cross clock-domain counter. In all of the following possibilities I have:

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;

The approaches I consider are:

Non-clocked counter similar to the answer in How is this simple counter implemented on an FPGA without a clock?

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;

This simulates fine, and Vivado is able to synthesize it, however it complains about using frm_cnt_modif as clock, which causes timing problems. Additionally the control signals have a chance of overlapping.

Another approach I have tried is to have separate counters in their respective clock domains and perform logic based on their difference. Here, I am concerned with counter overflow, so resetting them when no action on both sides is performed and they are equal seemed like a good solution, however I think that the signal controlling the reset will be subject to similar synchronization problems.

What I am experimenting right now is the 'synchronization to the faster clock'. I have a:

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;

And the actual counter increment and decrement are performed in a process synchronized with clk_b. This seems to work fine as long as clk_b > clk_a and clk_b < 2 * clk_a.

Any other possible solutions and/or best practices? (I am aware that I should probably use grey-codes for the counter itself).

EDIT1:

After reply from Dave and reading http://fpgacenter.com/examples/basic/edge_detector.php I ended up with the following:

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;

This seems safe for any clock values. What I do not understand however, is the purpose of the frm_cnt_inc_d FF (code simulates perfectly fine without it). Anyone able to explain if it is indeed necessary?

Best Answer

Are you by any chance counting video frames in a frame buffer? I do this sort of thing all the time.

One way to pass a pulse from one clock domain to another is to turn it into an edge and then do edge detection on it. In domain A, use the pulse to toggle a FF. In domain B, synchronize the output through two FFs. Run the output of the synchronizer through an edge detector (FF and XOR gate).

You'll get a pulse in domain B for every pulse in domain A, as long as the pulses don't occur faster than either clock.


This is needed often enough that I've created a generic module to implement it. xd_ is short for "cross-domain" — I have other modules in this library that perform similar functions.

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;