---------------------------------------------------------------------------
-- File           : ea_content_addressable_memory.vhd
-- Author(s)      : Nikola Avramovic
-- Created        : April 16th, 2019
---------------------------------------------------------------------------
-- Description    : CAM chose which frame type have been received
---------------------------------------------------------------------------
--
-- Commands for this module:- PRIORITY_OUTPUT
--                          - SET_FRAG       
--                          - NO_OF_FARGMENT 
--                          - PACKET_CRC_EN  
--                          - PREAMBLE_LENGTH
--                          - SFD            
--                          - SMD_I          
--                          - SMD_C          
--                          - TERMINATED
---------------------------------------------------------------------------
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_mon_interface.all;
   use work.PCK_CRC32_D8.all;

entity ea_content_addressable_memory is
   generic(
      G_NO_OF_PATT_MON : natural
      );
   port (clk           : in  std_logic;
         reset         : in  std_logic;
         -- to/from control port
         control_porti : in  TYPE_CONTROL_PORT_TO_CAM;
         control_porto : out TYPE_CAM_TO_CONTROL_PORT;
         -- to/from control port
         header_checki : in  TYPE_HEADER_CHECK_TO_CAM;
         header_checko : out TYPE_CAM_TO_HEADER_CHECK;
         -- output port
         duti          : in  TYPE_AVALON_ST_SOURCE_TO_SINK;
         duto          : out TYPE_AVALON_ST_SINK_TO_SOURCE;
         -- to/from pattern monitor
         pattern_moni  : in  TYPE_AVALON_ST_SINK_TO_SOURCE_ARRAY(G_NO_OF_PATT_MON downto 1);
         pattern_mono  : out TYPE_AVALON_ST_SOURCE_TO_SINK_ARRAY(G_NO_OF_PATT_MON downto 1)
   );
end ea_content_addressable_memory;

architecture rtl of ea_content_addressable_memory is
 
   type TYPE_STATE is (IDLE, PREAMBLE, FRAG_NO, DATA, CRC32_PACKET, TERMINATE);
   type TYPE_STATE_DEMUX is (PATT_FRAGMENT, PATT_PRIORITY, NONE);
   
   type TYPE_REGS is record
      state                    : TYPE_STATE;
      input_demux              : TYPE_STATE_DEMUX;
      dut_ready                : boolean;
      crc_packet_en            : boolean;
      receiving                : boolean;
      preamble_receiving       : boolean;
      receiving_fragment       : boolean;
      cam_error                : boolean;
      patt_not_ready           : boolean;
      frag_no                  : natural;
      crc_count                : natural;
      idle                     : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      preamble                 : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      sfd                      : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      smd_i                    : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      smd_c                    : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      frag_const               : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      terminated               : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      crc_mon_data             : std_logic_vector(C_CRC32_DATA_WIDTH-1 downto 0);
      crc_received_data        : std_logic_vector(C_CRC32_DATA_WIDTH-1 downto 0); 
      priority_out             : TYPE_AVALON_ST_SOURCE_TO_SINK;
      fragment_out             : TYPE_AVALON_ST_SOURCE_TO_SINK;
   end record;
   
   constant CONST_REGS_RESET : TYPE_REGS := (
      state                    => IDLE,
      input_demux              => NONE,
      dut_ready                => true,
      crc_packet_en            => false,
      receiving                => false,
      preamble_receiving       => false,
      receiving_fragment       => false,
      cam_error                => false,
      patt_not_ready           => false,
      frag_no                  => 0,
      crc_count                => 0,
      idle                     => C_IDLE,
      preamble                 => C_PREAMBLE,
      sfd                      => C_SFD,
      smd_i                    => C_SMD_S_0,
      smd_c                    => C_SMD_C_0,
      frag_const               => C_FRAG_NO_0,
      crc_mon_data             => (others => '0'),
      crc_received_data        => (others => '0'),
      terminated               => C_TERMINATED,
      priority_out             => C_AVALON_ST_SOURCE_TO_SINK,
      fragment_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;
         -- dut output mast be set for whole time of transaction
         -- becase it is ethernet transaction, which can not stop
         s.dut_ready                     := true;

         ---------------------------------------------------------------------------
         -- check input transaction
         ---------------------------------------------------------------------------
         if control_porti.ready then
            s.receiving_fragment               := false;
            s.preamble_receiving               := false;

            case r.state is
               when IDLE => 

                  if duti.valid = '0' and duti.sop   = '0' and duti.eop   = '0' and duti.data  = C_PREAMBLE then
                     s.preamble                := duti.data;
                     s.receiving               := true;
                     s.preamble_receiving      := true;
                     s.state                   := PREAMBLE;

                  else
                     s.idle                    := duti.data;
                     s.input_demux             := NONE;
                     s.receiving               := false;
                     -- reset output registers
                     s.fragment_out            := C_AVALON_ST_SOURCE_TO_SINK;
                     s.priority_out            := C_AVALON_ST_SOURCE_TO_SINK;

                  end if;

               when PREAMBLE => 

                  if duti.valid = '0' and duti.sop   = '0' and duti.eop   = '0' and duti.data  = control_porti.sfd then
                     s.sfd                     := duti.data;
                     s.input_demux             := PATT_PRIORITY;
                     s.receiving               := true;
                     s.state                   := DATA;

                  elsif duti.valid = '0' and duti.sop   = '0' and duti.eop   = '0' and duti.data  = header_checki.smd_i_const then
                     s.smd_i                   := duti.data;
                     s.input_demux             := PATT_FRAGMENT;
                     s.receiving               := true;
                     s.receiving_fragment      := true;
                     s.state                   := DATA;
                     s.frag_no                 := (r.frag_no + 1) mod control_porti.no_of_fragment;

                  elsif duti.valid = '0' and duti.sop   = '0' and duti.eop   = '0' and duti.data  = header_checki.smd_c_const then
                     s.smd_c                   := duti.data;
                     s.input_demux             := PATT_FRAGMENT;
                     s.receiving               := true;
                     s.receiving_fragment      := true;
                     s.state                   := FRAG_NO;

                  else
                     s.preamble                := duti.data;
                     s.receiving               := true;
                     s.preamble_receiving      := true;

                  end if;

               when FRAG_NO =>
                  s.frag_const                 := duti.data;
                  s.input_demux                := PATT_FRAGMENT;
                  s.receiving                  := true;
                  s.receiving_fragment         := true;
                  s.state                      := DATA;
                  s.frag_no                    := (r.frag_no + 1) mod control_porti.no_of_fragment;

               when DATA => 
                  s.receiving                  := true;
                  s.cam_error                  := false;

                  case s.input_demux is 
                     when PATT_PRIORITY =>
                        s.priority_out         := duti;
                        s.crc_packet_en        := false;

                     when PATT_FRAGMENT =>
                        s.fragment_out         := duti;
                        s.crc_packet_en        := control_porti.packet_crc_en;
                        s.receiving_fragment   := true;
                        s.crc_mon_data         := nextCRC32_D8(duti.data, r.crc_mon_data);

                     when others =>
                        s.crc_packet_en        := false;
                        s.cam_error            := true;

                  end case;

                  -- eop is set during crc32 transaction
                  if duti.valid = '1' and duti.sop   = '0' and duti.eop = '1' and r.frag_no = control_porti.no_of_fragment-1
                  and r.input_demux = PATT_FRAGMENT then
                     s.state                   := CRC32_PACKET;

                  -- stop receiving after receive last byte of data
                  elsif duti.valid = '1' and duti.sop   = '0' and duti.eop = '1' then
                     s.state                   := TERMINATE;

                  end if;

               when CRC32_PACKET =>
                  -- reset output registers
                  s.fragment_out               := C_AVALON_ST_SOURCE_TO_SINK;
                  s.priority_out               := C_AVALON_ST_SOURCE_TO_SINK;

                  if r.crc_count < C_CRC32_NO_OF_BYTS then
                     s.crc_count               := r.crc_count + 1;

                     s.crc_received_data(C_BYTE_LENGTH-1 downto 0):= duti.data;
                     -- crc data buffer  
                     s.crc_received_data       := s.crc_received_data(C_BYTE_LENGTH-1 downto 0) & 
                                               s.crc_received_data(C_CRC32_DATA_WIDTH-1 downto C_BYTE_LENGTH);

                  -- last byte of packet CRC data
                  else
                     s.state                   := TERMINATE;
                     s.crc_count               := 0;

                     s.crc_received_data(C_BYTE_LENGTH-1 downto 0):= duti.data;
                     -- buffer input crc data 
                     s.crc_received_data       := s.crc_received_data(C_BYTE_LENGTH-1 downto 0) & 
                                                  s.crc_received_data(C_CRC32_DATA_WIDTH-1 downto C_BYTE_LENGTH);

                  end if;

               when TERMINATE =>
                  s.state                      := IDLE;
                  s.crc_packet_en              := false;
                  s.terminated                 := duti.data;
                  s.receiving                  := false;
                  s.cam_error                  := false;
                  -- reset output registers
                  s.fragment_out               := C_AVALON_ST_SOURCE_TO_SINK;
                  s.priority_out               := C_AVALON_ST_SOURCE_TO_SINK;

            end case;
         end if;

         ---------------------------------------------------------------------------
         -- check whether patterns is ready, if it stack set error register
         ---------------------------------------------------------------------------
         if (pattern_moni(1).ready /= '1' or pattern_moni(2).ready /= '1' ) and r.receiving then
            s.patt_not_ready             := true;

         else
            s.patt_not_ready             := false;

         end if;

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

         ---------------------------------------------------------------------------
         -- Assign the output port according command 
         ---------------------------------------------------------------------------
         if control_porti.priority_output = 1 then
            pattern_mono(1)              <= r.priority_out;
            pattern_mono(2)              <= r.fragment_out;

         elsif control_porti.priority_output = 2 then
            pattern_mono(2)              <= r.priority_out;
            pattern_mono(1)              <= r.fragment_out;
         end if;

      end if;  
   end process;

   -- fatal errors for this module
   control_porto               <= (patt_not_ready     => r.patt_not_ready, 
                                   cam_error          => r.cam_error);

   header_checko               <= (crc32_en           => r.crc_packet_en,
                                   receiving          => r.receiving,
                                   preamble_receiving => r.preamble_receiving,
                                   receiving_fragment => r.receiving_fragment,
                                   idle               => r.idle,
                                   preamble           => r.preamble,
                                   sfd                => r.sfd,
                                   smd_i              => r.smd_i,
                                   smd_c              => r.smd_c,
                                   frag_const         => r.frag_const,
                                   crc_mon_data       => r.crc_mon_data,
                                   crc_received_data  => r.crc_received_data);

   duto                        <= (ready              => boolean_to_stdlogic(r.dut_ready));

end architecture rtl;         