-- EMACS settings: -*-  tab-width: 2; indent-tabs-mode: t -*-
-- vim: tabstop=2:shiftwidth=2:noexpandtab
-- kate: tab-width 2; replace-tabs off; indent-width 2;
-- =============================================================================
-- Authors:				 	Patrick Lehmann
--
-- Entity:				 	TODO
--
-- Description:
-- 
-- .. TODO:: No documentation available.
--
-- License:
-- =============================================================================
-- Copyright 2007-2015 Technische Universitaet Dresden - Germany
--										 Chair of VLSI-Design, Diagnostics and Architecture
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--		http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- =============================================================================

library IEEE;
use			IEEE.STD_LOGIC_1164.all;
use			IEEE.NUMERIC_STD.all;

library PoC;
use			PoC.config.all;
use			PoC.utils.all;
use			PoC.vectors.all;
use			PoC.net.all;


entity [docs]ipv4_TX is
	generic (
		DEBUG														: boolean							:= FALSE
	);
	port (
		Clock														: in	std_logic;									--
		Reset														: in	std_logic;									--
		-- IN port
		In_Valid												: in	std_logic;
		In_Data													: in	T_SLV_8;
		In_SOF													: in	std_logic;
		In_EOF													: in	std_logic;
		In_Ack													: out	std_logic;
		In_Meta_rst											: out	std_logic;
		In_Meta_SrcIPv4Address_nxt			: out	std_logic;
		In_Meta_SrcIPv4Address_Data			: in	T_SLV_8;
		In_Meta_DestIPv4Address_nxt			: out	std_logic;
		In_Meta_DestIPv4Address_Data		: in	T_SLV_8;
		In_Meta_Length									: in	T_SLV_16;
		In_Meta_Protocol								: in	T_SLV_8;
		-- ARP port
		ARP_IPCache_Query								: out	std_logic;
		ARP_IPCache_IPv4Address_rst			: in	std_logic;
		ARP_IPCache_IPv4Address_nxt			: in	std_logic;
		ARP_IPCache_IPv4Address_Data		: out	T_SLV_8;
		ARP_IPCache_Valid								: in	std_logic;
		ARP_IPCache_MACAddress_rst			: out	std_logic;
		ARP_IPCache_MACAddress_nxt			: out	std_logic;
		ARP_IPCache_MACAddress_Data			: in	T_SLV_8;
		-- OUT port
		Out_Valid												: out	std_logic;
		Out_Data												: out	T_SLV_8;
		Out_SOF													: out	std_logic;
		Out_EOF													: out	std_logic;
		Out_Ack													: in	std_logic;
		Out_Meta_rst										: in	std_logic;
		Out_Meta_DestMACAddress_nxt			: in	std_logic;
		Out_Meta_DestMACAddress_Data		: out	T_SLV_8
	);
end entity;


architecture [docs]rtl of ipv4_TX is
	attribute FSM_ENCODING						: string;

	type T_STATE is (
		ST_IDLE,
			ST_ARP_QUERY,									ST_ARP_QUERY_WAIT,
			ST_CHECKSUM_IPV4_ADDRESSES,
				ST_CHECKSUM_IPVERSION_LENGTH_0,							ST_CHECKSUM_TYPE_OF_SERVICE_LENGTH_1,
				ST_CHECKSUM_IDENTIFICAION_FRAGMENTOFFSET_0,	ST_CHECKSUM_IDENTIFICAION_FRAGMENTOFFSET_1,
				ST_CHECKSUM_TIME_TO_LIVE,				ST_CHECKSUM_PROTOCOL,
			ST_CARRY_0, ST_CARRY_1,
			ST_SEND_VERSION,							ST_SEND_TYPE_OF_SERVICE,	ST_SEND_TOTAL_LENGTH_0,			ST_SEND_TOTAL_LENGTH_1,
			ST_SEND_IDENTIFICATION_0,			ST_SEND_IDENTIFICATION_1,	ST_SEND_FLAGS,							ST_SEND_FRAGMENT_OFFSET,
			ST_SEND_TIME_TO_LIVE,					ST_SEND_PROTOCOL,					ST_SEND_HEADER_CHECKSUM_0,	ST_SEND_HEADER_CHECKSUM_1,
			ST_SEND_SOURCE_ADDRESS,
			ST_SEND_DESTINATION_ADDRESS,
--			ST_SEND_OPTIONS_0,						ST_SEND_OPTIONS_1,				ST_SEND_OPTIONS_2,					ST_SEND_PADDING,
			ST_SEND_DATA,
		ST_DISCARD_FRAME,
		ST_ERROR
	);

	signal State											: T_STATE											:= ST_IDLE;
	signal NextState									: T_STATE;
	attribute FSM_ENCODING of State		: signal is ite(DEBUG, "gray", ite((VENDOR = VENDOR_XILINX), "auto", "default"));

	signal In_Ack_i										: std_logic;

	signal UpperLayerPacketLength			: std_logic_vector(15 downto 0);

	signal InternetHeaderLength				: T_SLV_4;
	signal TypeOfService							: T_NET_IPV4_TYPE_OF_SERVICE;
	signal TotalLength								: T_SLV_16;
	signal Identification							: T_SLV_16;
	signal Flag_DontFragment					: std_logic;
	signal Flag_MoreFragments					: std_logic;
	signal FragmentOffset							: std_logic_vector(12 downto 0);
	signal TimeToLive									: T_SLV_8;
	signal Protocol										: T_SLV_8;
	signal HeaderChecksum							: T_SLV_16;

	signal IPv4SeqCounter_rst					: std_logic;
	signal IPv4SeqCounter_en					: std_logic;
	signal IPv4SeqCounter_us					: unsigned(1 downto 0)				:= (others => '0');

	signal Checksum_rst								: std_logic;
	signal Checksum_en								: std_logic;
	signal Checksum_Addend0_us				: unsigned(T_SLV_8'range);
	signal Checksum_Addend1_us				: unsigned(T_SLV_8'range);
	signal Checksum0_nxt0_us					: unsigned(T_SLV_8'high + 1 downto 0);
	signal Checksum0_nxt1_us					: unsigned(T_SLV_8'high + 1 downto 0);
	signal Checksum0_d_us							: unsigned(T_SLV_8'high downto 0)												:= (others => '0');
	signal Checksum0_cy								: unsigned(T_SLV_2'range);
	signal Checksum1_nxt_us						: unsigned(T_SLV_8'range);
	signal Checksum1_d_us							: unsigned(T_SLV_8'range)																:= (others => '0');
	signal Checksum0_cy0							: std_logic;
	signal Checksum0_cy0_d						: std_logic																							:= '0';
	signal Checksum0_cy1							: std_logic;
	signal Checksum0_cy1_d						: std_logic																							:= '0';

	signal Checksum_i									: T_SLV_16;
	signal Checksum										: T_SLV_16;
	signal Checksum_mux_rst						: std_logic;
	signal Checksum_mux_set						: std_logic;
	signal Checksum_mux_r							: std_logic																							:= '0';

begin

	UpperLayerPacketLength	<= std_logic_vector(unsigned(In_Meta_Length) + 20);

	InternetHeaderLength		<= x"5";											-- standard IPv4 header length withou option fields (20 bytes -> 5 * 32-words)
	TypeOfService						<= C_NET_IPV4_TOS_DEFAULT;		-- Type of Service: routine, normal delay, normal throughput, normal relibility
	TotalLength							<= UpperLayerPacketLength;
	Identification					<= x"0000";
	Flag_DontFragment				<= '0';
	Flag_MoreFragments			<= '0';
	FragmentOffset					<= (others => '0');
	TimeToLive							<= x"40";											-- TTL = 64; see RFC 791
	Protocol								<= In_Meta_Protocol;

	process(Clock)
	begin
		if rising_edge(Clock) then
			if (Reset = '1') then
				State			<= ST_IDLE;
			else
				State			<= NextState;
			end if;
		end if;
	end process;

	[docs]process(State, In_Valid, In_SOF, In_EOF, In_Data,
					In_Meta_Length,
					Out_Ack, Out_Meta_rst, Out_Meta_DestMACAddress_nxt,
					ARP_IPCache_Valid, ARP_IPCache_IPv4Address_rst, ARP_IPCache_IPv4Address_nxt, ARP_IPCache_MACAddress_Data,
					In_Meta_DestIPv4Address_Data, In_Meta_SrcIPv4Address_Data, In_Meta_Protocol,
					InternetHeaderLength, UpperLayerPacketLength, TypeOfService, Flag_DontFragment, Flag_MoreFragments,
					Identification, FragmentOffset, TimeToLive, Protocol,
					IPv4SeqCounter_us, Checksum0_cy, Checksum)
	begin
		NextState													<= State;

		In_Ack_i													<= '0';

		Out_Valid													<= '0';
		Out_Data													<= (others => '0');
		Out_SOF														<= '0';
		Out_EOF														<= '0';

		In_Meta_rst												<= '0';

		ARP_IPCache_Query									<= '0';
		In_Meta_SrcIPv4Address_nxt				<= '0';
		In_Meta_DestIPv4Address_nxt				<= '0';
		ARP_IPCache_IPv4Address_Data			<= In_Meta_DestIPv4Address_Data;

		ARP_IPCache_MACAddress_rst				<= Out_Meta_rst;
		ARP_IPCache_MACAddress_nxt				<= Out_Meta_DestMACAddress_nxt;
		Out_Meta_DestMACAddress_Data			<= ARP_IPCache_MACAddress_Data;

		IPv4SeqCounter_rst								<= '0';
		IPv4SeqCounter_en									<= '0';

		Checksum_rst											<= '0';
		Checksum_en												<= '0';
		Checksum_Addend0_us								<= (others => '0');
		Checksum_Addend1_us								<= (others => '0');
		Checksum_mux_rst									<= '0';
		Checksum_mux_set									<= '0';

		case State is
			when ST_IDLE =>
				In_Meta_rst										<= ARP_IPCache_IPv4Address_rst;
				In_Meta_DestIPv4Address_nxt		<= ARP_IPCache_IPv4Address_nxt;

				IPv4SeqCounter_rst						<= '1';
				Checksum_rst									<= '1';

				if ((In_Valid and In_SOF) = '1') then
					NextState										<= ST_ARP_QUERY;
				end if;

			when ST_ARP_QUERY =>
				Out_Data											<= x"4" & InternetHeaderLength;
				Out_SOF												<= '1';

				In_Meta_rst										<= ARP_IPCache_IPv4Address_rst;
				In_Meta_DestIPv4Address_nxt		<= ARP_IPCache_IPv4Address_nxt;

				ARP_IPCache_Query							<= '1';

				if (ARP_IPCache_Valid = '1') then
					Out_Valid										<= '1';
					In_Meta_rst									<= '1';		-- reset metadata

--					if (Out_Ack	 = '1') then
--						NextState									<= ST_SEND_TYPE_OF_SERVICE;
--					else
--						NextState									<= ST_SEND_VERSION;
--					end if;
					NextState										<= ST_CHECKSUM_IPV4_ADDRESSES;
				else
					NextState										<= ST_ARP_QUERY_WAIT;
				end if;

			when ST_ARP_QUERY_WAIT =>
				Out_Valid											<= '0';
				Out_Data											<= x"4" & InternetHeaderLength;
				Out_SOF												<= '1';

				In_Meta_rst										<= ARP_IPCache_IPv4Address_rst;
				In_Meta_DestIPv4Address_nxt		<= ARP_IPCache_IPv4Address_nxt;

				if (ARP_IPCache_Valid = '1') then
					Out_Valid										<= '1';
					In_Meta_rst									<= '1';		-- reset metadata

--					if (Out_Ack	 = '1') then
--						NextState									<= ST_SEND_TYPE_OF_SERVICE;
--					else
--						NextState									<= ST_SEND_VERSION;
--					end if;
					NextState										<= ST_CHECKSUM_IPV4_ADDRESSES;
				end if;

			-- calculate checksum for IPv4 header
			-- ----------------------------------------------------------------------
			when ST_CHECKSUM_IPV4_ADDRESSES =>
				In_Meta_SrcIPv4Address_nxt		<= '1';
				In_Meta_DestIPv4Address_nxt		<= '1';

				IPv4SeqCounter_en							<= '1';
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(In_Meta_SrcIPv4Address_Data);
				Checksum_Addend1_us						<= unsigned(In_Meta_DestIPv4Address_Data);

				if (IPv4SeqCounter_us = 3) then
					NextState										<= ST_CHECKSUM_IPVERSION_LENGTH_0;
				end if;

			when ST_CHECKSUM_IPVERSION_LENGTH_0 =>
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(UpperLayerPacketLength(15 downto 8));
				Checksum_Addend1_us						<= unsigned(std_logic_vector'(x"4" & InternetHeaderLength));

				NextState											<= ST_CHECKSUM_TYPE_OF_SERVICE_LENGTH_1;

			when ST_CHECKSUM_TYPE_OF_SERVICE_LENGTH_1 =>
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(UpperLayerPacketLength(7 downto 0));
				Checksum_Addend1_us						<= unsigned(to_slv(TypeOfService));

				NextState											<= ST_CHECKSUM_IDENTIFICAION_FRAGMENTOFFSET_0;

			when ST_CHECKSUM_IDENTIFICAION_FRAGMENTOFFSET_0 =>
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(Identification(15 downto 8));
				Checksum_Addend1_us						<= unsigned(std_logic_vector'('0' & Flag_DontFragment & Flag_MoreFragments & FragmentOffset(12 downto 8)));

				NextState											<= ST_CHECKSUM_IDENTIFICAION_FRAGMENTOFFSET_1;

			when ST_CHECKSUM_IDENTIFICAION_FRAGMENTOFFSET_1 =>
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(Identification(7 downto 0));
				Checksum_Addend1_us						<= unsigned(FragmentOffset(7 downto 0));

				NextState											<= ST_CHECKSUM_TIME_TO_LIVE;

			when ST_CHECKSUM_TIME_TO_LIVE =>
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(TimeToLive);
				Checksum_Addend1_us						<= (others => '0');	--unsigned(In_Meta_Checksum(15 downto 8));

				NextState											<= ST_CHECKSUM_PROTOCOL;

			when ST_CHECKSUM_PROTOCOL =>
				Checksum_en										<= '1';
				Checksum_Addend0_us						<= unsigned(Protocol);
				Checksum_Addend1_us						<= (others => '0');	--unsigned(In_Meta_Checksum(7 downto 0));

				if (Checksum0_cy = "00") then
					NextState										<= ST_SEND_VERSION;
				else
					NextState										<= ST_CARRY_0;
				end if;

			-- circulate carry bit
			-- ----------------------------------------------------------------------
			when ST_CARRY_0 =>
				In_Meta_rst										<= Out_Meta_rst;

				Checksum_en										<= '1';
				Checksum_mux_set							<= '1';

				if (Checksum0_cy = "00") then
					NextState										<= ST_SEND_VERSION;
				else
					NextState										<= ST_CARRY_1;
				end if;

			when ST_CARRY_1 =>
				In_Meta_rst										<= Out_Meta_rst;

				Checksum_en										<= '1';
				Checksum_mux_rst							<= '1';

				NextState											<= ST_SEND_VERSION;

			-- assamble header
			-- ----------------------------------------------------------------------
			when ST_SEND_VERSION =>
				Out_Valid											<= '1';
				Out_Data											<= x"4" & InternetHeaderLength;
				Out_SOF												<= '1';

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_TYPE_OF_SERVICE;
				end if;

			when ST_SEND_TYPE_OF_SERVICE =>
				Out_Valid											<= '1';
				Out_Data											<= to_slv(TypeOfService);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_TOTAL_LENGTH_0;
				end if;

			when ST_SEND_TOTAL_LENGTH_0 =>
				Out_Valid											<= '1';
				Out_Data											<= std_logic_vector(TotalLength(15 downto 8));

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_TOTAL_LENGTH_1;
				end if;

			when ST_SEND_TOTAL_LENGTH_1 =>
				Out_Valid											<= '1';
				Out_Data											<= std_logic_vector(TotalLength(7 downto 0));

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_IDENTIFICATION_0;
				end if;

			when ST_SEND_IDENTIFICATION_0 =>
				Out_Valid											<= '1';
				Out_Data											<= Identification(15 downto 8);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_IDENTIFICATION_1;
				end if;

			when ST_SEND_IDENTIFICATION_1 =>
				Out_Valid											<= '1';
				Out_Data											<= Identification(7 downto 0);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_FLAGS;
				end if;

			when ST_SEND_FLAGS =>
				Out_Valid											<= '1';
				Out_Data(7)										<= '0';
				Out_Data(6)										<= Flag_DontFragment;
				Out_Data(5)										<= Flag_MoreFragments;
				Out_Data(4 downto 0)					<= FragmentOffset(12 downto 8);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_FRAGMENT_OFFSET;
				end if;

			when ST_SEND_FRAGMENT_OFFSET =>
				Out_Valid											<= '1';
				Out_Data											<= FragmentOffset(7 downto 0);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_TIME_TO_LIVE;
				end if;

			when ST_SEND_TIME_TO_LIVE =>
				Out_Valid											<= '1';
				Out_Data											<= TimeToLive;

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_PROTOCOL;
				end if;

			when ST_SEND_PROTOCOL =>
				Out_Valid											<= '1';
				Out_Data											<= Protocol;

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_HEADER_CHECKSUM_0;
				end if;

			when ST_SEND_HEADER_CHECKSUM_0 =>
				Out_Valid											<= '1';
				Out_Data											<= Checksum(15 downto 8);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_HEADER_CHECKSUM_1;
				end if;

			when ST_SEND_HEADER_CHECKSUM_1 =>
				Out_Valid											<= '1';
				Out_Data											<= Checksum(7 downto 0);

				if (Out_Ack	 = '1') then
					NextState										<= ST_SEND_SOURCE_ADDRESS;
				end if;

			when ST_SEND_SOURCE_ADDRESS =>
				Out_Valid											<= '1';
				Out_Data											<= In_Meta_SrcIPv4Address_Data;

				if (Out_Ack	 = '1') then
					In_Meta_SrcIPv4Address_nxt	<= '1';
					IPv4SeqCounter_en						<= '1';

					if (IPv4SeqCounter_us = 3) then
						NextState									<= ST_SEND_DESTINATION_ADDRESS;
					end if;
				end if;

			when ST_SEND_DESTINATION_ADDRESS =>
				Out_Valid											<= '1';
				Out_Data											<= In_Meta_DestIPv4Address_Data;

				if (Out_Ack	 = '1') then
					In_Meta_DestIPv4Address_nxt	<= '1';
					IPv4SeqCounter_en						<= '1';

					if (IPv4SeqCounter_us = 3) then
						NextState									<= ST_SEND_DATA;
					end if;
				end if;

			when ST_SEND_DATA =>
				Out_Valid											<= In_Valid;
				Out_Data											<= In_Data;
				Out_EOF												<= In_EOF;
				In_Ack_i											<= Out_Ack;

				if ((In_EOF and Out_Ack) = '1') then
					In_Meta_rst									<= '1';
					NextState										<= ST_IDLE;
				end if;

			when ST_DISCARD_FRAME =>
				null;

			when ST_ERROR =>
				null;

		end case;
	end process;

	process(Clock)
	begin
		if rising_edge(Clock) then
			if ((Reset or IPv4SeqCounter_rst) = '1') then
				IPv4SeqCounter_us		<= (others => '0');
			elsif (IPv4SeqCounter_en = '1') then
				IPv4SeqCounter_us		<= IPv4SeqCounter_us + 1;
			end if;
		end if;
	end process;

Checksum0_nxt0_us		<= ("0" & Checksum1_d_us)
													+ ("0" & Checksum_Addend0_us)
													+ ((Checksum_Addend0_us'range => '0') & Checksum0_cy0_d);
	Checksum0_nxt1_us		<= ("0" & Checksum0_nxt0_us(Checksum0_nxt0_us'high - 1 downto 0))
													+ ("0" & Checksum_Addend1_us)
													+ ((Checksum_Addend1_us'range => '0') & Checksum0_cy1_d);
	Checksum1_nxt_us		<= Checksum0_d_us(Checksum1_d_us'range);

	Checksum0_cy0				<= Checksum0_nxt0_us(Checksum0_nxt0_us'high);
	Checksum0_cy1				<= Checksum0_nxt1_us(Checksum0_nxt1_us'high);
	Checksum0_cy				<= Checksum0_cy1 & Checksum0_cy0;


	process(Clock)
	begin
		if rising_edge(Clock) then
			if (Checksum_rst = '1') then
				Checksum0_d_us		<= (others => '0');
				Checksum1_d_us		<= (others => '0');
			elsif (Checksum_en = '1') then
				Checksum0_d_us		<= Checksum0_nxt1_us(Checksum0_nxt1_us'high - 1 downto 0);
				Checksum1_d_us		<= Checksum1_nxt_us;

				Checksum0_cy0_d		<= Checksum0_cy0;
				Checksum0_cy1_d		<= Checksum0_cy1;
			end if;
		end if;
	end process;

	Checksum_i		<= not (std_logic_vector(Checksum0_nxt1_us(Checksum0_nxt1_us'high - 1 downto 0)) & std_logic_vector(Checksum1_nxt_us));
	Checksum			<= ite((Checksum_mux_r = '0'), Checksum_i, swap(Checksum_i, 8));

	process(Clock)
	begin
		if rising_edge(Clock) then
			if ((Reset or Checksum_mux_rst) = '1') then
				Checksum_mux_r		<= '0';
			elsif (Checksum_mux_set = '1') then
				Checksum_mux_r		<= '1';
			end if;
		end if;
	end process;

	In_Ack													<= In_Ack_i;

end architecture;