---------------------------------------------------------------------------
-- File           : ea_input_mux.vhd
-- Author(s)      : Nikola Avramovic
-- Created        : March 16th, 2019
---------------------------------------------------------------------------
-- Description    : Input mux choose corect stream between two patterns
---------------------------------------------------------------------------
--
---------------------------------------------------------------------------
library ieee; 
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   use work.p_parameters_and_constants.all;
   use work.p_interfaces.all;
   use work.p_functions.all;
   use work.p_port_gen_interface.all;

entity ea_input_mux is
   generic(
      G_NO_OF_PATT_GEN : natural
   );
   port (clk           : in  std_logic;
         reset         : in  std_logic;
         -- to/from first pattern generators
         patt_geni     : in  TYPE_AVALON_ST_SOURCE_TO_SINK_ARRAY(G_NO_OF_PATT_GEN downto 1);
         patt_geno     : out TYPE_AVALON_ST_SINK_TO_SOURCE_ARRAY(G_NO_OF_PATT_GEN downto 1);
         -- to/from control port
         control_porti : in  TYPE_CONTROL_PORT_TO_INPUT_MUX;
         control_porto : out TYPE_INPUT_MUX_TO_CONTROL_PORT;
         -- to/from ethernet multiplexer
         eth_outputi   : in  TYPE_ETH_OUTPUT_TO_INPUT_MUX;
         eth_outputo   : out TYPE_INPUT_MUX_TO_ETH_OUTPUT
   );
end ea_input_mux;

architecture rtl of ea_input_mux is

   constant C_BUFFER_NO        : natural := 5; -- according to the simulation it needs 5 cycles, for relasing added 8 cycles
   
   type TYPE_REGS is record   
      mux_error                : boolean;
      ready_priority           : boolean;
      ready_frag               : boolean;
      sop_priority             : boolean;
      sop_fragment             : boolean;
      eop_priority             : boolean;
      eop_fragment             : boolean;
      add_priority_zeros       : boolean;
      add_fragment_zeros       : boolean;
      priority_end_of_trans    : boolean;
      fragment_end_of_trans    : boolean;
      frag_len_counter         : natural;
      priority_buff_count      : natural;
      fragment_buff_count      : natural;
      priority_patt            : TYPE_AVALON_ST_SOURCE_TO_SINK_ARRAY(C_BUFFER_NO-1 downto 0);
      frag_patt                : TYPE_AVALON_ST_SOURCE_TO_SINK_ARRAY(C_BUFFER_NO-1 downto 0);
      mux_out                  : TYPE_AVALON_ST_SOURCE_TO_SINK;
   end record;
   
   constant CONST_REGS_RESET : TYPE_REGS := (
      mux_error                => false,
      ready_priority           => false,
      ready_frag               => false,
      sop_priority             => false,
      sop_fragment             => false,
      eop_priority             => false,
      eop_fragment             => false,
      add_priority_zeros       => false,
      add_fragment_zeros       => false,
      priority_end_of_trans    => false,
      fragment_end_of_trans    => false,
      frag_len_counter         => 0,
      priority_buff_count      => 0,
      fragment_buff_count      => 0,
      priority_patt            => (others => C_AVALON_ST_SOURCE_TO_SINK),
      frag_patt                => (others => C_AVALON_ST_SOURCE_TO_SINK),
      mux_out                  => C_AVALON_ST_SOURCE_TO_SINK
   ); 
   
   signal r                    : TYPE_REGS := CONST_REGS_RESET;

begin
   sync: process (reset, clk) 
      variable s               : TYPE_REGS;

   begin

      if reset = '1' then
         r                     <= CONST_REGS_RESET;

      elsif rising_edge(clk) then
         -- initialize the variables ------------------------------------------
         s                               := r;

         ---------------------------------------------------------------------------
         -- set which pattern generator will be priority; priority is defined by command
         -- this design allow just one priority patt and one frag pattern
         ---------------------------------------------------------------------------
         if control_porti.priority_input = 1 then
            if patt_geni(1).valid = '1' then -- and not r.sop_fragment then
               s.priority_patt(0)        := patt_geni(1);

               -- buffer input data, because modules needs to proccess input commands
               s.priority_patt           := s.priority_patt(0) & s.priority_patt(C_BUFFER_NO-1 downto 1);

            end if;

            if patt_geni(2).valid = '1' then 
               s.frag_patt(0)            := patt_geni(2);
               -- buffer input data, because modules needs to proccess input commands
               s.frag_patt               := s.frag_patt(0) & s.frag_patt(C_BUFFER_NO-1 downto 1);

            end if;

         elsif control_porti.priority_input = 2 then
            if patt_geni(2).valid = '1' then
               s.priority_patt(0)        := patt_geni(2);

               -- buffer input data, because modules needs to proccess input commands
               s.priority_patt           := s.priority_patt(0) & s.priority_patt(C_BUFFER_NO-1 downto 1);

            end if;

            if patt_geni(1).valid = '1' then
               s.frag_patt(0)            := patt_geni(1);
               -- buffer input data, because modules needs to proccess input commands
               s.frag_patt               := s.frag_patt(0) & s.frag_patt(C_BUFFER_NO-1 downto 1);
            end if;

         end if;

         ---------------------------------------------------------------------------
         -- Fill buffer with zeros at the end of transaction
         ---------------------------------------------------------------------------
         if ((s.priority_patt(C_BUFFER_NO-1).data = r.priority_patt(C_BUFFER_NO-1).data) 
         and s.priority_patt(C_BUFFER_NO-1).eop = '1') or r.add_priority_zeros then
            s.priority_end_of_trans      := true;

            if r.priority_buff_count < C_BUFFER_NO-1 then
               s.priority_buff_count     := r.priority_buff_count + 1;
               s.add_priority_zeros      := true;
               s.priority_patt(0)        := C_AVALON_ST_SOURCE_TO_SINK;
               -- buffer input data, because modules needs to proccess input commands
               s.priority_patt           := s.priority_patt(0) & s.priority_patt(C_BUFFER_NO-1 downto 1);
            
            else
               s.add_priority_zeros      := false;

            end if;
         end if;

         -- fell buffer with zeros at the end of transaction
         if ((s.frag_patt(C_BUFFER_NO-1).data = r.frag_patt(C_BUFFER_NO-1).data) 
         and s.frag_patt(C_BUFFER_NO-1).eop = '1') or r.add_fragment_zeros then
            s.fragment_end_of_trans      := true;

            if r.fragment_buff_count < C_BUFFER_NO-1 then
               s.fragment_buff_count     := r.fragment_buff_count + 1;
               s.add_fragment_zeros      := true;
               s.frag_patt(0)            := C_AVALON_ST_SOURCE_TO_SINK;
               -- buffer input data, because modules needs to proccess input commands
               s.frag_patt               := s.frag_patt(0) & s.frag_patt(C_BUFFER_NO-1 downto 1);
            
            else
               s.add_fragment_zeros      := false;

            end if;
         end if;

         ---------------------------------------------------------------------------
         -- Mangement for Start/End Of Packet (priority/faragmented pattern)
         ---------------------------------------------------------------------------
         -- set sop priority
         if r.priority_patt(C_BUFFER_NO-1).sop = '1' or r.mux_out.sop = '1' or r.sop_priority then
            s.sop_priority               := true;
            s.ready_frag                 := false;

            if control_porti.send_priority_frame or control_porti.send_fragment_frame then
               s.sop_priority            := false; 

            end if;
         end if;

         -- set sop fragment
         if r.frag_patt(C_BUFFER_NO-1).sop = '1' or r.mux_out.sop = '1' or r.sop_fragment then
            s.sop_fragment               := true;
            s.ready_priority             := false;

            if control_porti.send_priority_frame or control_porti.send_fragment_frame then
               s.sop_fragment            := false; 

            end if;
         end if;

         -- set priority eop 
         if s.priority_patt(1).eop = '1' or r.eop_priority then
            s.eop_priority               := true;
            s.eop_fragment               := false;

            if not eth_outputi.data_read_out then 
               s.eop_priority            := false;

            end if;
         end if;

         -- set fragment eop 
         if s.frag_patt(1).eop = '1' or r.eop_fragment then
            s.eop_priority               := false;
            s.eop_fragment               := true;

            if not eth_outputi.data_read_out then 
               s.eop_fragment            := false;

            end if;
         end if;

         ---------------------------------------------------------------------------
         -- Input multiplexor 
         ---------------------------------------------------------------------------
         case control_porti.input_mux_sel is
            when SET_PRIORITY =>
               if eth_outputi.ready = '1' then
                  s.ready_frag           := false;

                  if r.eop_priority then
                     s.ready_priority       := false;

                  elsif not s.eop_priority then
                     s.ready_priority       := true;

                  end if;

                  if r.priority_patt(0).valid = '1' then
                     s.mux_out           := r.priority_patt(0);

                  end if;

               else
                  s.ready_priority       := false;

               end if;

            when SET_FRAG =>
               if eth_outputi.ready = '1' then
                  s.ready_priority       := false;

                  if r.eop_fragment then
                     s.ready_frag        := false;

                  elsif not s.eop_fragment then
                     s.ready_frag        := true;

                  end if;

                  if r.frag_patt(0).valid = '1' then
                     s.mux_out           := r.frag_patt(0);

                  end if;
               else
                  s.ready_frag           := false;

               end if;

            when NONE => 
               s.ready_priority          := false;
               s.ready_frag              := false;

               -- if dut is ready set ouput signals
               if eth_outputi.ready = '1' and control_porti.ready then

                  -- priority data are already in buffers
                  if (s.priority_patt(0).sop = '1') then
                     s.ready_priority    := false;
                     s.sop_priority      := true;

                  -- fragment data are already in buffers
                  elsif (s.frag_patt(0).sop = '1') then --or (s.frag_patt(0).sop = '1') 
                     s.ready_frag        := false;
                     s.sop_fragment      := true;

                  else --if not r.sop_fragment or not r.sop_priority then
                     s.ready_priority    := true;
                     s.ready_frag        := true;

                  end if;

               end if;

         end case;

         -- Assign the register record -------------------------------------------
         r                     <= s;

         --------------------------------------------------------------------------
         -- set FIFO ready signal, 
         --------------------------------------------------------------------------
         if control_porti.priority_input = 1 then 
            patt_geno(1).ready <= boolean_to_stdlogic(s.ready_priority);

         else
            patt_geno(1).ready <= boolean_to_stdlogic(s.ready_frag);

         end if;

         if control_porti.priority_input = 2 then
            patt_geno(2).ready <= boolean_to_stdlogic(s.ready_priority);

         else
            patt_geno(2).ready <= boolean_to_stdlogic(s.ready_frag);

         end if;
      end if;  
   end process;   
   
   -- set output signals
   control_porto               <= (sop_priority          => r.sop_priority,
                                   sop_fragment          => r.sop_fragment,
                                   priority_end_of_trans => r.priority_end_of_trans,
                                   fragment_end_of_trans => r.fragment_end_of_trans);

   eth_outputo                 <= (sop_priority          => r.sop_priority,
                                   sop_fragment          => r.sop_fragment,
                                   mux_out               => r.mux_out);

end architecture rtl;         