library IEEE;
use IEEE.Std_logic_1164.all;
use IEEE.Numeric_Std.all;
use work.ipbus.all;
use work.ipbus_reg_types.all;
use work.fmcTLU.all;
use work.BFMTypes.all;

entity triggerInputs_newTLU_tb is
  generic (
    g_NUM_INPUTS  : natural  := 6;
    g_IPBUS_WIDTH : positive := 32;
    g_BFMINPUT    : string  := "$BFMINPUT";
    g_BFMOUTPUT    : string  := "$BFMOUTPUT"
    );
end;

architecture bench of triggerInputs_newTLU_tb is

  component triggerInputs_newTLU
    generic(
      g_NUM_INPUTS  : natural  := g_NUM_INPUTS;
      g_NUM_COARSE_TS_BITS : natural := 3; --! Number of coarse ( clk_1x_logic normally 40MHz ) timestamp bits to add to MSB of trigger times.
      g_IPBUS_WIDTH : positive := g_IPBUS_WIDTH
      );
    port(
      clk_4x_logic         : in  std_logic;
      clk_200_i            : in  std_logic;
      strobe_4x_logic_i    : in  std_logic;
      threshold_discr_p_i  : in  std_logic_vector (g_NUM_INPUTS-1 downto 0);
      threshold_discr_n_i  : in  std_logic_vector (g_NUM_INPUTS-1 downto 0);
      reset_i              : in  std_logic;
      coarse_timestamp_i   : in  std_logic_vector (g_NUM_COARSE_TS_BITS-1 DOWNTO 0); --! Global timestamp. Clocked on clk_4x_logic, but only increments with logic_strobe
      trigger_times_o      : out t_triggerTimeArray (g_NUM_INPUTS-1 downto 0);
      trigger_o            : out std_logic_vector (g_NUM_INPUTS-1 downto 0);
      edge_rising_times_o  : out t_triggerTimeArray (g_NUM_INPUTS-1 downto 0);
      edge_falling_times_o : out t_triggerTimeArray (g_NUM_INPUTS-1 downto 0);
      edge_rising_o        : out std_logic_vector (g_NUM_INPUTS-1 downto 0);
      edge_falling_o       : out std_logic_vector (g_NUM_INPUTS-1 downto 0);
      ipbus_clk_i          : in  std_logic;
      ipbus_reset_i        : in  std_logic;
      ipbus_i              : in  ipb_wbus;
      ipbus_o              : out ipb_rbus;
      clk_8x_logic_i       : in  std_logic;
      strobe_8x_logic_i    : in  std_logic
      );
  end component;

  signal clk_4x_logic         : std_logic := '0';
  signal clk_200_i            : std_logic;
  signal strobe_4x_logic_i    : std_logic;
  signal s_pulses  : std_logic_vector (g_NUM_INPUTS-1 downto 0);
  signal threshold_discr_p_i  : std_logic_vector (g_NUM_INPUTS-1 downto 0);
  signal threshold_discr_n_i  : std_logic_vector (g_NUM_INPUTS-1 downto 0);
  signal reset_i              : std_logic;
  signal trigger_times_o      : t_triggerTimeArray (g_NUM_INPUTS-1 downto 0);
  signal trigger_o            : std_logic_vector (g_NUM_INPUTS-1 downto 0);
  signal edge_rising_times_o  : t_triggerTimeArray (g_NUM_INPUTS-1 downto 0);
  signal edge_falling_times_o : t_triggerTimeArray (g_NUM_INPUTS-1 downto 0);
  signal edge_rising_o        : std_logic_vector (g_NUM_INPUTS-1 downto 0);
  signal edge_falling_o       : std_logic_vector (g_NUM_INPUTS-1 downto 0);
  signal ipbus_clk_i          : std_logic;
  signal ipbus_reset_i        : std_logic;
  signal ipbus_i              : ipb_wbus;
  signal ipbus_o              : ipb_rbus;
  signal clk_8x_logic_i       : std_logic;
  signal clk_logic            : std_logic;
  signal strobe_8x_logic_i    : std_logic;
  signal s_coarse_timestamp : std_logic_vector(c_TRIGIN_NUM_COARSE_TS_BITS-1 downto 0) := ( others => '0' );  -- counts on each strobe_4x_logic pulse
 -- constant C_NUM_STROBE_TAPS  : positive  := 2;  --! Adjust to shift strobes relative to 40MHz clock edge
 -- signal s_clk40_delayed_160  : std_logic_vector(C_NUM_STROBE_TAPS downto 0);  --! Shift register used to generate clock_4x strobe. Adjust length for correct alignment with incoming clock
 -- signal s_clk40_delayed_320  : std_logic_vector((2*C_NUM_STROBE_TAPS)+1 downto 0);  --! Shift register used to generate clock_8x strobe. Adjust length for correct alignment with incoming clock

  signal s_dut_clk, s_clocks_locked, s_logic_reset : std_logic;

	signal s_pulseTransaction : t_pulseTransaction;
	signal s_pulseTransactionReturn : t_pulseTransactionReturn;

  signal  s_wbTransaction : t_wbTransaction;
  signal  s_wbTransactionReturn : t_wbTransactionReturn;
  
  signal s_pulse_sync : std_logic := '0';  -- strobe high once every 8 clock cycles of 40MHz

  signal s_triggerNumber :  std_logic_vector(g_IPBUS_WIDTH - 1 downto 0);
  constant delta             : time := 0.02 ns;  -- make sure IPBus clock drifts w.r.t.
  -- logic clocks
  constant clockLogic_period : time := 25 ns;    -- 40MHz clock
  --constant clock320_period: time := 3.125 ns; -- 320 MHz clock
  constant clock200_period   : time := 5 ns + delta;      -- 200 MHz clock
  constant clockipbus_period : time := 31.25 ns + delta;  -- 320 MHz clock

  signal stop_the_clock : boolean := false;

  -- Look at /projects/HEP_Instrumentation/cad/designs/uob-hep-pc043a/trunk/firmware/maroc3_fmc/sim_src/variablePulseTransactor_rtl.vhd
  --/projects/HEP_Instrumentation/cad/designs/uob-hep-pc043a/trunk/firmware/maroc3_fmc/sim_src/wishbone_master_behavioural.vhd
  -- and friends for parameterizable pulse generator.

begin

  -- Insert values for generic parameters !!
  uut : triggerInputs_newTLU generic map (g_NUM_INPUTS  => g_NUM_INPUTS,
                                          g_IPBUS_WIDTH => g_IPBUS_WIDTH)
    port map (clk_4x_logic         => clk_4x_logic,
              clk_200_i            => clk_200_i,
              strobe_4x_logic_i    => strobe_4x_logic_i,
              threshold_discr_p_i  => threshold_discr_p_i,
              threshold_discr_n_i  => threshold_discr_n_i,
              reset_i              => s_logic_reset,
              coarse_timestamp_i   => s_coarse_timestamp,
              trigger_times_o      => trigger_times_o,
              trigger_o            => trigger_o,
              edge_rising_times_o  => edge_rising_times_o,
              edge_falling_times_o => edge_falling_times_o,
              edge_rising_o        => edge_rising_o,
              edge_falling_o       => edge_falling_o,
              ipbus_clk_i          => ipbus_clk_i,
              ipbus_reset_i        => ipbus_reset_i,
              ipbus_i              => ipbus_i,
              ipbus_o              => ipbus_o,
              clk_8x_logic_i       => clk_8x_logic_i,
              strobe_8x_logic_i    => strobe_8x_logic_i);

  proc_PrintTimes: process (trigger_o) is
  begin  -- process proc_PrintTimes
    if rising_edge(trigger_o(0)) or rising_edge(trigger_o(1)) then
    report "Got a trigger time  " & to_hstring(s_triggerNumber) & "    " & time'image(NOW) & "    " & to_hstring(trigger_times_o(0)) & "   " & to_hstring(trigger_times_o(1)) & "   "& to_hstring(trigger_times_o(2)) & "   "& to_hstring(trigger_times_o(3)) & "   "& to_hstring(trigger_times_o(4)) & "   "& to_hstring(trigger_times_o(5))  severity note;      
    end if;
  end process proc_PrintTimes;
  stimSequence : process
  begin

    -- Put initialisation code here
    reset_i        <= '1';
    ipbus_reset_i  <= '1';
    wait for clockLogic_period * 16;
    reset_i        <= '0';
    ipbus_reset_i  <= '0';

    -- Wait for commands to be played through
    wait until stop_the_clock;
    reset_i <= '1';

    wait;
  end process;

  transactionGen : entity work.wishboneTransactionGenerator
    generic map (
      g_BUSWIDTH => g_IPBUS_WIDTH,  --! Number of bits in each word
      BFMINPUT   => g_BFMINPUT ,  --! File containing list of commands 
      BFMOUTPUT  => g_BFMOUTPUT  --! Output file
      )
    port map (
      wb_transaction_o      => s_wbTransaction,    -- ! signal that carries transaction to IPBus transactor
      wb_returned_data_i    => s_wbTransactionReturn,  -- ! Carrys data back from BFM
      pulse_transaction_o   => s_pulseTransaction,
      pulse_returned_data_i => s_pulseTransactionReturn,

      --! Signal to clock generator
      endOfSim_o => stop_the_clock      --! Take high to stop simulation.
      );


  s_pulse_sync <= '1' when  strobe_4x_logic_i='1' and s_coarse_timestamp = "000" and rising_edge(strobe_4x_logic_i) else '0';
  
  pulseGen: entity work.variablePulseTransactor 
    generic map (
      g_BUSWIDTH => g_IPBUS_WIDTH)  -- width for triggernumber and timestamp
      port map (
        --clk_i           => strobe_4x_logic_i,
        clk_i           => s_pulse_sync,
        trans_i         => s_pulseTransaction,
        returnedData_o  => s_pulseTransactionReturn,
        signal_o        => s_pulses,
        triggerNumber_o => s_triggerNumber,
        timeStamp_o     => open
        );

  threshold_discr_p_i <= s_pulses;
  threshold_discr_n_i <= not s_pulses;

  ipbusGen : entity work.ipbusTransactor 
  port map (
    -- interface to transaction generator
    Trans => s_wbTransaction,
    returnedData => s_wbTransactionReturn,
    -- interface to "physical" ports
    clk_i => ipbus_clk_i,
    ipb_to_slave => ipbus_i,        --! Signals from master to slave
    ipb_from_slave => ipbus_o        --! Signals from slave to master
      );
    
    clockGenerator : entity work.logic_clocks
      port map (
        ipbus_clk_i           => ipbus_clk_i,
        ipbus_i               => ipbus_i,
        ipbus_reset_i         => ipbus_reset_i,
        Reset_i               => reset_i,
        clk_logic_xtal_i      => clk_logic,
        clk_8x_logic_o        => clk_8x_logic_i,
        clk_4x_logic_o        => clk_4x_logic,
        ipbus_o               => ipbus_o,
        strobe_8x_logic_o     => strobe_8x_logic_i,
        strobe_4x_logic_o     => strobe_4x_logic_i,
        DUT_clk_o             => s_dut_clk,
        logic_clocks_locked_o => s_clocks_locked,
        logic_reset_o         => s_logic_reset
        );


    clockLogic : process
    begin
      while not stop_the_clock loop
        clk_logic <= '0', '1' after clockLogic_period / 2;
        wait for clockLogic_period;
      end loop;
      wait;
    end process;

    clockIpbus : process
    begin
      while not stop_the_clock loop
        ipbus_clk_i <= '0', '1' after clockipbus_period / 2;
        wait for clockipbus_period;
      end loop;
      wait;
    end process;

    clock200 : process
    begin
      while not stop_the_clock loop
        clk_200_i <= '0', '1' after clock200_period / 2;
        wait for clock200_period;
      end loop;
      wait;
    end process;

  tsCounter : process
    variable v_tsctr : unsigned ( c_TRIGIN_NUM_COARSE_TS_BITS-1 downto 0) := (others => '0') ;
  begin
    while not stop_the_clock loop
      wait until rising_edge(clk_4x_logic);
      if reset_i = '1' then
        v_tsctr := ( others => '0');
      elsif strobe_4x_logic_i ='1' then
        v_tsctr := v_tsctr + 1;
      end if;
      s_coarse_timestamp <= std_logic_vector(v_tsctr);      
    end loop;
    wait;
  end process;
  
  end;

