1
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ It is necessary. If you don't yet understand the implications of metastability, you are quickly going to get into trouble when designing for multiple clock domains. I suggest you read up on this topic before proceeding. \$\endgroup\$ – Dave Tweed Nov 21 '17 at 15:50
  • \$\begingroup\$ Now that I think about it I do remember hearing about that during one of my courses. Since this is actually the first time I am doing something cross-domain I completely forgot about that. The third FF in the code you have provided is also because of metastability problem, is that correct? Thank you. \$\endgroup\$ – aproxp Nov 21 '17 at 16:05
0
\$\begingroup\$

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;
\$\endgroup\$
  • \$\begingroup\$ Those are actually Ethernet frames, but yes, I am counting the amount of them in a buffer so I guess this would be very similar to your use case. If I understand you correctly (and I am still quite new at this) this will be similar to the third approach with control_synchronizer where frm_cnt_inc is a pulse from domain A. Is the second FF in A supposed to be reset after a confirmation signal from domain B?. frm_cnt_inc_sync will be the first FF in the B domain and I am supposed to add another one after? I will try to test this and edit my question with the code. Thank you for your help! \$\endgroup\$ – aproxp Nov 21 '17 at 14:58
  • \$\begingroup\$ After comparing the code you have provided with the one I have written I want to ask the following: 1. Your implementation is separate component. Since I'm doing it in one entity, I think it is reasonable to use only one FF in domain A (see an edit to the question). 2. Why do you need 3 FF in domain B (wouldn't toggle_a xor toggle_b suffice)? 3. Does your approach of ANY edge detection has benefits over e.g. rising edge detection (apart from requiring only one xor gate instead of an inverter and AND gate)? Thank you for your time. \$\endgroup\$ – aproxp Nov 21 '17 at 15:57
  • \$\begingroup\$ See my comment above about metastability. You REALLY need to understand this, and a behavioral simulation won't demonstrate it. It's a common pitfall for newbies. And don't be afraid to introduce sub-modules. You don't get any prizes for putting everything in one giant module, especially when you have a common function that can be debugged once and reused many, many times. \$\endgroup\$ – Dave Tweed Nov 21 '17 at 16:06
  • 1
    \$\begingroup\$ Just to explicitly answer my own question 3 about ANY edge detection: Apart from requiring only one XOR, using ANY edge detection and toggle signal allows handling of consecutive pulses to be transferred cross-domain. \$\endgroup\$ – aproxp Nov 22 '17 at 6:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.