---------------------------------------------------------------------------
-- (c) 2019 mark watson
-- I am happy for anyone to use this for non-commercial use.
-- If my vhdl files are used commercially or otherwise sold,
-- please contact me for explicit permission at scrameta (gmail).
-- This applies for source and binary form and derived works.
---------------------------------------------------------------------------

LIBRARY ieee;
USE ieee.std_logic_1164.all; 
use ieee.numeric_std.all;
--USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

use ieee.std_logic_misc.all;

LIBRARY work;

ENTITY switch_pal_ntsc IS 
    GENERIC
    (
        CLOCKS : integer :=1;
        SYNC_ON : integer -- Which clock to send reset_n_out on and pll enable
    );
    PORT
    (
        RECONFIG_CLK : IN STD_LOGIC; -- A clock from another PLL
        RESET_N : IN STD_LOGIC;

        PAL : IN STD_LOGIC;

        INPUT_CLK : IN STD_LOGIC;
        PLL_CLKS : OUT STD_LOGIC_VECTOR(CLOCKS-1 downto 0);

        RESET_N_OUT : OUT STD_LOGIC
    );
END switch_pal_ntsc;

ARCHITECTURE vhdl OF switch_pal_ntsc IS 

    -- pal/ntsc switches
    signal pll_reconfig_areset : std_logic;
    signal pll_reconfig_configupdate : std_logic;
    signal pll_reconfig_scanclk : std_logic;
    signal pll_reconfig_scanclkena : std_logic;
    signal pll_reconfig_scandata : std_logic;
    signal pll_reconfig_scandataout : std_logic;
    signal pll_reconfig_scandone : std_logic;
    signal pll_reconfig_busy : std_logic;
    
    signal pll_reconfig_areset_pal : std_logic;
    signal pll_reconfig_configupdate_pal : std_logic;
    signal pll_reconfig_scanclk_pal : std_logic;
    signal pll_reconfig_scanclkena_pal : std_logic;
    signal pll_reconfig_scandata_pal : std_logic;
    signal pll_reconfig_scandataout_pal : std_logic;
    signal pll_reconfig_scandone_pal : std_logic;
    signal pll_reconfig_busy_pal : std_logic;

    signal pll_reconfig_areset_ntsc : std_logic;
    signal pll_reconfig_configupdate_ntsc : std_logic;
    signal pll_reconfig_scanclk_ntsc : std_logic;
    signal pll_reconfig_scanclkena_ntsc : std_logic;
    signal pll_reconfig_scandata_ntsc : std_logic;
    signal pll_reconfig_scandataout_ntsc : std_logic;
    signal pll_reconfig_scandone_ntsc : std_logic;
    signal pll_reconfig_busy_ntsc : std_logic;

    signal pll_pause_counter_reg : std_logic_vector(29 downto 0);
    signal pll_pause_counter_next : std_logic_vector(29 downto 0);    
    
    signal pll_enable_reg : std_logic;
    signal pll_enable_next : std_logic;
    signal pll_enable_reg_sync : std_logic;
    
    signal pll_state_reg : std_logic_vector(3 downto 0);
    signal pll_state_next : std_logic_vector(3 downto 0);
    constant PLL_STATE_WAIT : std_logic_vector(3 downto 0) := "0000";
    constant PLL_STATE_DISABLE : std_logic_vector(3 downto 0) := "0001";
    constant PLL_STATE_DISABLE_WAIT1 : std_logic_vector(3 downto 0) := "0010";
    constant PLL_STATE_DISABLE_WAIT2 : std_logic_vector(3 downto 0) := "0011";
    constant PLL_STATE_DISABLE_WAIT3 : std_logic_vector(3 downto 0) := "0100";
    constant PLL_STATE_DISABLE_WAIT4 : std_logic_vector(3 downto 0) := "0101";
    constant PLL_STATE_RECONFIG1 : std_logic_vector(3 downto 0) := "0110";
    constant PLL_STATE_RECONFIG1_WAIT_CONFIG : std_logic_vector(3 downto 0) := "0111";
    constant PLL_STATE_RECONFIG1_WAIT_LOCK : std_logic_vector(3 downto 0) := "1000";
    constant PLL_STATE_PAUSE1 : std_logic_vector(3 downto 0) := "1001";  
    
    signal pll_trigger_reconfig : std_logic;  
    signal pll_trigger_reconfig_downstream : std_logic;  
    
    signal pll_upstream_reset : std_logic;
    signal pll_upstream_reset_pal : std_logic;
    signal pll_upstream_reset_ntsc : std_logic;
    signal pll_downstream_reset : std_logic;
    
    signal reset_n_next : std_logic;
    signal reset_n_reg : std_logic;
    signal reset_n_reg_sync : std_logic;
    
	 signal CLK_PLL1 : std_logic;
	 signal PLL_LOCKED1 : std_logic;
	 signal PLL_LOCKED : std_logic;	 
	 
    signal CLK_RAW : std_logic_vector(5 downto 0);
  
    signal pal_fpga_sync : std_logic;
  
    signal reconfig_to_pal_reg : std_logic;
    signal reconfig_to_pal_next : std_logic;
    
BEGIN 

    generic_pll : entity work.pal_pll
    PORT MAP(inclk0 => INPUT_CLK,
             c0 => CLK_PLL1,
             locked => PLL_LOCKED1,
             areset => pll_reconfig_areset,
             -- from reconfig
             configupdate => pll_reconfig_configupdate,
             scanclk => pll_reconfig_scanclk,
             scanclkena => pll_reconfig_scanclkena,
             scandata => pll_reconfig_scandata,    
            -- back to reconfig 
             scandataout => pll_reconfig_scandataout,
             scandone => pll_reconfig_scandone         
             );
    generic_pll2 : entity work.pll_downstream_pal
    PORT MAP(inclk0 => CLK_PLL1,
             c0 => CLK_RAW(0),
             c1 => CLK_RAW(1),
             c2 => CLK_RAW(2),
             c3 => CLK_RAW(3),
             c4 => CLK_RAW(4), 
             areset => pll_downstream_reset,
             locked => PLL_LOCKED
             );
     
     process(reconfig_to_pal_reg,
        pll_reconfig_scanclk_pal, pll_reconfig_scanclkena_pal, pll_reconfig_busy_pal, pll_reconfig_areset_pal,pll_reconfig_scandata_pal,pll_reconfig_configupdate_pal,
        pll_reconfig_scanclk_ntsc, pll_reconfig_scanclkena_ntsc, pll_reconfig_busy_ntsc, pll_reconfig_areset_ntsc,pll_reconfig_scandata_ntsc,pll_reconfig_configupdate_ntsc,
        pll_upstream_reset,pll_reconfig_scandone,pll_reconfig_scandataout
    )
     begin
        pll_upstream_reset_pal<= '0';
        pll_upstream_reset_ntsc <= '0';            
        pll_reconfig_scandone_pal <= '0';
        pll_reconfig_scandone_ntsc <= '0';
        pll_reconfig_scandataout_pal <= '0';
        pll_reconfig_scandataout_ntsc <= '0';
     
        if (reconfig_to_pal_reg='1') then
            pll_reconfig_scanclk<=pll_reconfig_scanclk_pal;
            pll_reconfig_scanclkena<=pll_reconfig_scanclkena_pal;
            pll_reconfig_scandata<= pll_reconfig_scandata_pal;
            pll_reconfig_busy <= pll_reconfig_busy_pal;
            pll_reconfig_areset <= pll_reconfig_areset_pal;                
            pll_reconfig_configupdate <= pll_reconfig_configupdate_pal;
                            
            pll_upstream_reset_pal <= pll_upstream_reset;
            pll_reconfig_scandone_pal <= pll_reconfig_scandone;
            pll_reconfig_scandataout_pal <= pll_reconfig_scandataout;                
        else
            pll_reconfig_scanclk<=pll_reconfig_scanclk_ntsc;
            pll_reconfig_scanclkena<=pll_reconfig_scanclkena_ntsc;
            pll_reconfig_scandata<= pll_reconfig_scandata_ntsc;
            pll_reconfig_busy <= pll_reconfig_busy_ntsc;
            pll_reconfig_areset <= pll_reconfig_areset_ntsc;        
            pll_reconfig_configupdate <= pll_reconfig_configupdate_ntsc;                
            
            pll_upstream_reset_ntsc <= pll_upstream_reset;
            pll_reconfig_scandone_ntsc  <= pll_reconfig_scandone;
            pll_reconfig_scandataout_ntsc <= pll_reconfig_scandataout;
        end if;
     end process;
             
     generic_pll1_reconfig : entity work.video_pll_reconfig
     PORT MAP
     ( 
         busy    => pll_reconfig_busy_ntsc,
         clock => RECONFIG_CLK,
         counter_param    => (OTHERS => '0'),
         counter_type    => (OTHERS => '0'),
         data_in    => (OTHERS => '0'),
         read_param    => '0',
         write_param => '0',
         --data_out    :    OUT  STD_LOGIC_VECTOR (8 DOWNTO 0);
         pll_areset    => pll_reconfig_areset_ntsc,
         pll_areset_in    => pll_upstream_reset_ntsc,
         pll_configupdate    => pll_reconfig_configupdate_ntsc,
         pll_scanclk => pll_reconfig_scanclk_ntsc,
         pll_scanclkena => pll_reconfig_scanclkena_ntsc,
         pll_scandata => pll_reconfig_scandata_ntsc,
         pll_scandataout => pll_reconfig_scandataout,
         pll_scandone    => pll_reconfig_scandone,
         
         reconfig => pll_trigger_reconfig,
         reset => not(RESET_N)
     );     

     generic_pll1_reconfig_pal : entity work.video_pll_reconfig_pal
     PORT MAP
     ( 
         busy    => pll_reconfig_busy_pal,
         clock => RECONFIG_CLK,
         counter_param    => (OTHERS => '0'),
         counter_type    => (OTHERS => '0'),
         data_in    => (OTHERS => '0'),
         read_param    => '0',
         write_param => '0',
         --data_out    :    OUT  STD_LOGIC_VECTOR (8 DOWNTO 0);
         pll_areset    => pll_reconfig_areset_pal,
         pll_areset_in    => pll_upstream_reset_pal,
         pll_configupdate    => pll_reconfig_configupdate_pal,
         pll_scanclk => pll_reconfig_scanclk_pal,
         pll_scanclkena => pll_reconfig_scanclkena_pal,
         pll_scandata => pll_reconfig_scandata_pal,
         pll_scandataout => pll_reconfig_scandataout,
         pll_scandone    => pll_reconfig_scandone,
         
         reconfig => pll_trigger_reconfig,
         reset => not(RESET_N)
     ); 

     process(RECONFIG_CLK,RESET_N)
     begin
        if (RESET_N='0') then
            pll_state_reg <= PLL_STATE_WAIT;
            pll_pause_counter_reg<= (others=>'0');
            pll_enable_reg<='0';
            reset_n_reg <= '0';
            
            reconfig_to_pal_reg <= '1';
            
        elsif (RECONFIG_CLK'event and RECONFIG_CLK='1') then
            pll_state_reg <= pll_state_next;
            pll_pause_counter_reg <= pll_pause_counter_next;
            pll_enable_reg <= pll_enable_next;
            reset_n_reg <= reset_n_next;        
    
            reconfig_to_pal_reg <= reconfig_to_pal_next;
        end if;
     end process;        
     
    pal_synchronizer : entity work.synchronizer
              port map (clk=>RECONFIG_CLK, raw=>pal, sync=>pal_fpga_sync);                   
     
    process (pll_state_reg, pal_fpga_sync, pll_pause_counter_reg,pll_enable_reg, PLL_LOCKED1, PLL_LOCKED, pll_reconfig_busy,reconfig_to_pal_reg)
    begin
              pll_state_next <= pll_state_reg;
              reset_n_next <= '1';
              reconfig_to_pal_next <= reconfig_to_pal_reg;
              
              pll_pause_counter_next <= std_logic_vector(unsigned(pll_pause_counter_reg)+1);                        

              pll_trigger_reconfig <= '0';
              pll_trigger_reconfig_downstream <= '0';
              pll_downstream_reset <= '0';
              pll_upstream_reset <= '0';
              
              pll_enable_next <= pll_enable_reg;                                                          

              case pll_state_reg is
                         when PLL_STATE_WAIT =>
                                    pll_enable_next <= '1';
                                    reset_n_next <= PLL_LOCKED;
                                    if (not(pal_fpga_sync = reconfig_to_pal_reg) and PLL_LOCKED='1' and PLL_LOCKED1='1') then
                                              reconfig_to_pal_next <= pal_fpga_sync;
                                              pll_state_next <= PLL_STATE_DISABLE;
                                              pll_enable_next <= '0';
                                    end if;

                                    
                         -- Wait for pll_enable_next to make through synchronizer
              when PLL_STATE_DISABLE =>        
                                    pll_state_next <= PLL_STATE_DISABLE_WAIT1;                                                    
                                    
              when PLL_STATE_DISABLE_WAIT1 =>        
                                    pll_state_next <= PLL_STATE_DISABLE_WAIT2;            

              when PLL_STATE_DISABLE_WAIT2 =>        
                                    pll_state_next <= PLL_STATE_DISABLE_WAIT3;

              when PLL_STATE_DISABLE_WAIT3 =>        
                                    pll_state_next <= PLL_STATE_DISABLE_WAIT4;                                        
                                    
              when PLL_STATE_DISABLE_WAIT4 =>        
                                    pll_state_next <= PLL_STATE_RECONFIG1;                                                
                                    
                         -- Set params for upstream pll and reset downstream pll
              when PLL_STATE_RECONFIG1 =>        
												pll_trigger_reconfig <= '1';
                                    pll_downstream_reset <= '1';
                                    pll_state_next <= PLL_STATE_RECONFIG1_WAIT_CONFIG;
                                                                         
              when PLL_STATE_RECONFIG1_WAIT_CONFIG =>        
                                    --pll_downstream_reset <= '1';
                                    if (pll_reconfig_busy = '0') then                                        
                                        pll_state_next <= PLL_STATE_RECONFIG1_WAIT_LOCK;
                                    end if;    

              when PLL_STATE_RECONFIG1_WAIT_LOCK =>
                                    --pll_downstream_reset <= '1';
                                    if (PLL_LOCKED1 = '1') then
                                        pll_state_next <= PLL_STATE_PAUSE1;
                                        pll_pause_counter_next <= (others=>'0');
                                        --pll_downstream_reset <= '0';
                                    end if;                    
                                
              when PLL_STATE_PAUSE1 =>
                                    if (pll_pause_counter_reg(24)='1') then
                                        pll_state_next <= PLL_STATE_WAIT;
                                    end if;                                        

              when others =>
                                    pll_state_next <= PLL_STATE_WAIT;

              end case;                 

    end process;

    reset_n_out <= reset_n_reg_sync;

    reset_synchronizer : entity work.synchronizer
              port map (clk=>CLK_RAW(SYNC_ON), raw=>reset_n_reg, sync=>reset_n_reg_sync); 

    pll_enable_synchronizer : entity work.synchronizer
              port map (clk=>CLK_RAW(SYNC_ON), raw=>pll_enable_reg, sync=>pll_enable_reg_sync); 
        
   GEN_CLKCTRL:
   for I in 0 to (CLOCKS-1) generate
        CLKCTRLX : entity work.clkctrl
        port map (
                inclk  => CLK_RAW(I),
                ena    => pll_enable_reg_sync,
                outclk => PLL_CLKS(I)
        );
   end generate GEN_CLKCTRL;

end vhdl;

