---------------------------------------------------------------------------
-- File           : ea_ethernet_output.vhd
-- Author(s)      : Nikola Avramovic
-- Created        : March 16th, 2019
---------------------------------------------------------------------------
-- Description    : This module generate Ethernet stream
---------------------------------------------------------------------------
--
---------------------------------------------------------------------------

library ieee; 
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   use work.p_parameters_and_constants.all;
   use work.p_functions.all;
   use work.p_interfaces.all;
   use work.p_generator_interface.all;
   use work.p_port_gen_interface.all;
   use work.PCK_CRC32_D8.all;

entity ea_ethernet_output is 
   port (clk           : in  std_logic;
         reset         : in  std_logic;
         -- from control port
         control_porti : in  TYPE_CONTROL_PORT_TO_ETH_OUTPUT;
         control_porto : out TYPE_ETH_OUTPUT_TO_CONTROL_PORT;
         -- to/from ethernet multiplexer
         input_muxi    : in  TYPE_INPUT_MUX_TO_ETH_OUTPUT;
         input_muxo    : out TYPE_ETH_OUTPUT_TO_INPUT_MUX;
         -- output port
         duti          : in  TYPE_AVALON_ST_SINK_TO_SOURCE;
         duto          : out TYPE_AVALON_ST_SOURCE_TO_SINK
   );
end ea_ethernet_output;

architecture rtl of ea_ethernet_output is

   type TYPE_STATE is (IDLE, PREAMBLE, SFD, SMD_I, SMD_C, FRAG_NO, DATA_AND_CRC, CRC32, TERMINATE);
   
   type TYPE_REGS is record
      state                    : TYPE_STATE;
      ready                    : boolean;
      crc_en                   : boolean;
      first_frag               : boolean;
      frag_no                  : natural;
      frag_const               : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      smd_const                : std_logic_vector(C_BYTE_LENGTH-1 downto 0);
      smd_i_count              : natural;
      smd_c_count              : natural;
      preamble_count           : natural;
      crc32_count              : natural;
      check_waiting            : boolean;
      data_read_out            : boolean;
      end_of_packet            : boolean;
      valid                    : std_logic;
      sop                      : std_logic;
      eop                      : std_logic;
      data                     : std_logic_vector(C_AVALON_ST_DATA_WIDTH - 1 downto 0);
      data_crc                 : std_logic_vector(C_CRC32_DATA_WIDTH - 1 downto 0);
   end record;
   
   constant CONST_REGS_RESET : TYPE_REGS := (
      state                    => IDLE,
      crc_en                   => false,
      ready                    => false,
      first_frag               => false,
      frag_no                  => 0,
      frag_const               => (others => '0'),
      smd_const                => (others => '0'),
      smd_i_count              => 0,
      smd_c_count              => 0,
      preamble_count           => 0,
      crc32_count              => 0,
      check_waiting            => false,
      data_read_out            => false,
      end_of_packet            => false,
      valid                    => '0',
      sop                      => '0',
      eop                      => '0',
      data                     => (others => '0'),
      data_crc                 => (others => '0')
   ); 
   
   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;

         case r.state is
            when IDLE => 
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := C_IDLE;
               s.preamble_count          := 0;
               s.ready                   := (duti.ready = '1');

               if (input_muxi.sop_priority or input_muxi.sop_fragment) 
                  and r.ready and control_porti.ready then
                  s.check_waiting        := true;

               end if;

               if s.check_waiting then
                  s.ready                := false;

                  if control_porti.send_priority_frame or control_porti.send_fragment_frame then
                     s.state             := PREAMBLE;
                     s.check_waiting     := false;

                  end if;
               end if;

            when PREAMBLE => 
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := C_PREAMBLE;


               if control_porti.input_mux_sel = SET_PRIORITY then 
                  -- send preamble according to command; default value should be 7 bytes
                  if s.preamble_count < control_porti.preamble_length-1 then
                     s.preamble_count    := r.preamble_count + 1;

                     -- ready signal must be set 4 cycles before data stream
                     -- ready signal is four times registered (eth_port, input_mux, avalon_fifo in and avalon fifo out)
                     if r.preamble_count = 4 then
                        s.ready          := true;
                     end if;

                  else
                     s.state             := SFD;

                  end if;

               elsif control_porti.input_mux_sel = SET_FRAG then
                  if (r.frag_no = 0) then 
                     -- send preamble according to command; default value should be 7 bytes
                     -- just first fragment has 7 bytes preable, others have 6 bytes preamble 
                     if s.preamble_count < control_porti.preamble_length-1 then
                        s.preamble_count := r.preamble_count + 1;

                        -- ready signal must be set 4 cycles before data stream
                        -- ready signal is four times registered (eth_port, input_mux, avalon_fifo in and avalon fifo out)
                        if r.preamble_count = 4 then
                           s.ready       := true;
                        end if;

                     else
                        s.state          := SMD_I;
                        s.first_frag     := true;
                        s.smd_c_count    := 0;

                        -- smd i will be increment for every next frame (with modulo according to standard)
                        s.smd_const      := smd_i_const(s.smd_i_count mod C_PREEMPTABLE_NO);
                        s.smd_i_count    := (r.smd_i_count + 1);

                        -- according to the standard IEEE802.3br (TSN-br):
                        -- modulo-4 counter containing the fragment count for the next outgoing continuation mPacket
                        s.frag_const     := frag_no_const(r.frag_no mod C_PREEMPTABLE_NO);
                        s.frag_no        := (r.frag_no + 1) mod control_porti.no_of_fragment;

                     end if;
                  else
                     s.first_frag        := false;

                     -- send preamble according to command; default value should be 7 bytes
                     -- just first fragment has 7 bytes preable, others have 6 bytes preamble 
                     if s.preamble_count < control_porti.preamble_length - 2 then
                        s.preamble_count := r.preamble_count + 1;

                        -- ready signal must be set 4 cycles before data stream
                        -- ready signal is four times registered (eth_port, input_mux, avalon_fifo in and avalon fifo out)
                        if r.preamble_count = 4 then
                           s.ready       := true;

                        end if;

                     else
                        -- according to the standard IEEE802.3br (TSN-br):
                        -- modulo-4 counter containing the fragment count for the next outgoing continuation mPacket
                        s.frag_const     := frag_no_const(r.frag_no mod C_PREEMPTABLE_NO);
                        s.smd_const      := smd_c_const(r.smd_c_count mod C_PREEMPTABLE_NO);
                        s.frag_no        := (r.frag_no + 1) mod control_porti.no_of_fragment;
                        s.smd_c_count    := (r.smd_c_count + 1);
                        s.state          := SMD_C;

                     end if;
                  end if;
               end if;

            when SFD => 
               s.state                   := DATA_AND_CRC;
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := control_porti.sfd;
               s.crc_en                  := false;

            when SMD_I =>
               s.state                   := DATA_AND_CRC;
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := r.smd_const; 
               s.crc_en                  := true;

            when SMD_C =>
               s.state                   := FRAG_NO;
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := r.smd_const;

            when FRAG_NO =>
               s.state                   := DATA_AND_CRC;
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := r.frag_const;
               s.crc_en                  := true;

            when DATA_AND_CRC => 
               s.valid                   := input_muxi.mux_out.valid;
               s.sop                     := input_muxi.mux_out.sop;
               s.eop                     := input_muxi.mux_out.eop;
               s.data                    := input_muxi.mux_out.data;
               s.data_read_out           := true;

               if r.crc_en then
                  s.data_crc             := nextCRC32_D8(input_muxi.mux_out.data,r.data_crc);

               end if;

               if input_muxi.mux_out.eop = '1' then
                  if r.frag_no = control_porti.no_of_fragment-1 and r.crc_en then
                     s.state             := CRC32; 
                     s.end_of_packet     := false;
                     s.crc_en            := false;
                  else
                     s.state             := TERMINATE;
                     s.end_of_packet     := true;
                     s.crc_en            := false;

                  end if;
               end if;

            when CRC32 =>
               if r.crc32_count < C_CRC32_NO_OF_BYTS then
                  s.valid                   := '1';
                  s.sop                     := '0';
                  s.eop                     := '1';
                  s.data                    := r.data_crc(r.crc32_count*8+7 downto r.crc32_count*8);
                  s.crc32_count             := r.crc32_count + 1;

               else
                  s.valid                   := '1';
                  s.sop                     := '0';
                  s.eop                     := '1';
                  s.data                    := r.data_crc(r.crc32_count*8+7 downto r.crc32_count*8);
                  s.crc32_count             := 0;
                  s.state                   := TERMINATE;
                  s.end_of_packet           := true;

               end if;

            when TERMINATE =>
               s.state                   := IDLE;
               s.valid                   := '0';
               s.sop                     := '0';
               s.eop                     := '0';
               s.data                    := control_porti.terminated;
               s.data_read_out           := false;
               s.end_of_packet           := false;

         end case;


         -- Assign the register record -------------------------------------------
         r                     <= s;
      end if;  
   end process;   

   control_porto               <= (end_of_packet  => r.end_of_packet);

   input_muxo                  <= (ready          => boolean_to_stdlogic(r.ready),
                                   eop            => r.eop,
                                   data_read_out  => r.data_read_out);

   duto                        <= (valid          => r.valid,
                                   sop            => r.sop,  
                                   eop            => r.eop,  
                                   data           => r.data);

end architecture rtl;         