This clock mux is meant to allow glitch-free muxing between asynchronous clocks clk_a
and clk_b
via a (also asynchronous to both clocks) sel
signal.
I'm looking for any feedback, but in particular I'm looking for confirmation that there can be no output glitches or metastability issues assuming synchronous/related paths meet timing and that one period of either clock is long enough for a 2-flop synchronizer to allow potential metastable states to settle.
Additionally, I'm interested in ways to improve the switchover time or reduce the FF/LUT utilization.
library ieee;
use ieee.std_logic_1164.all;
entity clk_mux is
port(
clk_a : in std_logic;
clk_b : in std_logic;
clk_o : out std_logic;
sel : in std_logic
);
end clk_mux;
architecture rtl of clk_mux is
--combinational
signal clk_i : std_logic;
--async set for disabled clock's lock to ensure internal select can safely be turned low again on first rising edge of the new active clock
signal lock_a : std_logic; --despite being combinational, output always last's exactly 1/2 the period of the other clock and cannot have glitches (inputs never change near each other in time)
signal lock_b : std_logic;
--FF
--ensures the disabled clock can't be re-enabled until the currently enabled clock is disabled
signal force_a_lock : std_logic := '0';
signal force_b_lock : std_logic := '1';
--forces the respective clock being disabled high (and keeps it that way) its rising edge to prevent output glitches
signal force_a_re : std_logic := '0';
signal force_b_re : std_logic := '1';
--releases the forcing on the falling edge (also to prevent output glitches)
signal force_a_fe : std_logic := '0';
signal force_b_fe : std_logic := '1';
--sel is syncronized and pulse extended to 2 cycles of active clock to prevent internal select from changing right as final rising of edge of the old active clock happens
signal sel_sync : std_logic_vector(1 to 3) := (others => '0');
signal sel_ce : std_logic := '0'; --sync. CE used to pulse extend when both high pulses and low pulses need to be extended
begin
process(clk_a, lock_a)
begin
if (lock_a='1') then
force_a_lock <= '1';
elsif (clk_a'event and clk_a='1') then
force_a_lock <= not force_b_re; --release lock once b is being forced. asnyc input with possibly metastable output, so metastability must settle between here and force_a_re
end if;
if (clk_a'event and clk_a='1') then
force_a_re <= force_a_lock or sel_sync(3); --changes to select can only be async when a is already locked, preventing input to force_a_re from changing. No metastability on the output
end if;
if (clk_a'event and clk_a='0') then
force_a_fe <= force_a_re; --re to fe is twice as tight of timing as the other sync ff's. Shouldn't be difficlut to meet with no intermediary logic.
end if;
end process;
process(clk_b, lock_b)
begin
if (lock_b='1') then
force_b_lock <= '1';
elsif (clk_b'event and clk_b='1') then
force_b_lock <= not force_a_re; --release lock once a is being forced. asnyc input, so metastability must settle between here and force_b_re
end if;
if (clk_b'event and clk_b='1') then
force_b_re <= force_b_lock or not sel_sync(3); --changes to select can only be async when b is already locked, preventing input to force_b_re from changing. No metastability on the output
end if;
if (clk_b'event and clk_b='0') then
force_b_fe <= force_b_re; --re to fe is twice as tight of timing as the other sync ff's. Shouldn't be difficlut to meet with no intermediary logic.
end if;
end process;
process(clk_i) --dynamic to ensure that the internal select is syncronous to force_*_re when it makes an immediate difference to the output. Also prevents the internal select from changing while switching over even when one clock is much slower or faster than the other
begin
if (clk_i'event and clk_i='1') then
if (sel_ce='1') then
sel_sync(3) <= sel_sync(2);
end if;
sel_sync(1 to 2) <= sel & sel_sync(1); --sync to avoid metastability from async (to either/both clocks) sel input
sel_ce <= sel_sync(3) xnor sel_sync(2);
end if;
end process;
lock_a <= not force_b_re and force_b_fe;
lock_b <= not force_a_re and force_a_fe; --anync reset for b's lock to ensure internal select can be safely turned high again on first rising edge of active clk_a
clk_i <= (force_a_re or force_a_fe or clk_a) and (force_b_re or force_b_fe or clk_b);
clk_o <= clk_i;
end rtl;
Test:
library ieee;
use ieee.std_logic_1164.all;
entity tb_clk_mux is
end tb_clk_mux;
architecture testbench of tb_clk_mux is
component clk_mux is
port(
clk_a : in std_logic;
clk_b : in std_logic;
clk_o : out std_logic;
sel : in std_logic
);
end component;
signal clk_a : std_logic := '0';
signal clk_b : std_logic := '0';
signal clk_o : std_logic := '0';
signal sel : std_logic := '0';
begin
clk_a <= not clk_a after 10 ns; --periods arbitrary
clk_b <= not clk_b after 23 ns;
sel <= not sel after 200 ns;
uut : clk_mux
port map (
clk_a => clk_a,
clk_b => clk_b,
clk_o => clk_o,
sel => sel
);
end testbench;