library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.ip_package.all;
use work.math_pkg.all;

-- IPv4 Handler
-- This entity parses IPv4 packets read from the input FIFO, and extracts the underlying Protocol (Payload)
-- to the respective output FIFO together with the Src and Dest Addresses.

-- The input FIFO has to be a 32-bit wide FWFT (First Word Fall Through) FIFO. The IPv4 packet has to be
-- 32-bit word aligned (padded with zeroes in the end to the closest 32-bit boundary), and the first word 
-- before the actual packet has to be the length of the following IPv4 packet (in 32-bit words + padding). 
-- This is necessary to recover from spurious packets.
-- ex.
-- 31                             0
-- +------------------------------+
-- |        LENGTH=4              |
-- +------------------------------+
-- |        IPv4 Packet           |
-- +                              +
-- |                              |
-- +                              +
-- |                              |
-- +               +--------------+
-- |               | PADDING      |
-- +---------------+--------------+

-- The output FIFO distribution is controlled by the 'LAYER3_PROTOCOLS' constant in the 'ip_package' package.
-- The array position of the protocol version number is also the id of the output FIFO where this protocol
-- is sent to. First word on the output FIFO is the Source address, second the Destination Address, third
-- the payload length (in 32-bit words + padding), followed by the actual packet.
-- ex.
-- 31                             0
-- +------------------------------+
-- |        Source Address        |
-- +------------------------------+
-- |        Destination Address   |
-- +------------------------------+
-- |        LENGTH=4              |
-- +------------------------------+
-- |        UDP Packet            |
-- +                              +
-- |                              |
-- |                              +
-- |                              |
-- +               +--------------+
-- |               | PADDING      |
-- +---------------+--------------+

-- This Entity features 2 architecture implementations.
-- The 'no_frag' implementation does not support fragmentation and just drops fragmented packets. This needs the least amount of resources
-- The 'with_frag' implementation supports fragmentation as stated in RFC 791 (fragments of any size received in any order with auto adjusted
-- timeout according to transmitted TTL). The re-assembly needs considerable more resources for packet buffering and state holding.
-- The generics allow tuning of the supported packet sizes and used resources.

entity ipv4_in_handler is
    generic(
        CLK_FREQ            : integer := 20000000;  -- Frequency of input clock in Hz
        MAX_FRAG_SIZE       : integer := 1600;      -- Maximum re-assembled IP packet size. If a re-assembled packet exceeds this threshold it is dropped. (Does not limit the size of received non-fragmented packets)
        MAX_PARALLEL_FRAG   : integer := 1;         -- Maximum number of parallel running re-assemble procedures. If a fragment of a new Ip packet is received, while 'MAX_PARALLEL_FRAG' packets are already in the re-assembly procedure, it is dropped.
        DEFAULT_FRAG_TIMEOUT: integer := 5          -- Default timeout in seconds until which the re-assembly procedure of an IP packet has to be completed before being dropped to fre up resources.
    );
    port (
        clk     : in std_logic;                                         -- Input Clock
        reset   : in std_logic;                                         -- Synchronous Reset
        empty   : in std_logic;                                         -- Input FIFO empty flag
        rd      : out std_logic;                                        -- Input FIFO read signal
        data_in : in std_logic_vector(31 downto 0);                     -- Input FIFO data signal
        full    : in std_logic_vector(LAYER3_PROTOCOL_NUM-1 downto 0);  -- Output FIFO full flag
        wr      : out std_logic_vector(LAYER3_PROTOCOL_NUM-1 downto 0); -- Output FIFO write signal
        data_out: out IP_OUTPUT_TYPE                                    -- Output FIFO data signal
    );
end entity;

architecture with_frag of ipv4_in_handler is
    
    --*****COMPONENT DECLARATION*****
    -- The RAM is used for reassembly of ip fragments.
    -- The RAM should have a READ latency of 1 clock cycle, 
    -- and a WRITE latency smaller than 5 clock cycles
    component single_port_ram is
        generic (
            ADDR_WIDTH      : integer := 8;
            DATA_WIDTH      : integer := 12;
            MEMORY_SIZE     : integer := DATA_WIDTH*(2**ADDR_WIDTH)
        );
        port (
            clk         : in std_logic;
            addr        : in std_logic_vector(ADDR_WIDTH-1 downto 0);
            wen         : in std_logic;
            ren         : in std_logic;
            wr_data     : in std_logic_vector(DATA_WIDTH-1 downto 0);
            rd_data     : out std_logic_vector(DATA_WIDTH-1 downto 0)
        );
    end component;
    
    --*****CONSTANT DECLARATION*****
    -- The width of the RAM address has a lower bound equal to the fragment offset field
    constant RAM_ADDR_WIDTH : integer := max(log2c(MAX_PARALLEL_FRAG*MAX_FRAG_SIZE), 14);
    
    --*****TYPE DECLARATION*****
    -- FSM states. Explained delow in detail
    type PARSER_STAGE_TYPE is (IPv4_INIT, IPv4_HEADER_1, IPv4_HEADER_2, IPv4_HEADER_3, IPv4_HEADER_4, 
        IPv4_HEADER_5, IPv4_PAYLOAD_LENGTH, IPv4_PAYLOAD, IPv4_FRAGMENT_PRE, IPv4_FRAGMENT, 
        IPv4_FRAGMENT_POST, IPv4_BUFFER_SRC, IPv4_BUFFER_DEST, IPv4_BUFFER_LENGTH, IPv4_BUFFER_PAYLOAD,
        SKIP_HEADER, SKIP_PACKET);
    -- Array of buffer identifiers. A buffer identifier is the concatenation of source, destination, protocol, and identification fields
    -- NOTE: The MAX_PARALLEL_FRAG array position is not mapped to a buffer, but is used for comparison purposes
    type BUFFER_ID_ARRAY is array (MAX_PARALLEL_FRAG downto 0) of std_logic_vector(87 downto 0);
    -- Array of bitmaps used to identify received fragment words.
    type BUFFER_BITMAP_ARRAY is array (MAX_PARALLEL_FRAG-1 downto 0) of std_logic_vector((MAX_FRAG_SIZE/4)-1 downto 0);
    -- Array of buffer timers used for timeout checking
    type BUFFER_TIMER_ARRAY is array (MAX_PARALLEL_FRAG-1 downto 0) of unsigned(7 downto 0);
    -- Array of address offsets for the RAM. Used as constant
    type BUFFER_ADDR_OFFSET_TYPE is array (MAX_PARALLEL_FRAG-1 downto 0) of unsigned(RAM_ADDR_WIDTH-1 downto 0);
    -- Array of packet sizes in buffers
    type FRAG_SIZE_ARRAY is array (MAX_PARALLEL_FRAG-1 downto 0) of std_logic_vector(13 downto 0);
    -- Array of 32-bit buffer word counters. Used for counting new received fragments and determining if the packet is completely re-assebled
    type BUFFER_WORD_COUNTER_ARRAY is array (MAX_PARALLEL_FRAG-1 downto 0) of integer range 0 to (MAX_FRAG_SIZE/4);
    
    
    --*****SIGNAL DECLARATION*****
    -- FSM state
    signal stage, stage_next : PARSER_STAGE_TYPE := IPv4_INIT;
    -- 32-bit aligned total packet length
    signal packet_length, packet_length_next : unsigned(13 downto 0) := (others => '0');
    -- 32-bit aligned header length
    signal header_length, header_length_next : unsigned(3 downto 0) := (others => '0');
    -- 32-bit word counter (Counts words read from input fifo)
    signal read_cnt : unsigned(13 downto 0) := (others => '0');
    -- Intermediate input read signal. (Read from output port not allowed)
    signal rd_sig : std_logic := '0';
    -- States if the current processed packet is a fragment
    signal is_fragment, is_fragment_next : std_logic := '0';
    -- States if the current processed packet is the last fragment (Needed to determine total payload size)
    signal is_last_fragment, is_last_fragment_next : std_logic := '0';
    -- Clock with 1 Hz Frequency
    signal sec_clk : std_logic := '0';
    -- Counter used to generate 'sec_clk'
    signal sec_cnt : integer range 0 to (CLK_FREQ/2)-1 := 0;
    -- ID of current output fifo (Used to MUX output FIFOs)
    signal output_id, output_id_next : integer range 0 to LAYER3_PROTOCOL_NUM-1 := 0;
    -- Buffer Identifiers for each buffer. See 'BUFFER_ID_ARRAY'
    signal buffer_id, buffer_id_next : BUFFER_ID_ARRAY := (others => (others => '0'));
    -- Used as Constant. See 'BUFFER_ADDR_OFFSET_TYPE'
    signal buffer_addr_offset : BUFFER_ADDR_OFFSET_TYPE := (others => (others => '0'));
    -- Bitmap array. See 'BUFFER_BITMAP_ARRAY'
    signal buffer_bitmap, buffer_bitmap_next : BUFFER_BITMAP_ARRAY := (others => (others => '0'));
    -- 32-bit word aligned fragment offset
    signal frag_offset, frag_offset_next : unsigned(13 downto 0) := (others => '0');
    -- Packet size array. See 'FRAG_SIZE_ARRAY'
    signal frag_size, frag_size_next : FRAG_SIZE_ARRAY := (others => (others => '0'));
    -- TimeToLive temporary storage
    signal ttl, ttl_next : std_logic_vector(7 downto 0);
    -- Buffer Timers. See 'BUFFER_TIMER_ARRAY'
    signal buffer_timer : BUFFER_TIMER_ARRAY := (others => (others => '0'));
    -- Received fragment word counters. See BUFFER_WORD_COUNTER_ARRAY
    signal buffer_word_cnt, buffer_word_cnt_next : BUFFER_WORD_COUNTER_ARRAY := (others => 0);
    -- Timeout values of buffer timers
    signal max_buffer_timer, max_buffer_timer_next : BUFFER_TIMER_ARRAY := (others => (others => '0'));
    -- Signal used to reset buffer timers
    signal reset_buffer_timer : std_logic := '0';
    -- ID of buffer timer to be reset
    signal reset_buffer_timer_id : integer range 0 to MAX_PARALLEL_FRAG-1 := 0;
    -- ID of current buffer
    signal cur_buffer_id, cur_buffer_id_next : integer range 0 to MAX_PARALLEL_FRAG-1 := 0;
    -- Signal used to reset the word counter
    signal reset_read_cnt : std_logic;
    -- Intermediate output signal
    signal data_out_sig : std_logic_vector(31 downto 0);
    -- Intermediate output signal, and its 1 clk cycle delayed variant
    signal wr_sig, wr_sig_del : std_logic;
    -- RAM Signals
    signal buffer_addr, buffer_addr_next : unsigned(RAM_ADDR_WIDTH-1 downto 0) := (others => '0');
    signal buffer_wen, buffer_ren  : std_logic := '0';
    signal buffer_wr_data, buffer_rd_data : std_logic_vector(31 downto 0) := (others => '0');
    -- Old RAM Address (Used to reset address on read error)
    signal buffer_addr_old, buffer_addr_old_next : unsigned(RAM_ADDR_WIDTH-1 downto 0) := (others => '0');
    
    
    --*****ALIAS DEFINATION*****
    --IPv4 HEADER
    alias ip_version    : std_logic_vector(3 downto 0) is data_in(31 downto 28);
    alias ip_ihl        : std_logic_vector(3 downto 0) is data_in(27 downto 24);
    alias ip_dscp       : std_logic_vector(5 downto 0) is data_in(23 downto 18);
    alias ip_ecn        : std_logic_vector(1 downto 0) is data_in(17 downto 16);
    alias ip_length     : std_logic_vector(15 downto 0) is data_in(15 downto 0);
    alias ip_id         : std_logic_vector(15 downto 0) is data_in(31 downto 16);
    alias ip_DF_flag    : std_logic is data_in(14);
    alias ip_MF_flag    : std_logic is data_in(13);
    alias ip_frag_offset: std_logic_vector(12 downto 0) is data_in(12 downto 0);
    alias ip_ttl        : std_logic_vector(7 downto 0) is data_in(31 downto 24);
    alias ip_protocol   : std_logic_vector(7 downto 0) is data_in(23 downto 16);
    alias ip_checksum   : std_logic_vector(15 downto 0) is data_in(15 downto 0);
    
    --*****FUNCTION DECLARATION*****
    
    -- Compares all buffer identifiers with the last identifier. If there is a match, it returns the
    -- array index of the buffer identifier that matches, else it returns the highest index (which is the
    -- base of the comparisons)
    function match_buffer_id (ids : BUFFER_ID_ARRAY) return integer is
        variable id, max_id : integer := 0;
    begin
        max_id  := ids'length - 1;
        id      := max_id;
        for i in 0 to ids'length - 2 loop
            if(ids(i) = ids(max_id)) then
                id := i;
            end if;
        end loop;
        return id;
    end function;
    
    -- Returns the last zero buffer id index
    function empty_buffer_id (ids : BUFFER_ID_ARRAY) return integer is
        variable id : integer := 0;
    begin
        id      := ids'length - 1;
        for i in 0 to ids'length - 2 loop
            if(ids(i) = (ids(i)'reverse_range => '0')) then
                id := i;
            end if;
        end loop;
        return id;
    end function;
    
    -- Truncates the lower 2 bits of the input, and if they are not equal zero, adds one to the result.
    -- This is used to round the byte length to 32-bit word length.
    function normalize_length (len : std_logic_vector(15 downto 0)) return std_logic_vector is
        variable tmp : std_logic_vector(13 downto 0) := (others => '0');
    begin
        tmp := len(15 downto 2);
        if(len(1 downto 0) /= "00") then
            tmp := std_logic_vector(unsigned(tmp) + to_unsigned(1, tmp'length));
        end if;
        return tmp;
    end function;
    
begin
    
    -- Generic Assertions
    assert (MAX_PARALLEL_FRAG > 0) report "MAX_PARALLEL_FRAG has to be greater than 0" severity failure;
    assert (MAX_FRAG_SIZE mod 4 = 0) report "MAX_FRAG_SIZE has to be multiple of 4" severity failure;
    assert (MAX_FRAG_SIZE <= 65516) report "MAX_FRAG_SIZE has to be smaller or equal to 65516" severity error;
    assert (DEFAULT_FRAG_TIMEOUT <= 255) report "DEFAULT_FRAG_TIMEOUT has to be smaller or equal to 255" severity error;
    
    -- Generic Dependant Constant
    -- Does not change its value after generation
    -- TODO: Change to constant with function initialization
    const_gen : for i in 0 to buffer_addr_offset'length-1 generate
        buffer_addr_offset(i) <= to_unsigned(MAX_FRAG_SIZE*i,buffer_addr_offset(i)'length);
    end generate;
    
    --*****COMPONENT INSTANTIATION*****
    ram_inst : single_port_ram
        generic map (
            ADDR_WIDTH  => RAM_ADDR_WIDTH,
            DATA_WIDTH  => 32,
            MEMORY_SIZE => MAX_PARALLEL_FRAG*MAX_FRAG_SIZE
        )
        port map (
            clk         => clk,
            addr        => std_logic_vector(buffer_addr),
            wen         => buffer_wen,
            ren         => buffer_ren,
            wr_data     => buffer_wr_data,
            rd_data     => buffer_rd_data
        );
    
    rd <= rd_sig;
    
    -- TODO: Make only wr signal conditional and connect ALL output FIFOs to the Signal
    -- This process is responsible for MUXing the correct signal to the correct output FIFO
    output_prc : process(all)
    begin
        -- Reading from input FIFO
        if(stage /= IPv4_BUFFER_PAYLOAD) then
            data_out(output_id) <= data_out_sig;
            wr(output_id)       <= wr_sig;
        -- Reading from RAM Buffer
        else
            data_out(output_id) <= buffer_rd_data;
            -- The state machine toggles the 'wr_sig_del' on the first cycle when full is high
            -- The and gate here prevents this from propagating to the actual fifo.
            wr(output_id)       <= wr_sig_del and (not full(output_id));
        end if;
    end process;
    
    -- Main State Machine
    -- STATE                DESCRIPSION
    -- IPv4_INIT            Initial and idle state. Responsible for checking the buffer timers, and reading the packet length from the input FIFO
    -- IPv4_HEADER_1        Parsing first word of IPv4 HEADER
    -- IPv4_HEADER_2        Parsing second word of IPv4 HEADER
    -- IPv4_HEADER_3        Parsing third word of IPv4 HEADER
    -- IPv4_HEADER_4        Parsing fourth word of IPv4 HEADER and writing SRC Addr to output FIFO (if not a fragment)
    -- IPv4_HEADER_5        Parsing fifth word of IPv4 HEADER and writing DEST Addr to output FIFO (if not a fragment)
    -- IPv4_PAYLOAD_LENGTH  Writing packet(payload) length to output FIFO
    -- IPv4_PAYLOAD         Writing of packet(payload) from input FIFO to output FIFO
    -- IPv4_FRAGMENT_PRE    Fragment pre-processing. Checking if fragment is part of already initiated packet re-assembly, initiating new packetre-assembly, or dropping packet if no resources available
    -- IPv4_FRAGMENT        Writing fragment from input FIFO to RAM buffer
    -- IPv4_FRAGMENT_POST   Fragment post-processing. Checking if received fragment completes the re-assembled packet.
    -- IPv4_BUFFER_SRC      Writing re-assembled packet(payload) SRC Address to output FIFO
    -- IPv4_BUFFER_DEST     Writing re-assembled packet(payload) DEST Address to output FIFO
    -- IPv4_BUFFER_LENGTH   Writing re-assembled packet(payload) length to output FIFO
    -- IPv4_BUFFER_PAYLOAD  Writing re-assembled packet(payload) from RAM to output FIFO
    -- SKIP_HEADER          Skip to beginning of Payload in input FIFO
    -- SKIP_PACKET          Skip to end of packet in input FIFO
    parser_prc : process(all)
        variable tmp                : integer range 0 to max(MAX_PARALLEL_FRAG,LAYER3_PROTOCOL_NUM) := 0;
        variable tmp_frag_offset    : unsigned(buffer_addr'length-1 downto 0) := (others => '0');
    begin
        --DEFAULT Registered
        stage_next              <= stage;
        packet_length_next      <= packet_length;
        header_length_next      <= header_length;
        is_fragment_next        <= is_fragment;
        is_last_fragment_next   <= is_last_fragment;
        frag_offset_next        <= frag_offset;
        buffer_id_next          <= buffer_id;
        ttl_next                <= ttl;
        output_id_next          <= output_id;
        frag_size_next          <= frag_size;
        cur_buffer_id_next      <= cur_buffer_id;
        buffer_bitmap_next      <= buffer_bitmap;
        buffer_addr_next        <= buffer_addr;
        buffer_addr_old_next    <= buffer_addr_old;
        buffer_word_cnt_next    <= buffer_word_cnt;
        -- DEFAULT Unregistered
        rd_sig                  <= '0';
        wr_sig                  <= '0';
        data_out_sig            <= (others => '0');
        reset_buffer_timer      <= '0';
        reset_buffer_timer_id   <= 0;
        buffer_wen              <= '0';
        buffer_ren              <= '0';
        buffer_wr_data          <= (others => '0');
        reset_read_cnt          <= '0';
        
        case(stage) is
            -- Initial/Idle State
            when IPv4_INIT =>
                -- Reset packet Byte Counter
                reset_read_cnt  <= '1';
                -- Check Buffer timers for timeouts
                for i in 0 to MAX_PARALLEL_FRAG-1 loop
                    -- If timeout reached
                    if(buffer_timer(i) >= max_buffer_timer(i)) then
                        -- Free Buffer ID
                        buffer_id_next(i)   <= (others => '0');
                        -- The rest of the Buffer related values are reset upon buffer selection
                    end if;
                end loop;
                -- Read Packet Length from input
                if (empty = '0') then
                    packet_length_next      <= unsigned(data_in(13 downto 0));
                    rd_sig                  <= '1';
                    -- Reset Parsing Flags
                    is_fragment_next        <= '0';
                    is_last_fragment_next   <= '0';
                    -- Begin Processing
                    stage_next              <= IPv4_HEADER_1;
                end if;
            -- First IPv4 Header word (Fields: Version, IHL, DSCP, ECN, Total Length)
            when IPv4_HEADER_1 =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- Wrong IP Version or Packet Length missmatch, skip packet
                    if (ip_version /= "0100" or normalize_length(ip_length) /= std_logic_vector(packet_length)) then
                        stage_next  <= SKIP_PACKET;
                    -- Store data and continue Parsing
                    else
                        header_length_next      <= unsigned(ip_ihl);
                        stage_next              <= IPv4_HEADER_2;
                    end if;
                end if;
            -- Second IPv4 Header word (Fields: Identification, Flags, Fragment Offset)
            when IPv4_HEADER_2 =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- No fragmentation, continue parsing normaly
                    if (ip_MF_flag = '0' and ip_frag_offset = (ip_frag_offset'reverse_range => '0')) then
                        stage_next  <= IPv4_HEADER_3;
                    -- IP Fragmentation
                    else
                        is_fragment_next                                <= '1';
                        frag_offset_next                                <= unsigned(std_logic_vector'(ip_frag_offset & "0")); --'std_logic_vector needed to avoid "Ambiguous type in infix expression" 
                        -- Temporal storage for comparison
                        buffer_id_next(MAX_PARALLEL_FRAG)(15 downto 0)  <= ip_id;
                        -- Needed to determine the total size of the reassembled packet
                        if (ip_MF_flag = '0') then
                            is_last_fragment_next                       <= '1';
                        end if;
                        -- Continue Parsing
                        stage_next                                      <= IPv4_HEADER_3;
                    end if;
                end if;
            -- Third IPv4 Header word (Fields: TTL, Protocol, Checksum)
            when IPv4_HEADER_3 =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- Select Output according to Layer 3 Protocol
                    tmp := LAYER3_PROTOCOL_NUM;
                    for i in 0 to LAYER3_PROTOCOL_NUM-1 loop
                        if(ip_protocol = LAYER3_PROTOCOLS(i)) then
                            tmp := i;
                        end if;
                    end loop;
                    -- No Protocol match, skip packet
                    if (tmp = LAYER3_PROTOCOL_NUM) then
                        stage_next  <= SKIP_PACKET;
                    -- Protocol Match, continue parsing
                    else
                        output_id_next  <= tmp;
                        stage_next      <= IPv4_HEADER_4;
                    end if;
                    -- Store Fragment relevant data
                    if (is_fragment = '1') then
                        ttl_next                                        <= ip_ttl;
                        -- Temporal storage for comparison
                        buffer_id_next(MAX_PARALLEL_FRAG)(23 downto 16) <= ip_protocol;
                    end if;
                end if;
            -- Fourth IPv4 Header word (Fields: Src Address)
            when IPv4_HEADER_4 =>
                if (empty = '0') then
                    -- Store relevant fragment data
                    if (is_fragment = '1') then
                        rd_sig  <= '1';
                        -- Temporal storage for comparison
                        buffer_id_next(MAX_PARALLEL_FRAG)(87 downto 56)     <= data_in;
                    -- Write Src Address to Output FIFO
                    elsif (full(output_id) = '0') then
                        rd_sig          <= '1';
                        wr_sig          <= '1';
                        data_out_sig    <= data_in;
                    end if;
                    -- In both cases, Continue Parsing
                    if (is_fragment = '1' or full(output_id) = '0') then
                        stage_next      <= IPv4_HEADER_5;
                    end if;
                end if;
            -- Fourth IPv4 Header word (Fields: Dest Address)
            when IPv4_HEADER_5 =>
                if (empty = '0') then
                    -- Store relevant fragment data
                    if (is_fragment = '1') then
                        rd_sig  <= '1';
                        -- Temporal storage for comparison
                        buffer_id_next(MAX_PARALLEL_FRAG)(55 downto 24) <= data_in;
                    -- Write Dst Address to Output FIFO
                    elsif (full(output_id) = '0') then
                        rd_sig          <= '1';
                        wr_sig          <= '1';
                        data_out_sig    <= data_in;
                    end if;
                    -- In both cases
                    if (is_fragment = '1' or full(output_id) = '0') then
                        -- Check Header Size
                        if (read_cnt /= ("0000000000" & header_length)) then
                            -- If Header has "Options", skip to Payload
                            stage_next  <= SKIP_HEADER;
                        else 
                            if (is_fragment = '1') then
                                -- Fragment Processing
                                stage_next  <= IPv4_FRAGMENT_PRE;
                            else
                                -- Payload Processing
                                stage_next  <= IPv4_PAYLOAD_LENGTH;
                            end if;
                        end if;
                    end if;
                end if;
            -- Push Payload Length
            when IPv4_PAYLOAD_LENGTH =>
                -- Write Payload Length to Output FIFO
                if (full(output_id) = '0') then
                    wr_sig                      <= '1';
                    data_out_sig(13 downto 0)   <= std_logic_vector(packet_length - ("0000000000" & header_length));
                    stage_next                  <= IPv4_PAYLOAD;
                end if;
            -- Push Payload
            when IPv4_PAYLOAD =>
                -- Write to Output FIFO
                if (empty = '0' and full(output_id) = '0') then
                    rd_sig          <= '1';
                    wr_sig          <= '1';
                    data_out_sig    <= data_in;
                    -- End Of Packet
                    if (read_cnt = packet_length) then
                        -- Done, process next packet
                        stage_next  <= IPv4_INIT;
                    end if;
                end if;
            -- Fragment Pre-Processing
            when IPv4_FRAGMENT_PRE =>
                tmp := match_buffer_id(buffer_id);
                -- Existing Buffer ID Match (Fragment is part of packet in a buffer)
                if(tmp /= MAX_PARALLEL_FRAG) then
                    -- Calculate buffer address
                    tmp_frag_offset                 := (others => '0');
                    tmp_frag_offset(13 downto 0)    := frag_offset;
                    buffer_addr_next                <= buffer_addr_offset(tmp) + tmp_frag_offset;
                    -- Reset timeout if needed
                    if (max_buffer_timer(tmp) < unsigned(ttl)) then
                        max_buffer_timer_next(tmp)  <= unsigned(ttl);
                    end if;
                    -- Save Buffer ID
                    cur_buffer_id_next              <= tmp;
                    -- Store Fragment
                    stage_next                      <= IPv4_FRAGMENT;
                -- Fragment is not part of a packet in a buffer
                else
                    -- Search for free buffer
                    tmp := empty_buffer_id(buffer_id);
                    -- Buffer Found
                    if (tmp /= MAX_PARALLEL_FRAG) then
                        -- Store Buffer ID
                        buffer_id_next(tmp)             <= buffer_id(MAX_PARALLEL_FRAG);
                        -- Reset Bitmap
                        buffer_bitmap_next(tmp)         <= (others => '0');
                        -- Reset Buffer Timer
                        reset_buffer_timer              <= '1';
                        reset_buffer_timer_id           <= tmp;
                        -- Reset Maximum Fragment Size
                        frag_size_next(tmp)             <= (others => '0');
                        -- Reset fragment word cntouer
                        buffer_word_cnt_next(tmp)       <= 0;
                        -- Calculate buffer address
                        tmp_frag_offset                 := (others => '0');
                        tmp_frag_offset(13 downto 0)    := frag_offset;
                        buffer_addr_next                <= buffer_addr_offset(tmp) + tmp_frag_offset;
                        -- Reset timeout if needed
                        if (max_buffer_timer(tmp) < unsigned(ttl)) then
                            max_buffer_timer_next(tmp)  <= unsigned(ttl);
                        end if;
                        -- Save Buffer ID
                        cur_buffer_id_next              <= tmp;
                        stage_next                      <= IPv4_FRAGMENT;
                    -- No buffer available, skip packet.
                    else
                        stage_next  <= SKIP_PACKET;
                    end if;
                end if;
            -- Store Fragment
            when IPv4_FRAGMENT =>
                if (empty = '0') then
                    -- Re-assembled packet exceeds buffer resources or fragments included in packet already received
                    if (to_integer(frag_offset) > MAX_FRAG_SIZE or buffer_bitmap(cur_buffer_id)(to_integer(frag_offset)) = '0') then
                        -- Free Buffer ID
                        buffer_id_next(cur_buffer_id)   <= (others => '0');
                        -- The rest of the Buffer related values are reset upon buffer selection
                        -- Skip Rest of Packet
                        stage_next  <= SKIP_PACKET;
                    else
                        rd_sig          <= '1';
                        -- Write Data to Buffer
                        buffer_wen      <= '1';
                        buffer_wr_data  <= data_in;
                        -- Mark BITMAP
                        buffer_bitmap_next(cur_buffer_id)(to_integer(frag_offset))  <= '1';
                        -- Increment Word Counter
                        buffer_word_cnt_next(cur_buffer_id) <= buffer_word_cnt(cur_buffer_id) + 1;
                        -- End of Packet
                        if (read_cnt = packet_length) then
                            -- If this is the last fragment, save total length
                            if (is_last_fragment = '1') then
                                frag_size_next(cur_buffer_id)   <= std_logic_vector(frag_offset);
                            end if;
                            -- Continue with Post-Processing
                            stage_next      <= IPv4_FRAGMENT_POST;
                        else 
                            -- Increment Pointers
                            buffer_addr_next    <= buffer_addr + 1;
                            frag_offset_next    <= frag_offset + 1;
                        end if;
                    end if;
                end if;
            -- Fragment Postprocessing
            when IPv4_FRAGMENT_POST =>
                -- Check if BITMAP complete (All fragments of packet received)
                -- Done by comparing the number of received fragment words to the expected size
                -- 'frag_size' is either zero or the expected value, and 'buffer_word_cnt' is at least 1 when this state is entered
                if (to_integer(unsigned(frag_size(cur_buffer_id))) = buffer_word_cnt(cur_buffer_id)) then
                    -- Reset Buffer Address
                    buffer_addr_next    <= buffer_addr_offset(cur_buffer_id);
                    buffer_addr_old_next<= buffer_addr_offset(cur_buffer_id);
                    -- Reset read count
                    reset_read_cnt      <= '1';
                    -- Write buffer packet to Output FIFO
                    stage_next          <= IPv4_BUFFER_SRC;
                else
                    -- Done, process next packet
                    stage_next  <= IPv4_INIT;
                end if;
            -- Push Src Address
            when IPv4_BUFFER_SRC =>
                if (full(output_id) = '0') then
                    -- Write Packet Length
                    wr_sig          <= '1';
                    data_out_sig    <= buffer_id(MAX_PARALLEL_FRAG)(87 downto 56);
                    -- Next State
                    stage_next      <= IPv4_BUFFER_DEST;
                end if;
            -- Push Dest Address
            when IPv4_BUFFER_DEST =>
                if (full(output_id) = '0') then
                    -- Write Packet Length
                    wr_sig          <= '1';
                    data_out_sig    <= buffer_id(MAX_PARALLEL_FRAG)(55 downto 24);
                    -- Next State
                    stage_next      <= IPv4_BUFFER_LENGTH;
                end if;
            -- Push Payload Length
            when IPv4_BUFFER_LENGTH =>
                if (full(output_id) = '0') then
                    -- Write Packet Length
                    wr_sig                      <= '1';
                    data_out_sig(13 downto 0)   <= frag_size(cur_buffer_id);
                    -- Next State
                    stage_next                  <= IPv4_BUFFER_PAYLOAD;
                end if;
            -- Push Payload from Buffer
            when IPv4_BUFFER_PAYLOAD =>
                -- Buffer read complete (End Of Packet)
                if (buffer_addr_old = packet_length) then
                    -- Free Buffer ID
                    buffer_id_next(cur_buffer_id)   <= (others => '0');
                    -- The rest of the Buffer related values are reset upon buffer selection
                    -- Done, process next packet
                    stage_next  <= IPv4_INIT;
                -- Buffer Read in progress
                else
                    -- Write from Buffer to Output FIFO
                    if (full(output_id) = '0') then
                        -- Read, and write output next cycle
                        -- Note that 'wr_sig_del' is actually connected to the output fifo
                        wr_sig              <= '1';
                        buffer_ren          <= '1';
                        buffer_addr_next    <= buffer_addr + 1;
                        buffer_addr_old_next<= buffer_addr;
                    -- Output Fifo Full, reset read position
                    else
                        -- Reset buffer address
                        buffer_addr_next  <= buffer_addr_old;
                    end if;
                end if;
            -- Skip until beginning of payload
            when SKIP_HEADER =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- End of Header
                    if(read_cnt = header_length) then
                        if (is_fragment = '1') then
                            -- Fragment Processing
                            stage_next  <= IPv4_FRAGMENT_PRE;
                        else
                            -- Payload Processing
                            stage_next  <= IPv4_PAYLOAD;
                        end if;
                    end if;
                end if;
            -- Skip entire packet
            when SKIP_PACKET =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- End of Packet
                    if(read_cnt = packet_length) then
                        -- Continue parsing next packet
                        stage_next  <= IPv4_INIT;
                    end if;
                end if;
            when others =>
                null;
        end case;
    end process;
    
    -- Process responsible for the buffer timers
    -- The timers increment each second until they reach the maximum value, and stay there until a reset
    buffer_timer_prc : process(sec_clk)
    begin
        if rising_edge(sec_clk) then
            -- Reset timer specified by id
            if(reset_buffer_timer = '0') then
                buffer_timer(reset_buffer_timer_id) <= (others => '0');
            else
                -- Increment Timers
                for i in 0 to MAX_PARALLEL_FRAG-1 loop
                    -- Timers stay at highest value until reset
                    if(buffer_timer(i) /= (buffer_timer(i)'reverse_range => '1')) then
                        buffer_timer(i) <= buffer_timer(i) + 1;
                    end if;
                end loop;
            end if;
        end if;
    end process;
    
    -- Process responsible for counting read words
    -- This process uses the actual RAM and FIFO read signals to determine reads
    word_counter_prc : process(clk, reset)
    begin
        if rising_edge(clk) then
            -- Reset Read counter
            if (reset = '1' or reset_read_cnt = '1') then
                read_cnt    <= to_unsigned(1, read_cnt'length);
            -- Increment read counter each time rd is high
            elsif (rd_sig = '1' or buffer_ren = '1') then
                read_cnt    <= read_cnt + to_unsigned(1, read_cnt'length);
            end if;
        end if;
    end process;
    
    -- 1 Hz Clock Generator
    sec_clk_prc : process(clk, reset)
    begin
        if rising_edge(clk) then
            if (reset = '1') then
                sec_cnt <= 0;
                sec_clk <= '0';
            else
                sec_cnt <= sec_cnt + 1;
                -- Toggle sec_clk every second
                if (sec_cnt = (CLK_FREQ/2)-1) then
                    sec_clk <= not sec_clk;
                end if;
            end if;
        end if;
    end process;
    
    -- State Machine Sync Process (Registers)
    sync : process(clk)
    begin
        if rising_edge(clk) then
            if (reset = '1') then
                stage               <= IPv4_INIT;
                packet_length       <= (others => '0');
                header_length       <= (others => '0');
                is_fragment         <= '0';
                is_last_fragment    <= '0';
                frag_offset         <= (others => '0');
                buffer_id           <= (others => (others => '0'));
                ttl                 <= (others => '0');
                output_id           <= 0;
                frag_size           <= (others => (others => '0'));
                cur_buffer_id       <= 0;
                buffer_bitmap       <= (others => (others => '0'));
                buffer_addr         <= (others => '0');
                buffer_addr_old     <= (others => '0');
                wr_sig_del          <= '0';
                buffer_word_cnt     <= (others => 0);
            else
                stage               <= stage_next;
                packet_length       <= packet_length_next;
                header_length       <= header_length_next;
                is_fragment         <= is_fragment_next;
                is_last_fragment    <= is_last_fragment_next;
                frag_offset         <= frag_offset_next;
                buffer_id           <= buffer_id;
                ttl                 <= ttl_next;
                output_id           <= output_id_next;
                frag_size           <= frag_size_next;
                cur_buffer_id       <= cur_buffer_id_next;
                buffer_bitmap       <= buffer_bitmap_next;
                buffer_addr         <= buffer_addr_next;
                buffer_addr_old     <= buffer_addr_old_next;
                wr_sig_del          <= wr_sig;
                buffer_word_cnt     <= buffer_word_cnt_next;
            end if;
        end if;
    end process;
    
end architecture;

architecture no_frag of ipv4_in_handler is
    
    
    --*****TYPE DECLARATION*****
    -- FSM states. Explained delow in detail
    type PARSER_STAGE_TYPE is (IPv4_INIT, IPv4_HEADER_1, IPv4_HEADER_2, IPv4_HEADER_3, IPv4_HEADER_4, 
        IPv4_HEADER_5, IPv4_PAYLOAD_LENGTH, IPv4_PAYLOAD, SKIP_HEADER, SKIP_PACKET);
    
    --*****SIGNAL DECLARATION*****
    -- FSM state
    signal stage, stage_next : PARSER_STAGE_TYPE := IPv4_INIT;
    -- 32-bit aligned total packet length
    signal packet_length, packet_length_next : unsigned(13 downto 0) := (others => '0');
    -- 32-bit aligned header length
    signal header_length, header_length_next : unsigned(3 downto 0) := (others => '0');
    -- 32-bit word counter (Counts words read from input fifo)
    signal read_cnt : unsigned(13 downto 0) := (others => '0');
    -- Intermediate input read signal. (Read from output port not allowed)
    signal rd_sig : std_logic := '0';
    -- ID of current output fifo (Used to MUX output FIFOs)
    signal output_id, output_id_next : integer range 0 to LAYER3_PROTOCOL_NUM-1 := 0;
    -- Signal used to reset the word counter
    signal reset_read_cnt : std_logic;
    -- Intermediate output signal
    signal data_out_sig : std_logic_vector(31 downto 0);
    -- Intermediate output signal, and its 1 clk cycle delayed variant
    signal wr_sig : std_logic;
    
    --*****ALIAS DEFINATION*****
    --IPv4 HEADER
    alias ip_version    : std_logic_vector(3 downto 0) is data_in(31 downto 28);
    alias ip_ihl        : std_logic_vector(3 downto 0) is data_in(27 downto 24);
    alias ip_dscp       : std_logic_vector(5 downto 0) is data_in(23 downto 18);
    alias ip_ecn        : std_logic_vector(1 downto 0) is data_in(17 downto 16);
    alias ip_length     : std_logic_vector(15 downto 0) is data_in(15 downto 0);
    alias ip_id         : std_logic_vector(15 downto 0) is data_in(31 downto 16);
    alias ip_DF_flag    : std_logic is data_in(14);
    alias ip_MF_flag    : std_logic is data_in(13);
    alias ip_frag_offset: std_logic_vector(12 downto 0) is data_in(12 downto 0);
    alias ip_ttl        : std_logic_vector(7 downto 0) is data_in(31 downto 24);
    alias ip_protocol   : std_logic_vector(7 downto 0) is data_in(23 downto 16);
    alias ip_checksum   : std_logic_vector(15 downto 0) is data_in(15 downto 0);
    
    --*****FUNCTION DECLARATION*****
    
    -- Truncates the lower 2 bits of the input, and if they are not equal zero, adds one to the result.
    -- This is used to round the byte length to 32-bit word length.
    function normalize_length (len : std_logic_vector(15 downto 0)) return std_logic_vector is
        variable tmp : std_logic_vector(13 downto 0) := (others => '0');
    begin
        tmp := len(15 downto 2);
        if(len(1 downto 0) /= "00") then
            tmp := std_logic_vector(unsigned(tmp) + to_unsigned(1, tmp'length));
        end if;
        return tmp;
    end function;
    
begin
    
    rd <= rd_sig;
    
    -- This process is responsible for MUXing the correct signal to the correct output FIFO
    output_prc : process(all)
    begin
        data_out(output_id) <= data_out_sig;
        wr(output_id)       <= wr_sig;        
    end process;
    
    -- Main State Machine
    -- STATE                DESCRIPSION
    -- IPv4_INIT            Initial and idle state. Responsible for checking the buffer timers, and reading the packet length from the input FIFO
    -- IPv4_HEADER_1        Parsing first word of IPv4 HEADER
    -- IPv4_HEADER_2        Parsing second word of IPv4 HEADER
    -- IPv4_HEADER_3        Parsing third word of IPv4 HEADER
    -- IPv4_HEADER_4        Parsing fourth word of IPv4 HEADER and writing SRC Addr to output FIFO (if not a fragment)
    -- IPv4_HEADER_5        Parsing fifth word of IPv4 HEADER and writing DEST Addr to output FIFO (if not a fragment)
    -- IPv4_PAYLOAD_LENGTH  Writing packet(payload) length to output FIFO
    -- IPv4_PAYLOAD         Writing of packet(payload) from input FIFO to output FIFO
    -- SKIP_HEADER          Skip to beginning of Payload in input FIFO
    -- SKIP_PACKET          Skip to end of packet in input FIFO
    parser_prc : process(all)
        variable tmp                : integer range 0 to max(MAX_PARALLEL_FRAG,LAYER3_PROTOCOL_NUM) := 0;
    begin
        --DEFAULT Registered
        stage_next              <= stage;
        packet_length_next      <= packet_length;
        header_length_next      <= header_length;
        output_id_next          <= output_id;
        -- DEFAULT Unregistered
        rd_sig                  <= '0';
        wr_sig                  <= '0';
        data_out_sig            <= (others => '0');
        reset_read_cnt          <= '0';
        
        case(stage) is
            -- Initial/Idle State
            when IPv4_INIT =>
                -- Reset packet Byte Counter
                reset_read_cnt  <= '1';
                -- Read Packet Length from input
                if (empty = '0') then
                    packet_length_next      <= unsigned(data_in(13 downto 0));
                    rd_sig                  <= '1';
                    -- Begin Processing
                    stage_next              <= IPv4_HEADER_1;
                end if;
            -- First IPv4 Header word (Fields: Version, IHL, DSCP, ECN, Total Length)
            when IPv4_HEADER_1 =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- Wrong IP Version or Packet Length missmatch, skip packet
                    if (ip_version /= "0100" or normalize_length(ip_length) /= std_logic_vector(packet_length)) then
                        stage_next  <= SKIP_PACKET;
                    -- Store data and continue Parsing
                    else
                        header_length_next      <= unsigned(ip_ihl);
                        stage_next              <= IPv4_HEADER_2;
                    end if;
                end if;
            -- Second IPv4 Header word (Fields: Identification, Flags, Fragment Offset)
            when IPv4_HEADER_2 =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- No fragmentation, continue parsing normaly
                    if (ip_MF_flag = '0' and ip_frag_offset = (ip_frag_offset'reverse_range => '0')) then
                        stage_next  <= IPv4_HEADER_3;
                    -- IP Fragmentation
                    else
                        -- Fragmentation Reassembly disabled, skip packet
                        stage_next  <= SKIP_PACKET;
                    end if;
                end if;
            -- Third IPv4 Header word (Fields: TTL, Protocol, Checksum)
            when IPv4_HEADER_3 =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- Select Output according to Layer 3 Protocol
                    tmp := LAYER3_PROTOCOL_NUM;
                    for i in 0 to LAYER3_PROTOCOL_NUM-1 loop
                        if(ip_protocol = LAYER3_PROTOCOLS(i)) then
                            tmp := i;
                        end if;
                    end loop;
                    -- No Protocol match, skip packet
                    if (tmp = LAYER3_PROTOCOL_NUM) then
                        stage_next  <= SKIP_PACKET;
                    -- Protocol Match, continue parsing
                    else
                        output_id_next  <= tmp;
                        stage_next      <= IPv4_HEADER_4;
                    end if;
                end if;
            -- Fourth IPv4 Header word (Fields: Src Address)
            when IPv4_HEADER_4 =>
                if (empty = '0') then
                    -- Write Src Address to Output FIFO
                    if (full(output_id) = '0') then
                        rd_sig          <= '1';
                        wr_sig          <= '1';
                        data_out_sig    <= data_in;
                        -- Continue parsing
                        stage_next      <= IPv4_HEADER_5;
                    end if;
                end if;
            -- Fourth IPv4 Header word (Fields: Dest Address)
            when IPv4_HEADER_5 =>
                if (empty = '0') then
                    -- Write Dst Address to Output FIFO
                    if (full(output_id) = '0') then
                        rd_sig          <= '1';
                        wr_sig          <= '1';
                        data_out_sig    <= data_in;
                        -- Check Header Size
                        if (read_cnt /= ("0000000000" & header_length)) then
                            -- If Header has "Options", skip to Payload
                            stage_next  <= SKIP_HEADER;
                        else 
                            -- Payload Processing
                            stage_next  <= IPv4_PAYLOAD_LENGTH;
                        end if;
                    end if;
                end if;
            -- Push Payload Length
            when IPv4_PAYLOAD_LENGTH =>
                -- Write Payload Length to Output FIFO
                if (full(output_id) = '0') then
                    wr_sig                      <= '1';
                    data_out_sig(13 downto 0)   <= std_logic_vector(packet_length - ("0000000000" & header_length));
                    stage_next                  <= IPv4_PAYLOAD;
                end if;
            -- Push Payload
            when IPv4_PAYLOAD =>
                -- Write to Output FIFO
                if (empty = '0' and full(output_id) = '0') then
                    rd_sig          <= '1';
                    wr_sig          <= '1';
                    data_out_sig    <= data_in;
                    -- End Of Packet
                    if (read_cnt = packet_length) then
                        -- Done, process next packet
                        stage_next  <= IPv4_INIT;
                    end if;
                end if;
            -- Skip until beginning of payload
            when SKIP_HEADER =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- End of Header
                    if(read_cnt = header_length) then
                        -- Payload Processing
                        stage_next  <= IPv4_PAYLOAD;
                    end if;
                end if;
            -- Skip entire packet
            when SKIP_PACKET =>
                if (empty = '0') then
                    rd_sig  <= '1';
                    -- End of Packet
                    if(read_cnt = packet_length) then
                        -- Continue parsing next packet
                        stage_next  <= IPv4_INIT;
                    end if;
                end if;
            when others =>
                null;
        end case;
    end process;
    
    -- Process responsible for counting read words
    -- This process uses the actual RAM and FIFO read signals to determine reads
    word_counter_prc : process(clk, reset)
    begin
        if rising_edge(clk) then
            -- Reset Read counter
            if (reset = '1' or reset_read_cnt = '1') then
                read_cnt    <= to_unsigned(1, read_cnt'length);
            -- Increment read counter each time rd is high
            elsif (rd_sig = '1') then
                read_cnt    <= read_cnt + to_unsigned(1, read_cnt'length);
            end if;
        end if;
    end process;
    
    -- State Machine Sync Process (Registers)
    sync : process(clk)
    begin
        if rising_edge(clk) then
            if (reset = '1') then
                stage               <= IPv4_INIT;
                packet_length       <= (others => '0');
                header_length       <= (others => '0');
                output_id           <= 0;
            else
                stage               <= stage_next;
                packet_length       <= packet_length_next;
                header_length       <= header_length_next;
                output_id           <= output_id_next;
            end if;
        end if;
    end process;
    
end architecture;