library IEEE;
use IEEE.std_logic_1164.ALL;

entity x4 is
	port(
		fastclk : in std_logic;
		slowclk : in std_logic;
		out_strobe : out std_logic;
		locked : out std_logic
	);
end entity;

architecture rtl of x4 is
	constant max_factor : natural := 255;

	-- clock domain crossing
	signal slowclk_r, slowclk_rr, slowclk_rrr : std_logic;
	signal pulse, pulse_r : std_logic;

	-- strobe from CDC to PLL
	signal in_strobe : std_logic;

	signal in_counter : natural range 0 to max_factor := 0;
	signal out_counter, out_counter_start : natural range 0 to max_factor / 4 := 0;

	-- error signal from PLL
	signal pll_err : std_logic;
begin
	-- clock domain crossing
	slowclk_r <= slowclk			when rising_edge(fastclk);
	slowclk_rr <= slowclk_r			when rising_edge(fastclk);
	slowclk_rrr <= slowclk_rr		when rising_edge(fastclk);
	pulse <= slowclk_rr and slowclk_rrr;

	-- generate strobe on rising/falling edge
	pulse_r <= pulse			when rising_edge(fastclk);
	in_strobe <= pulse xor pulse_r;

	pll : process(fastclk)
		variable in_counter_overflow : boolean;
		variable out_counter_underflow : boolean;
		variable out_strobe_int : std_logic;

		-- state machine for counting output pulses
		type realign_type is (one, two, three, last, resync, err);
		variable realign : realign_type;
	begin
		if(rising_edge(fastclk)) then
			in_counter_overflow := (in_counter = max_factor);

			-- count upwards between input strobes
			if(in_strobe = '1') then
				in_counter <= 0;
				out_counter_start <= in_counter / 4;
			elsif(in_counter_overflow) then
				null;
			else
				in_counter <= in_counter + 1;
			end if;

			out_counter_underflow := (out_counter = 0);

			if(out_counter_underflow) then
				out_strobe_int := '1';
			else
				out_strobe_int := '0';
			end if;

			-- count downwards between output strobes, resetting on input strobe
			if(out_counter_underflow or in_strobe = '1') then
				out_counter <= out_counter_start;
			else
				out_counter <= out_counter - 1;
			end if;

			-- output strobes are generated from the output counter except
			-- for the last iteration, then we resync
			case realign is
				when one | two | three =>
					out_strobe <= out_strobe_int;
				when last | resync =>
					out_strobe <= in_strobe;
				when err =>
					out_strobe <= '0';
			end case;

			if(in_strobe = '1') then
				-- reset
				realign := one;
			elsif(realign = err) then
				-- latching error
				null;
			elsif(out_counter_underflow) then
				-- next state
				realign := realign_type'succ(realign);
			end if;

			-- export error state
			if(realign = err or in_counter_overflow) then
				pll_err <= '1';
			else
				pll_err <= '0';
			end if;
		end if;
	end process;

	locked <= not pll_err;
end architecture;
