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).
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?