library IEEE, work;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
use work.AddressMap.all;
use work.BusSignalTypes.all;
use work.AddressBook.all;

entity TDC is
    port(
        rst	              : in std_logic;
        clk	              : in std_logic;
        -- Module input --
        tofSig            : in std_logic_vector(NofTOF-1 downto 0);
    	fbhSig            : in std_logic_vector(NofFBH-1 downto 0);
    	chSig             : in std_logic_vector(NofCH-1  downto 0);
		
		-- DAQ signals --
		tdcStop             : in std_logic;
		trigL2              : in std_logic;
		Clear               : in std_logic;
		reqData             : in std_logic;
		tdcData             : out std_logic_vector(7 downto 0);
		validData           : out std_logic;
		emptyData           : out std_logic;
		busyDAQ             : out std_logic;
		gTag                : in std_logic;

		-- Local bus --
		addrLocalBus		: in LocalAddressType;
		dataLocalBusIn		: in LocalBusInType;
		dataLocalBusOut	    : out LocalBusOutType;
		reLocalBus			: in std_logic;
		weLocalBus			: in std_logic;
		readyLocalBus		: out std_logic
	);
end TDC;

architecture RTL of TDC is
	-- internal signal declaration ----------------------------------------
	constant NofLength : integer := 4;
	
	-- FBH --
	type bufFBH is array(integer range NofLength downto 0) of std_logic_vector(NofFBH-1 downto 0);
	signal buf_fbh         : bufFBH;
	
    signal data_in   : std_logic_vector(NofFBH-1 downto 0);
    signal pedge_out : std_logic_vector(NofFBH-1 downto 0);
    signal ring_out  : std_logic_vector(NofFBH-1 downto 0);
    
    type dataType is array(integer range NofFBH-1 downto 0) of std_logic_vector(7 downto 0);
    signal tdc_data : dataType;
    
    -- TOF --
  	type bufTOF is array(integer range NofLength downto 0) of std_logic_vector(NofTOF-1 downto 0);
    signal buf_tof         : bufTOF;
    
    signal data_in_tof   : std_logic_vector(NofTOF-1 downto 0);
    signal pedge_out_tof : std_logic_vector(NofTOF-1 downto 0);
    signal ring_out_tof  : std_logic_vector(NofTOF-1 downto 0);
    
    type dataTypeTOF is array(integer range NofTOF-1 downto 0) of std_logic_vector(7 downto 0);
    signal tdc_data_tof : dataTypeTOF;
    
    -- CH --
    type bufCH is array(integer range NofLength downto 0) of std_logic_vector(NofCH-1 downto 0);
    signal buf_ch         : bufCH;
    
    signal data_in_ch   : std_logic_vector(NofCH-1 downto 0);
    signal pedge_out_ch : std_logic_vector(NofCH-1 downto 0);
    signal ring_out_ch  : std_logic_vector(NofCH-1 downto 0);
    
    type dataTypeCH is array(integer range NofCH-1 downto 0) of std_logic_vector(7 downto 0);
    signal tdc_data_ch : dataTypeCH;
    
    signal write_en     : std_logic_vector(0 downto 0);
    signal ring_addr, present_addr, count    : std_logic_vector(7 downto 0);
    signal index    : std_logic_vector(5 downto 0);
    
    component EdgeDetector
        port (
            rst : in std_logic;
            clk : in std_logic;
            dIn : in std_logic;
            dOut : out std_logic 
        );
    end component;
    
    COMPONENT blk_mem_gen_0
      PORT (
        clka  : IN STD_LOGIC;
        ena   : IN STD_LOGIC;
        wea   : IN STD_LOGIC_VECTOR(0 DOWNTO 0);
        addra : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
        dina  : IN STD_LOGIC_VECTOR(30 DOWNTO 0);
        douta : OUT STD_LOGIC_VECTOR(30 DOWNTO 0)
      );
    END COMPONENT;
    
    COMPONENT blk_mem_tof_ring
      PORT (
        clka  : IN STD_LOGIC;
        ena   : IN STD_LOGIC;
        wea   : IN STD_LOGIC_VECTOR(0 DOWNTO 0);
        addra : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
        dina  : IN STD_LOGIC_VECTOR(23 DOWNTO 0);
        douta : OUT STD_LOGIC_VECTOR(23 DOWNTO 0)
      );
    END COMPONENT;
    
    COMPONENT blk_mem_ch_ring
      PORT (
        clka  : IN STD_LOGIC;
        ena   : IN STD_LOGIC;
        wea   : IN STD_LOGIC_VECTOR(0 DOWNTO 0);
        addra : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
        dina  : IN STD_LOGIC_VECTOR(63 DOWNTO 0);
        douta : OUT STD_LOGIC_VECTOR(63 DOWNTO 0)
      );
    END COMPONENT;
    
    signal buf_fifo, fifo_in   : std_logic_vector(31 downto 0);
    signal fifo_out   : std_logic_vector(7 downto 0);
    signal ren_fifo, wen_fifo   : std_logic;
    signal fifo_valid           : std_logic;
    signal reg_low_th, reg_high_th  : std_logic_vector(7 downto 0);
    
    COMPONENT fifo_generator_1
      PORT (
        clk   : IN STD_LOGIC;
        rst   : IN STD_LOGIC;
        din   : IN STD_LOGIC_VECTOR(31 DOWNTO 0);
        wr_en : IN STD_LOGIC;
        rd_en : IN STD_LOGIC;
        dout  : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
        full  : OUT STD_LOGIC;
        empty : OUT STD_LOGIC;
        valid : OUT STD_LOGIC
      );
    END COMPONENT;
	
	signal state_lbus	: BusProcessType;
	signal daq_start     : std_logic;
	signal common_stop   : std_logic;
	signal busy          : std_logic;
	signal delay_busy    : std_logic_vector(2 downto 0);
	signal mem_rst, reset_daq : std_logic;
	signal mem_l2, mem_clr : std_logic;
	signal write_enable : std_logic;
	signal self_counter    : std_logic_vector(11 downto 0);
	signal global_counter  : std_logic;
	
	type stateDAQType is (
        Init, Idle, RunDAQ, ReadRing, WaitDecision,
        SendHeader, SendHeader2,
        BuildPacket, BuildPacketTOF, BuildPacketCH,
        Finalize, Done
    );
    signal state_daq    : stateDAQType;
	
	-- =============================== body ===============================
begin
    u_ShiftReg : process(rst, clk)
    begin
        if(clk'event AND clk = '1') then
            buf_fbh <= buf_fbh(NofLength-1 downto 0) & fbhSig;
            buf_tof <= buf_tof(NofLength-1 downto 0) & tofSig;
            buf_ch  <= buf_ch(NofLength-1 downto 0)  & chSig;
        end if;
    end process u_ShiftReg;
    
    data_in     <= buf_fbh(NofLength);
    data_in_tof <= buf_tof(NofLength);
    data_in_ch  <= buf_ch(NofLength);
    gen_pedge : for i in 0 to NofFBH-1 generate
        u_pedge : EdgeDetector
            port map(rst => rst, clk => clk, dIn => data_in(i), dOut => pedge_out(i));
    end generate;
    
    gen_pedge_tof : for i in 0 to NofTOF-1 generate
        u_pedge : EdgeDetector
            port map(rst => rst, clk => clk, dIn => data_in_tof(i), dOut => pedge_out_tof(i));
    end generate;
    
    gen_pedge_ch : for i in 0 to NofCH-1 generate
        u_pedge : EdgeDetector
            port map(rst => rst, clk => clk, dIn => data_in_ch(i), dOut => pedge_out_ch(i));
    end generate;
    
    u_RingBuffer : blk_mem_gen_0
      port map(
        clka  => clk,
        ena   => '1',
        wea   => write_en,
        addra => ring_addr,
        dina  => pedge_out,
        douta => ring_out
      );
      
   u_RingBufferTOF : blk_mem_tof_ring
     port map(
       clka  => clk,
       ena   => '1',
       wea   => write_en,
       addra => ring_addr,
       dina  => pedge_out_tof,
       douta => ring_out_tof
     );
         
   u_RingBufferCH : blk_mem_ch_ring
     port map(
       clka  => clk,
       ena   => '1',
       wea   => write_en,
       addra => ring_addr,
       dina  => pedge_out_ch,
       douta => ring_out_ch
    );         
      
    u_DelayBusy : process(rst, clk)
    begin
        if(clk'event AND clk = '1') then
            delay_busy  <= delay_busy(1 downto 0) & busy;
        end if;
    end process u_DelayBusy;
      
    busyDAQ     <= delay_busy(2);
    common_stop <= tdcStop;
    tdcData     <= fifo_out;
    validData   <= fifo_valid;
    ren_fifo    <= reqData;
    fifo_in     <= buf_fifo(7 downto 0) & buf_fifo(15 downto 8) & buf_fifo(23 downto 16) & buf_fifo(31 downto 24);
    u_BuildFifo : fifo_generator_1
    port map(
      clk   => clk,
      rst   => rst,
      din   => fifo_in,
      wr_en => wen_fifo,
      rd_en => ren_fifo,
      dout  => fifo_out,
      full  => open,
      empty => emptyData,
      valid => fifo_valid
    );

    mem_rst <= rst OR reset_daq;
    u_MemL2 : process(mem_rst, clk)
    begin
        if(mem_rst = '1') then
            mem_l2   <= '0';
            mem_clr  <= '0';
        elsif(clk'event AND clk = '1') then
            if(trigL2 = '1') then
                mem_l2  <= '1';
            end if;
            
            if(Clear = '1') then
                mem_clr <= '1';
            end if;
        end if;
    end process u_MemL2;

    u_DAQProcess : process(rst, clk)
    variable id : integer;
    begin
        if(rst = '1') then
            wen_fifo    <= '0';
            state_daq   <= Init;
        elsif(clk'event AND clk = '1') then
        id      := conv_integer(index);
        case state_daq is
            when Init =>
                ring_addr   <= (others => '0');
                write_enable<= '1';
                write_en    <= "0";
                wen_fifo    <= '0';
                reset_daq   <= '0';
                busy        <= '0';
                self_counter    <= (others => '0');
                for i in 0 to NofFBH-1 loop
                    tdc_data(i)     <= (others => '0');
                end loop;
                for i in 0 to NofTOF-1 loop
                    tdc_data_tof(i) <= (others => '0');
                end loop;
                for i in 0 to NofCH-1 loop                
                    tdc_data_ch(i)  <= (others => '0');
                end loop;                    
                state_daq   <= Idle;
                
            when Idle =>
                busy        <= '0';
                reset_daq   <= '1';
                if(daq_start = '1') then
                    reset_daq   <= '0';
                    write_en    <= "1";
                    state_daq   <= RunDAQ;
                end if;
                
            when RunDAQ =>
                ring_addr   <= ring_addr + 1;
                if(common_stop = '1') then
                    busy        <= '1';
                    count       <= X"ff";
                    write_en    <= "0";
                    state_daq   <= ReadRing;
                end if;
                
            when ReadRing =>
                ring_addr       <= ring_addr + 1;
                count           <= count -1;
                if(count = X"00") then
                   --state_daq   <= SendHeader;
                    state_daq   <= WaitDecision;
                end if;
                
                if(reg_low_th < count AND count < reg_high_th) then
                for i in 0 to NofFBH-1 loop
                    if(ring_out(i) = '1') then
                        tdc_data(i) <= count;
                    end if;
                end loop;
                
                for i in 0 to NofTOF-1 loop
                    if(ring_out_tof(i) = '1') then
                        tdc_data_tof(i) <= count;
                    end if;
                end loop;
                
                for i in 0 to NofCH-1 loop
                    if(ring_out_ch(i) = '1') then
                        tdc_data_ch(i) <= count;
                    end if;
                end loop;                
                end if;
             
             when WaitDecision =>
                write_en    <= "1";
                if(mem_l2 = '1') then
                    self_counter    <= self_counter +1;
                    write_enable    <= '1';
                    state_daq       <= SendHeader;
                elsif(mem_clr = '1' OR daq_start = '0') then
                    write_enable    <= '0';
                    state_daq       <= SendHeader;
                end if;
             
             when SendHeader =>
                wen_fifo    <= write_enable;
                buf_fifo    <= X"ffff3dcc";
                index       <= "011110";
                --state_daq   <= BuildPacket;
                state_daq   <= SendHeader2;
             
             when SendHeader2 =>  
                buf_fifo    <= "0000" & self_counter & X"000" & "000" & gTag;
                state_daq   <= BuildPacket;
                
             when BuildPacket =>
                index   <= index -1;
                buf_fifo    <= X"fb80" & "00" & index & tdc_data(id);
                if(index = "000000") then
                    index       <= "010111";
                    state_daq   <= BuildPacketTOF;
                end if;

            when BuildPacketTOF =>
                index   <= index -1;
                buf_fifo    <= X"100f" & "00" & index & tdc_data_tof(id);
                if(index = "00000") then
                    index       <= "111111";
                    state_daq   <= BuildPacketCH;
                end if;  
                
            when BuildPacketCH =>
                    index   <= index -1;
                    buf_fifo    <= X"c800" & "00" & index & tdc_data_ch(id);
                    if(index = "00000") then
                        state_daq   <= Finalize;
                    end if;                                

             when Finalize =>
                wen_fifo    <= '0';
                reset_daq   <= '1';
               for i in 0 to NofFBH-1 loop
                     tdc_data(i)     <= (others => '0');
                 end loop;
                 for i in 0 to NofTOF-1 loop
                     tdc_data_tof(i) <= (others => '0');
                 end loop;
                 for i in 0 to NofCH-1 loop                
                     tdc_data_ch(i)  <= (others => '0');
                 end loop;  
                state_daq   <= Done;
                
             when Done =>
                reset_daq   <= '0';
                state_daq   <= Idle;
            
        end case;
        end if;
    end process u_DAQProcess;

    u_BusProcess : process(clk, rst)
	begin
        if(rst = '1') then
            state_lbus	<= Init;
        elsif(clk'event and clk = '1') then
			case state_lbus is
			when Init =>
				dataLocalBusOut 	<= x"00";
				readyLocalBus		<= '0';
				daq_start   <= '0';
                reg_low_th  <= (others => '0');
                reg_high_th <= (others => '0');
				state_lbus			<= Idle;
				
			when Idle =>
				readyLocalBus	<= '0';
				if(weLocalBus = '1' or reLocalBus = '1') then
					state_lbus	<= Connect;
				end if;
			
			when Connect =>
				if(weLocalBus = '1') then
					state_lbus	<= Write;
				else
					state_lbus	<= Read;
				end if;
				
			when Write =>
			    case addrLocalBus is
			    when TDC_start =>
                    daq_start    <= dataLocalBusIn(0);
                when TDC_lowth =>
                    reg_low_th   <= dataLocalBusIn(7 downto 0);
                when TDC_highth =>
                    reg_high_th  <= dataLocalBusIn(7 downto 0);
                when others => null;
				end case;
    	        state_lbus	<= Done;
				
			when Read =>
				state_lbus	     <= Done;
				
			when Done =>
				readyLocalBus	<= '1';
				if(weLocalBus = '0' and reLocalBus = '0') then
					state_lbus	<= Idle;
				end if;
			
			-- probably this is error --
			when others =>
				state_lbus	<= Init;
			end case;
		end if;
	end process u_BusProcess;

end RTL;

