--=============================================================================
--! @file eventFormatter_rtl.vhd
--=============================================================================
--
-------------------------------------------------------------------------------
-- --
-- University of Bristol, High Energy Physics Group.
-- --
------------------------------------------------------------------------------- --
-- VHDL Architecture fmc_mTLU_lib.eventFormatter.rtl
--
--! @brief Takes the data delivered on each trigger and turns it into a 64-bit
--!        word\n
--! \n
--
--! @author David Cussans , David.Cussans@bristol.ac.uk
--
--! @date 15:10:35 11/09/12
--
--! @version v0.1
--
--! @details
--!
--!
--! <b>Dependencies:</b>\n
--!
--! <b>References:</b>\n
--!
--! <b>Modified by: Alvaro Dosil , alvaro.dosil@usc.es </b>\n
--! Author: 
-------------------------------------------------------------------------------
--! \n\n<b>Last changes:</b>\n
-------------------------------------------------------------------------------
--! @todo Add more input data: \n
--! a) shutter signals. One per DUT. ?? \n
--! b) input levels ( for recording edge data ). Record rising and falling edges\n
--! c) veto levels. One per DUT. Record rising and falling edges.\n
--! \n
--! Add backpressure output if short FIFOs fill up? But many inputs won't
--! respond - e.g. scintillator inputs. This data will be lost....
--------------------------------------------------------------------------------
-- 
-- Created using using Mentor Graphics HDL Designer(TM) 2010.3 (Build 21)
--
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

USE work.fmcTLU.all;
USE work.ipbus.all;

ENTITY eventFormatter IS
   GENERIC( 
      g_EVENT_DATA_WIDTH    : positive := 64;
      g_IPBUS_WIDTH         : positive := 32;
		g_COUNTER_TRIG_WIDTH  : positive := 32;
		g_COUNTER_WIDTH       : positive := 12;
		g_EVTTYPE_WIDTH       : positive := 4;               -- Width of the event type word
		--g_NUM_INPUT_TYPES     : positive := 4;               -- Number of different input types (trigger, shutter, edge...)
		g_NUM_EDGE_INPUTS     : positive := 4;               -- Number of edge inputs
      g_NUM_TRIG_INPUTS     : positive := 5                -- Number of trigger inputs
   );
   PORT( 
      clk_4x_logic_i         : IN     std_logic;                                          -- ! Rising edge active
		ipbus_clk_i            : IN     std_logic;                                          
      logic_strobe_i         : IN     std_logic;                                          -- ! Pulses high once every 4 cycles of clk_4x_logic
		logic_reset_i          : IN     std_logic;                                          -- goes high to reset counters. Synchronous with clk_4x_logic
      
		trigger_i              : IN     std_logic;                                          --! goes high to load trigger data. One cycle of clk_4x_logic
      trigger_times_i        : IN     t_triggerTimeArray (g_NUM_TRIG_INPUTS-1 DOWNTO 0);  -- Array of trigger times ( w.r.t. logic_strobe)
      trigger_inputs_fired_i : IN     std_logic_vector (g_NUM_TRIG_INPUTS-1 DOWNTO 0);     -- high for each input that "fired"
		trigger_cnt_i          : IN     std_logic_vector (g_COUNTER_TRIG_WIDTH-1 DOWNTO 0);
		
		shutter_i              : IN     std_logic;
		shutter_cnt_i          : IN     std_logic_vector(g_COUNTER_WIDTH-1 DOWNTO 0);
		
		spill_i                : IN     std_logic;
		spill_cnt_i            : IN     std_logic_vector(g_COUNTER_WIDTH-1 DOWNTO 0);
		
		edge_rise_i            : IN     std_logic_vector (g_NUM_EDGE_INPUTS-1 DOWNTO 0);    -- ! High when rising edge
		edge_fall_i            : IN     std_logic_vector (g_NUM_EDGE_INPUTS-1 DOWNTO 0);    -- ! High when falling edge
		edge_rise_time_i       : IN     t_triggerTimeArray (g_NUM_EDGE_INPUTS-1 DOWNTO 0);  -- Array of edge times ( w.r.t. logic_strobe)
		edge_fall_time_i       : IN     t_triggerTimeArray (g_NUM_EDGE_INPUTS-1 DOWNTO 0);  -- Array of edge times ( w.r.t. logic_strobe)
		
		ipbus_i                : IN     ipb_wbus;
		ipbus_o                : OUT    ipb_rbus;
		data_strobe_o          : OUT    std_logic;                                          -- goes high when data ready to load into event buffer
      event_data_o           : OUT    std_logic_vector (g_EVENT_DATA_WIDTH-1 DOWNTO 0);
     event_number_i        : in    std_logic_vector (g_IPBUS_WIDTH-1 DOWNTO 0);
     trigger_count_o        : OUT    std_logic_vector (g_IPBUS_WIDTH-1 DOWNTO 0)
   );

-- Declarations

END ENTITY eventFormatter ;

--
ARCHITECTURE rtl OF eventFormatter IS

  -- add to ports
  -- trigger_i           : OUT    std_logic_vector (g_NUM_INPUTS-1 DOWNTO 0);    -- ! High when trigger active
  -- signal inputs_triggered_i :    std_logic_vector (g_NUM_INPUTS-1 DOWNTO 0);    -- ! High when trigger active
  -- signal logic_reset_i : std_logic := '0';  -- put into ports...
  
  constant c_NUM_INPUT_TYPES     : positive := 3+g_NUM_EDGE_INPUTS;               -- Number of different input types (trigger, shutter, edge(0), edge(1)...)
  
  type t_fifo_io is array(natural range <>) of std_logic_vector(g_EVENT_DATA_WIDTH-1 downto 0);
  type t_evttype is array(natural range <>) of std_logic_vector(g_EVTTYPE_WIDTH-1 downto 0);
  type t_var is array(natural range <>) of std_logic_vector(g_COUNTER_WIDTH-1 downto 0);
  -- Input types:
  -- 0 - Trigger
  -- 1 - Shutter
  -- 2 - Edge signal
  -- 3 - Spill
  
  --! delayed strobes
  signal s_event_strobe , s_event_strobe_d1 ,s_event_strobe_d2 ,s_event_strobe_d3 : std_logic := '0';
  signal shutter_i_d1, shutter_i_d2, edge_i_d1, edge_i_d2, spill_i_d1, spill_i_d2 : std_logic := '0';
  
  signal s_evttype : t_evttype(3+g_NUM_EDGE_INPUTS-1 downto 0) := (others=>(others=>'0'));   -- Event type
  -- 0000 trigger internal
  -- 0001 trigger external
  -- 0010 shutter falling
  -- 0011 shutter rising
  -- 0100 edge falling
  -- 0101 edge rising
  -- 0111 spill on
  -- 0110 spill off
  
  signal s_var        : t_var(3+g_NUM_EDGE_INPUTS-1 downto 0) :=(others=>(others=>'0'));       -- 12b different in every input
  
  signal s_FIFO_wr       : std_logic_vector(3+g_NUM_EDGE_INPUTS-1 downto 0) := (others=>'0');     -- FIFO write signal
  signal s_FIFO_rd       : std_logic_vector(3+g_NUM_EDGE_INPUTS-1 downto 0) := (others=>'0');     -- FIFO read signals
  signal s_FIFO_rd_d     : std_logic_vector(3+g_NUM_EDGE_INPUTS-1 downto 0) := (others=>'0');     -- FIFO read signals delayed
  signal s_FIFO_rd_mask  : unsigned(3+g_NUM_EDGE_INPUTS-1 downto 0) := (0 =>'1',others=>'0'); --(3+g_NUM_EDGE_INPUTS-1 =>'1',others=>'0');     -- FIFO read mask
  signal s_FIFO_empty    : std_logic_vector(3+g_NUM_EDGE_INPUTS-1 downto 0) := (others=>'0');     -- FIFO empty signals
  signal s_FIFO_i        : t_fifo_io(3+g_NUM_EDGE_INPUTS-1 downto 0) :=(others=>(others=>'0'));   -- FIFO input data
  signal s_FIFO_o        : t_fifo_io(3+g_NUM_EDGE_INPUTS-1 downto 0) :=(others=>(others=>'0'));   -- FIFO output data
  
  
  signal s_data_o        : std_logic_vector(g_EVENT_DATA_WIDTH-1 DOWNTO 0);         -- Multiplexed data from FIFOs
  --signal s_data_o_mask : unsigned(3+g_NUM_EDGE_INPUTS-1 downto 0) := (g_NUM_TRIG_INPUTS+g_NUM_EDGE_INPUTS-1 =>'1',others=>'0');     -- Output data mask
  
  constant c_COARSE_TIMESTAMP_WIDTH : positive := 48;  -- ! Number of bits in 40MHz timestamp
  signal s_coarse_timestamp : unsigned(c_COARSE_TIMESTAMP_WIDTH-1 downto 0) := (others => '0');  -- 40MHz timestamp.
--  signal s_event_number : unsigned(g_IPBUS_WIDTH-1 downto 0) := (others => '0');  -- increment after each post-veto trigger.

  signal s_word0 , s_word1, s_word2 , s_word3  : std_logic_vector(g_EVENT_DATA_WIDTH-1 downto 0) := (others => '0');  -- clocked to data output on logic_strobe , s_logic_strobe_d1 , etc.

  signal s_ipbus_ack      : std_logic := '0';  -- used to produce a delayed IPBus ack signal
  signal s_enable_record, s_enable_record_ipb : std_logic_vector(g_IPBUS_WIDTH-1 downto 0) := (2 downto 0 => '1', others=>'0'); -- Enable data record
  signal s_enable_trigger : std_logic := '1'; -- Enable trigger record
  signal s_enable_shutter : std_logic := '1'; -- Enable shutter record
  signal s_enable_spill   : std_logic := '1'; -- Enable spill record
  signal s_enable_edges   : std_logic_vector(g_NUM_EDGE_INPUTS-1 downto 0) := (others=>'0'); -- Enable edges record
  
BEGIN

  -----------------------------------------------------------------------------
  -- IPBus write
  -----------------------------------------------------------------------------
  ipbus_write: process (ipbus_clk_i)
  begin  -- process ipb_clk_i
    if rising_edge(ipbus_clk_i) then

       if (ipbus_i.ipb_strobe = '1' and ipbus_i.ipb_write = '1') then

         case ipbus_i.ipb_addr(2 downto 0) is
           when "000" => s_enable_record_ipb <= ipbus_i.ipb_wdata ; -- Enable data record
           when others => null;
         end case;
          
       end if;

       s_ipbus_ack <= ipbus_i.ipb_strobe and not s_ipbus_ack;
       
    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(2 downto 0) select
    ipbus_o.ipb_rdata <=
      s_enable_record_ipb                     when "000",
      (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_record  <= s_enable_record_ipb;
		
		s_enable_trigger <= s_enable_record(0);
		s_enable_shutter <= s_enable_record(1);
		s_enable_spill <= s_enable_record(2);
		s_enable_edges <= s_enable_record(g_NUM_EDGE_INPUTS-1+3 downto 3);
		
    end if;
  end process p_signals_clk_domain;


  -- purpose: generate delayed strobes, generate event types and write enable flags to the FIFOs
  -- type   : combinational
  -- inputs : clk_4x_logic_i , s_FIFO_rd
  -- outputs: s_event_strobe_d1 , s_event_strobe_d2 , s_event_strobe_d3 , s_FIFO_rd_d , s_**_evttype
  p_reg: process (clk_4x_logic_i)
  begin  -- process p_generate_strobes
    if rising_edge(clk_4x_logic_i) then      
      s_event_strobe_d1 <= trigger_i and s_enable_trigger;
      s_event_strobe_d2 <= s_event_strobe_d1;
		if to_unsigned(g_NUM_TRIG_INPUTS,4) > 4 then
		  s_event_strobe_d3 <= s_event_strobe_d2;
		end if;
		
		s_FIFO_rd_d <= s_FIFO_rd;
		
		shutter_i_d1 <= shutter_i and s_enable_shutter;
		shutter_i_d2 <= shutter_i_d1;

		spill_i_d1 <= spill_i and s_enable_spill;
		spill_i_d2 <= spill_i_d1;
		
    end if;
  end process p_reg;
  
  
-------------------------------------------------------------------------------
-- Trigger event formater
-------------------------------------------------------------------------------
  s_evttype(0) <= "0000" when unsigned(trigger_inputs_fired_i) = 0 and trigger_i = '1' else
						"0001";
  s_var(0)     <= trigger_inputs_fired_i & std_logic_vector(to_unsigned(0,s_var(0)'length-g_NUM_TRIG_INPUTS));

  s_word0 <= s_evttype(0) & s_var(0) & std_logic_vector(s_coarse_timestamp);
  
  s_word1 <= "000" & trigger_times_i(0) & "000" & trigger_times_i(1) &
             "000" & trigger_times_i(2) & "000" & trigger_times_i(3) &
             trigger_cnt_i;
  s_word2 <= "000" & trigger_times_i(4) & X"00000000000000"           when (g_NUM_TRIG_INPUTS = 5) else
             "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
				 X"000000000000"                                          when (g_NUM_TRIG_INPUTS = 6) else
				 "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
				 "000" & trigger_times_i(6) & X"0000000000"               when (g_NUM_TRIG_INPUTS = 7) else
				 "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
				 "000" & trigger_times_i(6) & "000" & trigger_times_i(7) &
				 X"00000000"                                              when (g_NUM_TRIG_INPUTS = 8) else
				 "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
				 "000" & trigger_times_i(6) & "000" & trigger_times_i(7) &
				 "000" & trigger_times_i(8) & X"000000"                   when (g_NUM_TRIG_INPUTS = 9) else
				 "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
				 "000" & trigger_times_i(6) & "000" & trigger_times_i(7) &
				 "000" & trigger_times_i(8) & "000" & trigger_times_i(9) &
				 X"0000"                                                  when (g_NUM_TRIG_INPUTS = 10) else
				 "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
				 "000" & trigger_times_i(6) & "000" & trigger_times_i(7) &
				 "000" & trigger_times_i(8) & "000" & trigger_times_i(9) &
				 "000" & trigger_times_i(10)& X"00"                       when (g_NUM_TRIG_INPUTS = 11) else
             "000" & trigger_times_i(4) & "000" & trigger_times_i(5) &
             "000" & trigger_times_i(6) & "000" & trigger_times_i(7) &
				 "000" & trigger_times_i(8) & "000" & trigger_times_i(9) &
             "000" & trigger_times_i(10)& "000" & trigger_times_i(11) when (g_NUM_TRIG_INPUTS = 12) else
				 (others=>'0');

  --! Could also output data on trigger_i , but let's use the delayed signals. \n
  --! The counters are one cicle delayed from the signal generation
  s_FIFO_i(0) <= s_word0 when (s_event_strobe_d1 = '1') else
                 s_word1 when (s_event_strobe_d2 = '1') else
					  s_word2 when (s_event_strobe_d3 = '1') else
                 (others => '0');
  
  --! If there is data in the FIFO output we put the strobe signal at high level.
  s_FIFO_wr(0) <= s_event_strobe_d1 or s_event_strobe_d2 or s_event_strobe_d3;
		
  s_FIFO_wr(1) <= shutter_i_d1 xor shutter_i_d2;
  s_evttype(1) <= "0011" when shutter_i_d1 = '1' and shutter_i_d2 = '0' else
                  "0010" when shutter_i_d1 = '0' and shutter_i_d2 = '1' else
						(others=>'0');
  s_var(1) <= shutter_cnt_i;
		
  edge_formatting : for i in 0 to (g_NUM_EDGE_INPUTS-1) generate		
    s_FIFO_wr(i+2) <= (edge_rise_i(i) or edge_fall_i(i)) and s_enable_edges(i);
    s_evttype(i+2) <= "0101" when edge_rise_i(i) = '1' else
                      "0100" when edge_fall_i(i) = '1' else
                      (others=>'0');
    s_var(i+2) <= std_logic_vector(to_unsigned(i,4)) & "000" & edge_rise_time_i(i) when edge_rise_i(i) = '1' else
	               std_logic_vector(to_unsigned(i,4)) & "000" & edge_fall_time_i(i) when edge_fall_i(i) = '1' else
						(others=>'0');
  end generate;
  
  -- The spill signal has to be the last signal to give it the lowest priority to save
  s_FIFO_wr(3+g_NUM_EDGE_INPUTS-1) <= spill_i_d1 xor spill_i_d2;
  s_evttype(3+g_NUM_EDGE_INPUTS-1) <= "0111" when spill_i_d1 = '1' and spill_i_d2 = '0' else
                                      "0110" when spill_i_d1 = '0' and spill_i_d2 = '1' else
						                    (others=>'0');
  s_var(3+g_NUM_EDGE_INPUTS-1) <= spill_cnt_i;

-------------------------------------------------------------------------------
-- Data event formater and types
-------------------------------------------------------------------------------

  gen_FIFO_i : for i in 1 to 3+g_NUM_EDGE_INPUTS-1 generate
    s_FIFO_i(i) <= s_evttype(i) & s_var(i) & std_logic_vector(s_coarse_timestamp);
  end generate gen_FIFO_i;
  
  gen_FIFOs : for i in 0 to 3+g_NUM_EDGE_INPUTS-1 generate
  begin
  
    FIFO_i : entity work.FIFO
      PORT MAP (
        clk => clk_4x_logic_i,
        rst => logic_reset_i,
        din => s_FIFO_i(i), 
        wr_en => s_FIFO_wr(i), 
        rd_en => s_FIFO_rd(i), 
        dout => s_FIFO_o(i), 
        full => open,
        empty => s_FIFO_empty(i)
	   );
		
  end generate gen_FIFOs;
  


-------------------------------------------------------------------------------
-- Reading FIFOs and multiplexing them to the output
-------------------------------------------------------------------------------
  
  -- NOTE: Rewrite this
  -- Mux to send the read signal to only one FIFO. Priority order: trigger, shutter, edge, spill
  -- every trigger word will be read before jump to other data
   
  p_FIFO_rd: process (s_FIFO_empty)
  begin  -- process p_generate_strobes
	 s_FIFO_rd <= (others=>'0');
    l_FIFO_rd: for i in 0 to 3+g_NUM_EDGE_INPUTS-1 loop
      if s_FIFO_empty(i) = '0' then
        s_FIFO_rd <= std_logic_vector(s_FIFO_rd_mask sll i);
		  exit l_FIFO_rd;
		end if;
    end loop;
  end process;		  
					
--  s_FIFO_rd <= "0001" when s_FIFO_empty(0) = '0' else
--	            "0010" when s_FIFO_empty(1) = '0' else
--		   	   "0100" when s_FIFO_empty(2) = '0' else
--					"1000" when s_FIFO_empty(3) = '0' else
--					(others=>'0');
  
--  p_data_o: process (s_FIFO_rd_d)
--  begin  -- process p_generate_strobes
--	 s_data_o <= (others=>'0');
--    l_data_o: for i in 0 to 3+g_NUM_EDGE_INPUTS-1 loop
--      if s_FIFO_rd_d(i) = '1' then
--        s_data_o <= s_FIFO_o(i);
--		  exit l_data_o;
--		end if;
--    end loop;
--  end process;		 
  
  -- Decide which FIFO to take into account as function of the read status in the last clock cicle
--  p_FIFO_rd_d: process(s_FIFO_rd_d)
--  begin
--    s_data_o <= (others=>'0');
--    l_FIFO_rd_d: for i in 0 to 3+g_NUM_EDGE_INPUTS-1 loop
--      if s_FIFO_rd_d(i) = '1' then
--        s_data_o <= s_FIFO_o(i);
--		  exit l_FIFO_rd_d;
--		end if;
--    end loop;
--  end process;
  s_data_o <= s_FIFO_o(0) when s_FIFO_rd_d(0) = '1' else -- Write it in a more elegant style
              s_FIFO_o(1) when s_FIFO_rd_d(1) = '1' else
				  s_FIFO_o(2) when s_FIFO_rd_d(2) = '1' else
				  s_FIFO_o(3) when s_FIFO_rd_d(3) = '1' else
				  s_FIFO_o(4) when s_FIFO_rd_d(4) = '1' else
				  s_FIFO_o(5) when s_FIFO_rd_d(5) = '1' else
				  s_FIFO_o(6) when s_FIFO_rd_d(6) = '1' else
				  (others=>'0');
  
  
  -- Send out the data and the strobe signal
  event_data_o <= s_data_o;
  data_strobe_o <= '0' when s_FIFO_rd_d = std_logic_vector(to_unsigned(0,s_FIFO_rd_d'length)) else
                   '1';

  -- purpose: Keep track of 40MHz timestamp
  -- type   : sequential
  -- inputs : clk_4x_logic_i
  -- outputs: 
  p_timestamp: process (clk_4x_logic_i, logic_reset_i)
  begin  -- process p_timestamp
    if rising_edge(clk_4x_logic_i) then  -- rising clock edge
      if logic_reset_i = '1' then
        s_coarse_timestamp <= ( others => '0');
      elsif ( logic_strobe_i = '1' )  then
        s_coarse_timestamp <= s_coarse_timestamp + 1;
      end if;
    end if;
  end process p_timestamp;
  
  -- Generate data in format decided at DESY. Put out two strobes for the
  -- two 64 bit words.
  -- get trigger inputs to also generate a global time-stamp ??
  -- add trigger_inputs_active_i array (to indicate which triggers fired)
  
END ARCHITECTURE rtl;

