-- 
-- phase_shifter.vhd
-- 
-- implements interface between w5300_modul.vhd 
-- and clock_generator_variable_PS_struct.vhd 
-- 
library ieee;
use ieee.std_logic_1164.all;
use IEEE.NUMERIC_STD.all;

library FACT_FAD_lib;
use FACT_FAD_lib.fad_definitions.all;


ENTITY phase_shifter IS

	PORT(
		CLK : IN std_logic;
		rst : out std_logic := '0'; --asynch in of DCM
		
		-- interface to: clock_generator_variable_PS_struct.vhd 
		PSCLK : OUT std_logic;
		PSEN : OUT std_logic := '0'; 
		PSINCDEC : OUT std_logic := '1'; -- default is 'incrementing'
		PSDONE : IN std_logic; -- will pulse once, if phase shifting was done.
		LOCKED : IN std_logic; -- when is this going high?
		
		
		-- interface to: w5300_modul.vhd
		shift_phase : IN std_logic;
		direction : IN std_logic; -- corresponds to 'PSINCDEC'
		reset_DCM : in std_logic; -- asynch in: orders us, to reset the DCM
		
		-- status:
		shifting : OUT std_logic := '0';
		ready : OUT std_logic := '0';
		
		locked_status_o : OUT std_logic;
		ready_status_o : OUT std_logic;
		offset : OUT std_logic_vector (7 DOWNTO 0) := (OTHERS => '0')
		
		
	);
END phase_shifter;

-- usage:
-- w5300_modul will set 'direction' to desired direction and pulse 'shift_phase' once
-- to initiate a phase shifting process.
-- while phase shifting, 'shifting' will show '1' and further pulses will be discarded.
-- 'offset' shows the number of phase_shift steps that have been performed. 
-- ready is high, when DCM is LOCKED and not phase_shifting.
-- DCM_status is a copy, of the STATUS input.
-- DCM_locked is a copy of LOCKED
-- 
-- how it works internally:
-- PSCLK is connected to clk, always.
--
-- main FSM goes from init to ready, when LOCKED is high.
-- main FSM goes from ready to shifting, when shift_phase goes high.
-- 		when in shifting: 
--			PSINCDEC is set to 'direction'
--			PSEN is set high
--			shifting is set high
--			next state waiting-for-done is entered
--
--		when in waiting-for-done:
--			PSEN is set low
--			if PSDONE is found to be high.
--			shifting is set low and state ready is entered.
--
--	whenever LOCKED goes low FSM enters 'init' state
--		when in init state:
--			'ready' is set low

architecture first_behave of phase_shifter is
	constant OFFS_MIN : integer := -51;
	constant OFFS_MAX : integer := 51;
		
	type states is (INIT, READY_STATE, PRE_SHIFTING_STATE, SHIFTING_STATE, WAITINGFORDONE, RESET_STATE); 
	signal state : states := INIT;
	
	signal local_direction : std_logic;
	signal offset_int : integer range OFFS_MIN to OFFS_MAX := 0;
	signal lower_limit_reached : std_logic := '0';
	signal upper_limit_reached : std_logic := '0';
		
	signal reset_dcm_sr : std_logic_vector(1 downto 0) := "00";
	signal shift_phase_sr : std_logic_vector(1 downto 0) := "00";
	signal ready_int : std_logic := '0';
begin

-- concurrent statements:
ready <= ready_int;
PSCLK <= CLK;
offset <= std_logic_vector(to_signed(offset_int,8));
locked_status_o <= LOCKED;
ready_status_o <= ready_int;

	-- MAIN FSM
	FSM: process(CLK)
	begin
	
	if rising_edge(CLK) then
	reset_dcm_sr <= reset_dcm_sr(1) & reset_DCM;  --synch in
	shift_phase_sr <= shift_phase_sr(1) & shift_phase; --synch in
	
		case state is
		-- INIT state: here the FSM is idling, when LOCKED is not HIGH.
		when INIT =>
			rst <= '0';
			ready_int <= '0';
			shifting <= '0';
			PSEN <= '0';
			offset_int <= 0;
			if (LOCKED = '1') then
				state <= READY_STATE;
			else 
				state <= INIT;
			end if;
			
		-- RESET state: when ordered to do so: DCM is reseted and FSM send back to INIT. 
		when RESET_STATE =>
			rst <= '1';
			ready_int <= '0';
			shifting <= '0';
			PSEN <= '0';
			state <= INIT;
			
		-- READY_STATE state: here FSM is waiting for the 'shift_phase' to go high, or
		-- if reset_DCM goes high, we will reset the DCM and go back to init.
		when READY_STATE =>
			ready_int <= '1';
			shifting <= '0';
			PSEN <= '0';

			lower_limit_reached <='0';
			upper_limit_reached <='0';
			if (offset_int = OFFS_MIN) then 
				lower_limit_reached <= '1';
			elsif (offset_int = OFFS_MAX) then
				upper_limit_reached <= '1';
			end if;
			
			if (shift_phase_sr = "01") then
				local_direction <= direction; -- direction is sampled, once 'shift_phase' goes high
				state <= PRE_SHIFTING_STATE;
			else
				state <= READY_STATE;
			end if;
			
			if (reset_dcm_sr = "01") then	
				state <= RESET_STATE;
			end if;
		
		-- checks if possible to shift in asked direction. If not ... back to READY.
		when PRE_SHIFTING_STATE =>
		ready_int <= '0';
			if (local_direction = '1' and upper_limit_reached = '1') or
				(local_direction = '0' and lower_limit_reached = '1') then
				state <= READY_STATE;
			else
				state <= SHIFTING_STATE;
			end if;
		
		
		-- SHIFTING_STATE state: PSENC is set HIGH here and set low in the next state.
		when SHIFTING_STATE =>
			ready_int <= '0';
			shifting <= '1';
			PSEN <= '1';
			PSINCDEC <= local_direction; -- this is the value of 'direction', when 'shift_phase' went up.
			state <= WAITINGFORDONE;
		
		-- WAITINGFORDONE state: PSENC is set LOW, ensuring that is was high only one clock cycle.
		when WAITINGFORDONE =>
			ready_int <= '0';
			shifting <= '1';
			PSEN <= '0';
			if (PSDONE = '1') then
				state <= READY_STATE;
				if (local_direction = '1') then
					offset_int <= offset_int + 1;
				else
					offset_int <= offset_int - 1;
				end if;
			else
				state <= WAITINGFORDONE;
			end if;
			
		-- does this work????
		when others =>
			state <= RESET_STATE;
		
		end case;
		end if;
	end process;

end first_behave;