--=============================================================================
--! @file GPP_ipb_rtl.vhd
--=============================================================================
--
-------------------------------------------------------------------------------
-- --
-- University of Santiago de Compostela, High Energy Physics Group.
-- --
------------------------------------------------------------------------------- --
-- VHDL Architecture fmc_mTLU_lib.GPP_ipb.rtl
--
--! @brief GPP - General purpose pulser. Generates a pulse with some conditions given by the IPBus\n
--! IPBus address map:\n
--
--! @author Alvaro Dosil , alvaro.dosil@usc.es
--
--! @date 13:31:12 01/15/2013 
--
--! @version v0.1
--
--! @details
--!
--!
--! <b>Dependencies:</b>\n
--!
--! <b>References:</b>\n
--!
--! <b>Modified by: 
--! Author: 
-------------------------------------------------------------------------------
--! \n\n<b>Last changes:</b>\n
-------------------------------------------------------------------------------
--! @todo Reset the GPP and go back to the Initial state after reach the max pulse number \n
--! 
--------------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

USE work.ipbus.all;



ENTITY GPP_ipb IS
  GENERIC( 
     g_IPBUS_WIDTH      : positive := 32;
	  g_PULSEDELAY_WIDTH : positive := 5;                 --! The maximum delay is 31 clock cicles
	  g_PULSE_TIME_DFLT  : positive := 5;                 --! Pulse width default value  
	  g_SUDTIME_DFLT     : natural  := 0;                 --! Startup dead-time default value
	  g_IPDTIME_DFLT     : natural  := 0;                 --! Interpulse dead-time default value
	  g_REARMTIME_DFLT   : natural  := 0;                 --! Rearm dead-time default value
	  g_PULSEDELAY_DFLT  : natural  := 0;                  --! Pulse delay default value
	  g_NMAXPULSES_DFLT  : natural  := 0;                  --! Max pulse number default value
	  g_WU_DFLT          : std_logic := '0'                --! Trigger update flag default value
   );
   PORT(
	  clk_4x_logic_i      : IN     std_logic;                                      --! Rising edge active
	  clk_logic_i         : IN     std_logic;                                      --! Global clock
     ipbus_clk_i         : IN     std_logic;
     ipbus_i             : IN     ipb_wbus;                                       --! Signals from IPBus core to slave
     ipbus_reset_i       : IN     std_logic;
     logic_reset_i       : IN     std_logic;                                      --! active high. Synchronous with clk_4x_logic
	  trigger_i           : IN     std_logic;                                      --! Trigger input signal
	  Force_PullDown_i    : IN     std_logic;                                      --! Force pull down
	  pulse_o             : OUT    std_logic;                                      --! Pulse output
	  pulse_d_o           : OUT    std_logic;                                      --! Delayed pulse
	  event_number_o      : OUT    std_logic_vector(g_IPBUS_WIDTH-1 downto 0);     --! Event number
	  MaxPulses_o         : OUT    std_logic;                                      --! Max number of pulses reached
	  ipbus_o             : OUT    ipb_rbus                                        --! signals from slave to IPBus core
	);
	
end GPP_ipb;

architecture rtl of GPP_ipb is

  -- Declarations
  signal s_Enable_gpp, s_Enable_gpp_ipb     : std_logic := '0';
  signal s_PulseTime, s_PulseTime_ipb       : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := std_logic_vector( to_unsigned( g_PULSE_TIME_DFLT, g_IPBUS_WIDTH));
  signal s_SuDTime, s_SuDTime_ipb           : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := std_logic_vector( to_unsigned( g_SUDTIME_DFLT, g_IPBUS_WIDTH));
  signal s_IpDTime, s_IpDTime_ipb           : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := std_logic_vector( to_unsigned( g_IPDTIME_DFLT, g_IPBUS_WIDTH));
  signal s_RearmTime, s_RearmTime_ipb       : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := std_logic_vector( to_unsigned( g_REARMTIME_DFLT, g_IPBUS_WIDTH));
  signal s_PulseDelay, s_PulseDelay_ipb     : std_logic_vector(g_PULSEDELAY_WIDTH-1 downto 0) := std_logic_vector( to_unsigned( g_PULSEDELAY_DFLT, g_PULSEDELAY_WIDTH));
  signal s_NMaxPulses, s_NMaxPulses_ipb     : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := std_logic_vector( to_unsigned( g_NMAXPULSES_DFLT, g_IPBUS_WIDTH));
  signal s_event_number, s_event_number_ipb : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := (others=>'0');
  signal s_RstPulseCnt, s_RstPulseCnt_ipb   : std_logic := '0';
  signal s_WU, s_WU_ipb                     : std_logic := g_WU_DFLT;
  signal s_Pulse, s_Pulse4x                 : std_logic := '0';                                             --! Active pulse signal
  signal s_Pulse_d                          : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := (others=>'0');  --! Active pulse signal delayed
  
  signal s_ipbus_ack                        : std_logic := '0';  -- used to produce a delayed IPBus ack signal
  
  
  COMPONENT GPP
  GENERIC( 
    g_IPBUS_WIDTH      : positive := 32;
	 g_PULSEDELAY_WIDTH : positive := 5                 --! The maximum delay is 32 cicles
  );
  PORT( clk_logic_i       : IN     std_logic;                                          -- ! Rising edge active
        Enable_i          : IN     std_logic;                                          --
        Reset_i           : IN     std_logic;                                          --
        RstPulseCnt_i     : IN     std_logic;                                          -- Reset pulse counter
        Trigger_i         : IN     std_logic;                                          -- Trigger input signal
        NMaxPulses_i      : IN     std_logic_vector(g_IPBUS_WIDTH-1 downto 0);         -- Max number of pulses
        SuDTime_i         : IN     std_logic_vector(g_IPBUS_WIDTH-1 downto 0);         -- Startup dead time
        PulseTime4x_i     : IN     std_logic_vector(g_IPBUS_WIDTH-1 downto 0);         -- Pulse time
        IpDTime_i         : IN     std_logic_vector(g_IPBUS_WIDTH-1 downto 0);         -- Interpulse dead time
		  RearmTime_i       : IN     std_logic_vector(g_IPBUS_WIDTH-1 downto 0);         -- Time before rearm after reach the max number of pulses
        Force_PullDown_i  : IN     std_logic;                                          -- Force pull down
        WU_i              : IN     std_logic;                                          -- Output trigger signal with update
        PulseDelay_i      : IN     std_logic_vector(g_PULSEDELAY_WIDTH-1 downto 0);    -- Pulse delay (0 to 31 clock cicles)
		  event_number_o    : OUT    std_logic_vector(g_IPBUS_WIDTH-1 downto 0);         -- Event number
        MaxPulses_o       : OUT    std_logic;                                          -- Maximun pulse reached output
        Pulse_o           : OUT    std_logic;                                          -- Pulse Generated
        Pulse_d_o         : OUT    std_logic                                           -- Pulse generated delayed n clock cicles
        );
  END COMPONENT GPP;

begin
-----------------------------------------------------------------------------
  -- IPBus write
  -----------------------------------------------------------------------------
  ipbus_write: process (ipbus_clk_i)
  begin  -- process ipb_clk_i
    if rising_edge(ipbus_clk_i) then
       s_RstPulseCnt_ipb <= '0';
		 s_event_number_ipb <= s_event_number;
		 
       if (ipbus_i.ipb_strobe = '1' and ipbus_i.ipb_write = '1') then

         case ipbus_i.ipb_addr(3 downto 0) is
           when "0000" => s_PulseTime_ipb  <= ipbus_i.ipb_wdata(g_IPBUS_WIDTH-1 downto 0) ;         -- Generated shutter width
			  when "0001" => s_SuDTime_ipb    <= ipbus_i.ipb_wdata(g_IPBUS_WIDTH-1 downto 0) ;         -- Startup dead time
			  when "0010" => s_IpDTime_ipb    <= ipbus_i.ipb_wdata(g_IPBUS_WIDTH-1 downto 0) ;         -- Interpulse dead time
			  when "0011" => s_PulseDelay_ipb <= ipbus_i.ipb_wdata(g_PULSEDELAY_WIDTH-1 downto 0) ;   -- Delay for the delayed pulse (0 to 31 clk cicles)
			  when "0100" => s_NMaxPulses_ipb <= ipbus_i.ipb_wdata ;    -- Max number of pulses before stop
			  when "0110" => 
			    s_RstPulseCnt_ipb <= ipbus_i.ipb_wdata(0) ;-- Reset pulse counter
			    s_WU_ipb <= ipbus_i.ipb_wdata(1) ;         -- Pulse with update allowed
			  when "0111" => s_RearmTime_ipb  <= ipbus_i.ipb_wdata(g_IPBUS_WIDTH-1 downto 0) ;         -- Rearm dead time
           when others => null;
			  
         end case;
          
       end if;

       s_ipbus_ack <= ipbus_i.ipb_strobe and not s_ipbus_ack;

       if (s_PulseTime_ipb = x"00000000") then
         s_Enable_gpp_ipb <= '0';
       else
         s_Enable_gpp_ipb <= '1';
       end if;
       
    end if;
  end process ipbus_write;

  ipbus_o.ipb_ack <= s_ipbus_ack;
  ipbus_o.ipb_err <= '0';

  -----------------------------------------------------------------------------
  -- IPBUS read
  -----------------------------------------------------------------------------
  with ipbus_i.ipb_addr(3 downto 0) select
    ipbus_o.ipb_rdata <=
      s_PulseTime_ipb                            when "0000",
		s_SuDTime_ipb                              when "0001",
		s_IpDTime_ipb                              when "0010",
		x"000000" & "000" & s_PulseDelay_ipb       when "0011",
		s_NMaxPulses_ipb                           when "0100",
		s_event_number_ipb                         when "0101",
		x"0000000" & "00" & s_WU_ipb & "0"         when "0110",
		s_RearmTime_ipb                            when "0111",
      (others => '1')       when others;


  -- Change clock domain
  p_signals_clk_domain: process (clk_4x_logic_i )
  begin  -- process p_internal_triggers
    if rising_edge(clk_4x_logic_i) then

      s_Enable_gpp  <= s_Enable_gpp_ipb;
		--s_PulseTime   <= s_PulseTime_ipb;
		--s_SuDTime     <= s_SuDTime_ipb;
		--s_IpDTime     <= s_IpDTime_ipb;
		--s_RearmTime   <= s_RearmTime_ipb;
		--s_PulseDelay  <= s_PulseDelay_ipb;
		--s_NMaxPulses  <= s_NMaxPulses_ipb;
		s_WU          <= s_WU_ipb;
		s_RstPulseCnt <= s_RstPulseCnt_ipb;
		
    end if;
  end process p_signals_clk_domain;
  
  register_PulseTime: entity work.sync_reg
	 generic map(g_Data_width => s_PulseTime_ipb'length)
    port map(
      clk_i => clk_4x_logic_i,
		async_i => s_PulseTime_ipb,
		sync_o => s_PulseTime
	   );
		  
  register_IpDTime: entity work.sync_reg
	 generic map(g_Data_width => s_IpDTime_ipb'length)
    port map(
      clk_i => clk_4x_logic_i,
		async_i => s_IpDTime_ipb,
		sync_o => s_IpDTime
	   );
		
  register_SuDTime: entity work.sync_reg
	 generic map(g_Data_width => s_SuDTime_ipb'length)
    port map(
      clk_i => clk_4x_logic_i,
		async_i => s_SuDTime_ipb,
		sync_o => s_SuDTime
	   );
		
  register_RearmTime: entity work.sync_reg
	 generic map(g_Data_width => s_RearmTime_ipb'length)
    port map(
      clk_i => clk_4x_logic_i,
		async_i => s_RearmTime_ipb,
		sync_o => s_RearmTime
	   );

  register_PulseDelay: entity work.sync_reg
	 generic map(g_Data_width => s_PulseDelay_ipb'length)
    port map(
      clk_i => clk_4x_logic_i,
		async_i => s_PulseDelay_ipb,
		sync_o => s_PulseDelay
	   );

  register_NMaxPulses: entity work.sync_reg
	 generic map(g_Data_width => s_NMaxPulses_ipb'length)
    port map(
      clk_i => clk_4x_logic_i,
		async_i => s_NMaxPulses_ipb,
		sync_o => s_NMaxPulses
	   );
		  
  -- Move the pulse length onto the logic clock domain to allow timing
  -- constraints to be met.
--  register_PulseTime : entity work.registerCounter
--    generic map (
--      g_DATA_WIDTH => s_PulseTime_ipb'length )
--    port map (
--    clk_input_i => ipbus_clk_i,
--    data_i      => s_PulseTime_ipb,
--    data_o      => s_PulseTime,
--    clk_output_i  => clk_4x_logic_i
--    );
	 
  -- Move the inter pulse dead time onto the logic clock domain to allow timing
  -- constraints to be met.
--  register_IpDTime : entity work.registerCounter
--    generic map (
--      g_DATA_WIDTH => s_IpDTime_ipb'length )
--    port map (
--    clk_input_i => ipbus_clk_i,
--    data_i      => s_IpDTime_ipb,
--    data_o      => s_IpDTime,
--    clk_output_i  => clk_4x_logic_i
--    );
	 
  -- Move the start up dead time onto the logic clock domain to allow timing
  -- constraints to be met.
--  register_SuDTime : entity work.registerCounter
--    generic map (
--      g_DATA_WIDTH => s_SuDTime_ipb'length )
--    port map (
--    clk_input_i => ipbus_clk_i,
--    data_i      => s_SuDTime_ipb,
--    data_o      => s_SuDTime,
--    clk_output_i  => clk_4x_logic_i
--    );
	 
  -- Move the rearm time onto the logic clock domain to allow timing
  -- constraints to be met.
--  register_RearmTime : entity work.registerCounter
--    generic map (
--      g_DATA_WIDTH => s_RearmTime_ipb'length )
--    port map (
--    clk_input_i => ipbus_clk_i,
--    data_i      => s_RearmTime_ipb,
--    data_o      => s_RearmTime,
--    clk_output_i  => clk_4x_logic_i
--    );
	 
  -- Move the pulse delay onto the logic clock domain to allow timing
  -- constraints to be met.
--  register_PulseDelay : entity work.registerCounter
--    generic map (
--      g_DATA_WIDTH => s_PulseDelay_ipb'length )
--    port map (
--    clk_input_i => ipbus_clk_i,
--    data_i      => s_PulseDelay_ipb,
--    data_o      => s_PulseDelay,
--    clk_output_i  => clk_4x_logic_i
--    );
	 
  -- Move the maximum number of pulses onto the logic clock domain to allow timing
  -- constraints to be met.
--  register_NMaxPulses : entity work.registerCounter
--    generic map (
--      g_DATA_WIDTH => s_NMaxPulses_ipb'length )
--    port map (
--    clk_input_i => ipbus_clk_i,
--    data_i      => s_NMaxPulses_ipb,
--    data_o      => s_NMaxPulses,
--    clk_output_i  => clk_4x_logic_i
--    );


  -----------------------------------------------------------------------------
  -- GPP instatiation
  -- This module works at 4x frequency. It issues 1x length pulses because we 
  -- multiply by 4 all the input intervals.
  -----------------------------------------------------------------------------
  GPP_inst : GPP
  GENERIC MAP( 
    g_IPBUS_WIDTH => g_IPBUS_WIDTH,
	 g_PULSEDELAY_WIDTH => g_PULSEDELAY_WIDTH
  )
  PORT MAP( clk_logic_i       => clk_4x_logic_i,
            Enable_i          => s_Enable_gpp,
            Reset_i           => logic_reset_i,
            RstPulseCnt_i     => s_RstPulseCnt,
            Trigger_i         => trigger_i,
            NMaxPulses_i      => s_NMaxPulses,
            SuDTime_i         => s_SuDTime(g_IPBUS_WIDTH-1-2 downto 0) & "00",
            PulseTime4x_i     => s_PulseTime(g_IPBUS_WIDTH-1-2 downto 0) & "00",
            IpDTime_i         => s_IpDTime(g_IPBUS_WIDTH-1-2 downto 0) & "00",
				RearmTime_i       => s_RearmTime(g_IPBUS_WIDTH-1-2 downto 0) & "00",
            Force_PullDown_i  => Force_PullDown_i,
            WU_i              => s_WU,
            PulseDelay_i      => (others=>'0'), --s_PulseDelay(g_PULSEDELAY_WIDTH-1-2 downto 0) & "00",
				event_number_o    => s_event_number,
            MaxPulses_o       => MaxPulses_o,
            Pulse_o           => s_pulse4x,
            Pulse_d_o         => open --pulse_d_o
            );
				
  	-- Pulse reg. Synchronize the pulse with the 40 MHz clock
	p_reg_pulse : process ( clk_logic_i , logic_reset_i )
   begin  
	  if logic_reset_i = '1' then
	    s_Pulse_d <= (others => '0');
	  
	  elsif rising_edge(clk_logic_i) then
	    s_Pulse <= s_Pulse4x;
		 
       for i in 0 to g_IPBUS_WIDTH-2 loop
         s_Pulse_d(i+1) <= s_Pulse_d(i);
       end loop;
	    s_Pulse_d(0) <= s_Pulse;
	  end if;
	end process p_reg_pulse;

pulse_o <= s_Pulse;
Pulse_d_o <= s_Pulse when s_PulseDelay = "0000000" else
	          s_Pulse_d(to_integer(unsigned(s_PulseDelay))-1);

event_number_o <= s_event_number;

end rtl;

