-- 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.physical.all;
use			PoC.cache.all;
use			PoC.net.all;


entity [docs]arp_Cache is
	generic (
		CLOCK_FREQ								: FREQ																	:= 125 MHz;
		REPLACEMENT_POLICY				: string																:= "LRU";
		TAG_BYTE_ORDER						: T_BYTE_ORDER													:= BIG_ENDIAN;
		DATA_BYTE_ORDER						: T_BYTE_ORDER													:= BIG_ENDIAN;
		INITIAL_CACHE_CONTENT			: T_NET_ARP_ARPCACHE_VECTOR
	);
	port (
		Clock											: in	std_logic;																	--
		Reset											: in	std_logic;																	--

		Command										: in	T_NET_ARP_ARPCACHE_COMMAND;
		Status										: out	T_NET_ARP_ARPCACHE_STATUS;
		NewIPv4Address_rst				: out	std_logic;
		NewIPv4Address_nxt				: out	std_logic;
		NewIPv4Address_Data				: in	T_SLV_8;
		NewMACAddress_rst					: out	std_logic;
		NewMACAddress_nxt					: out	std_logic;
		NewMACAddress_Data				: in	T_SLV_8;

		Lookup										: in	std_logic;
		IPv4Address_rst						: out	std_logic;
		IPv4Address_nxt						: out	std_logic;
		IPv4Address_Data					: in	T_SLV_8;

		CacheResult								: out	T_CACHE_RESULT;
		MACAddress_rst						: in	std_logic;
		MACAddress_nxt						: in	std_logic;
		MACAddress_Data						: out	T_SLV_8
	);
end entity;


architecture [docs]rtl of arp_Cache is
	constant CACHE_LINES							: positive	:= 8;
	constant TAG_BITS									: positive	:= 32;		-- IPv4 address
	constant DATA_BITS								:	positive	:= 48;		-- MAC address
	constant TAGCHUNK_BITS						: positive	:= 8;
	constant DATACHUNK_BITS						: positive	:= 8;

	constant DATACHUNKS								: positive	:= div_ceil(DATA_BITS, DATACHUNK_BITS);
	constant DATACHUNK_INDEX_BITS			: positive	:= log2ceilnz(DATACHUNKS);
	constant CACHEMEMORY_INDEX_BITS		: positive	:= log2ceilnz(CACHE_LINES);

	function [docs]to_TagData(CacheContent : T_NET_ARP_ARPCACHE_VECTOR) return T_SLM is
--		variable slvv		: T_SLVV_32(CACHE_LINES - 1 downto 0)	:= (others => (others => '0'));
		variable slvv		: T_SLVV_32(CacheContent'high downto CacheContent'low)	:= (others => (others => '0'));
	begin
		for i in CacheContent'range loop
			slvv(i)	:= to_slv(CacheContent(i).Tag);
		end loop;
		return to_slm(slvv);
	end function;

	function [docs]to_CacheData_slvv_48(CacheContent : T_NET_ARP_ARPCACHE_VECTOR) return T_SLVV_48 is
		variable slvv		: T_SLVV_48(CACHE_LINES - 1 downto 0)	:= (others => (others => '0'));
	begin
		for i in CacheContent'range loop
			slvv(i)	:= to_slv(CacheContent(i).MAC);
		end loop;
		return slvv;
	end function;

	function [docs]to_CacheMemory(CacheContent : T_NET_ARP_ARPCACHE_VECTOR) return T_SLVV_8 is
		constant BYTES_PER_LINE	: positive																				:= 6;
		constant slvv						: T_SLVV_48(CACHE_LINES - 1 downto 0)							:= to_CacheData_slvv_48(CacheContent);
		variable result					: T_SLVV_8((CACHE_LINES * BYTES_PER_LINE) - 1 downto 0);
	begin
		for i in slvv'range loop
			for j in 0 to BYTES_PER_LINE - 1 loop
				result((i * BYTES_PER_LINE) + j)	:= slvv(i)((j * 8) + 7 downto j * 8);
			end loop;
		end loop;
		return result;
	end function;

	constant INITIAL_TAGS					: T_SLM			:= to_TagData(INITIAL_CACHE_CONTENT);
	constant INITIAL_DATALINES		: T_SLVV_8	:= to_CacheMemory(INITIAL_CACHE_CONTENT);


	signal ReadWrite							: std_logic;

	type T_FSMREPLACE_STATE is (ST_IDLE, ST_REPLACE);

	signal FSMReplace_State				: T_FSMREPLACE_STATE						:= ST_IDLE;
	signal FSMReplace_NextState		: T_FSMREPLACE_STATE;

	signal Insert									: std_logic;

	signal TU_NewTag_rst					: std_logic;
	signal TU_NewTag_nxt					: std_logic;
	signal NewTag_Data						: T_SLV_8;

	signal NewCacheLine_Data			: T_SLV_8;

	signal TU_Tag_rst							: std_logic;
	signal TU_Tag_nxt							: std_logic;
	signal TU_Tag_Data						: T_SLV_8;
	signal CacheHit								: std_logic;
	signal CacheMiss							: std_logic;

	signal TU_Index								: std_logic_vector(CACHEMEMORY_INDEX_BITS - 1 downto 0);
	signal TU_Index_d							: std_logic_vector(CACHEMEMORY_INDEX_BITS - 1 downto 0);
--	signal TU_Index_us						: UNSIGNED(CACHEMEMORY_INDEX_BITS - 1 downto 0);

	signal TU_NewIndex						: std_logic_vector(CACHEMEMORY_INDEX_BITS - 1 downto 0);
	signal TU_Replaced						: std_logic;

	signal TU_TagHit							: std_logic;
	signal TU_TagMiss							: std_logic;

	constant TICKCOUNTER_RES			: time																																			:= 10 ms;
	constant TICKCOUNTER_MAX			: positive																																	:= TimingToCycles(TICKCOUNTER_RES, CLOCK_FREQ);
	constant TICKCOUNTER_BITS			: positive																																	:= log2ceilnz(TICKCOUNTER_MAX);

	signal TickCounter_s					: signed(TICKCOUNTER_BITS downto 0)																					:= to_signed(TICKCOUNTER_MAX, TICKCOUNTER_BITS + 1);
	signal Tick										: std_logic;

	signal Exp_Expired						: std_logic;
	signal Exp_KeyOut							: std_logic_vector(CACHEMEMORY_INDEX_BITS - 1 downto 0);

	signal DataChunkIndex_us					: unsigned((CACHEMEMORY_INDEX_BITS + DATACHUNK_INDEX_BITS) - 1 downto 0)		:= (others => '0');
	signal DataChunkIndex_l_us				: unsigned((CACHEMEMORY_INDEX_BITS + DATACHUNK_INDEX_BITS) - 1 downto 0)		:= (others => '0');
	signal NewDataChunkIndex_en				: std_logic;
	signal NewDataChunkIndex_us				: unsigned((CACHEMEMORY_INDEX_BITS + DATACHUNK_INDEX_BITS) - 1 downto 0)		:= (others => '0');
	signal NewDataChunkIndex_max_us		: unsigned((CACHEMEMORY_INDEX_BITS + DATACHUNK_INDEX_BITS) - 1 downto 0)		:= (others => '0');
	signal CacheMemory_we							: std_logic;
	signal CacheMemory								: T_SLVV_8((CACHE_LINES * T_NET_MAC_ADDRESS'length) - 1 downto 0)						:= INITIAL_DATALINES;
	signal Memory_ReadWrite						: std_logic;

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

	[docs]process(FSMReplace_State, Command, TU_Replaced, TU_NewTag_rst, TU_NewTag_nxt, NewDataChunkIndex_us, NewDataChunkIndex_max_us)
	begin
		FSMReplace_NextState							<= FSMReplace_State;

		Status														<= NET_ARP_ARPCACHE_STATUS_IDLE;

		NewMACAddress_rst									<= '0';
		NewMACAddress_nxt									<= '0';
		NewIPv4Address_rst								<= TU_NewTag_rst;
		NewIPv4Address_nxt								<= TU_NewTag_nxt;

		CacheMemory_we										<= '0';
		NewDataChunkIndex_en							<= '0';

		Insert														<= '0';

		case FSMReplace_State is
			when ST_IDLE =>
				NewMACAddress_rst							<= '1';

				case Command is
					when NET_ARP_ARPCACHE_CMD_NONE =>
						null;

					when NET_ARP_ARPCACHE_CMD_ADD =>
						Status										<= NET_ARP_ARPCACHE_STATUS_UPDATING;

						Insert										<= '1';
						CacheMemory_we						<= '1';
						NewMACAddress_rst					<= '0';
						NewMACAddress_nxt					<= '1';
						NewDataChunkIndex_en			<= '1';

						FSMReplace_NextState			<= ST_REPLACE;

					when others =>
						null;
				end case;

			when ST_REPLACE =>
				Status												<= NET_ARP_ARPCACHE_STATUS_UPDATING;

				CacheMemory_we								<= '1';
				NewMACAddress_nxt							<= '1';
				NewDataChunkIndex_en					<= '1';

				if (NewDataChunkIndex_us = NewDataChunkIndex_max_us) then
					Status											<= NET_ARP_ARPCACHE_STATUS_UPDATE_COMPLETE;
					FSMReplace_NextState				<= ST_IDLE;
				end if;

		end case;
	end process;

	ReadWrite						<= '0';
	NewTag_Data					<= NewIPv4Address_Data;
	NewCacheLine_Data		<= NewMACAddress_Data;

	IPv4Address_rst			<= TU_Tag_rst;
	IPv4Address_nxt			<= TU_Tag_nxt;
	TU_Tag_Data					<= IPv4Address_Data;

	CacheResult					<= to_Cache_Result(CacheHit, CacheMiss);

	-- Cache TagUnit
--	TU : entity PoC.Cache_TagUnit_seq
	TU : entity PoC.cache_TagUnit_seq
		generic map (
			REPLACEMENT_POLICY				=> REPLACEMENT_POLICY,
			CACHE_LINES								=> CACHE_LINES,
			ASSOCIATIVITY							=> CACHE_LINES,
			TAG_BITS									=> TAG_BITS,
			CHUNK_BITS								=> TAGCHUNK_BITS,
			TAG_BYTE_ORDER						=> TAG_BYTE_ORDER,
			USE_INITIAL_TAGS 					=> TRUE,
			INITIAL_TAGS							=> INITIAL_TAGS
		)
		port map (
			Clock											=> Clock,
			Reset											=> Reset,

			Replace										=> Insert,
			Replaced									=> TU_Replaced,
			Replace_NewTag_rst				=> TU_NewTag_rst,
			Replace_NewTag_rev				=> open,
			Replace_NewTag_nxt				=> TU_NewTag_nxt,
			Replace_NewTag_Data				=> NewTag_Data,
			Replace_NewIndex					=> TU_NewIndex,

			Request										=> Lookup,
			Request_ReadWrite					=> '0',
			Request_Invalidate				=> '0',--Invalidate,
			Request_Tag_rst						=> TU_Tag_rst,
			Request_Tag_rev						=> open,
			Request_Tag_nxt						=> TU_Tag_nxt,
			Request_Tag_Data					=> TU_Tag_Data,
			Request_Index							=> TU_Index,
			Request_TagHit						=> TU_TagHit,
			Request_TagMiss						=> TU_TagMiss
		);

	-- expiration time tick generator
	process(Clock)
	begin
		if rising_edge(Clock) then
			if (Tick = '1') then
				TickCounter_s		<= to_signed(TICKCOUNTER_MAX, TickCounter_s'length);
			else
				TickCounter_s	<= TickCounter_s - 1;
			end if;
		end if;
	end process;

	Tick			<= TickCounter_s(TickCounter_s'high);

--	Exp : entity PoC.list_expire
	Exp : entity PoC.list_expire
		generic map (
			CLOCK_CYCLE_TICKS				=> 65536,
			EXPIRATION_TIME_TICKS		=> 8192,
			ELEMENTS								=> CACHE_LINES,
			KEY_BITS								=> CACHEMEMORY_INDEX_BITS
		)
		port map (
			Clock										=> Clock,
			Reset										=> Reset,

			Tick										=> Tick,

			Insert									=> Insert,
			KeyIn										=> TU_NewIndex,

			Expired									=> Exp_Expired,
			KeyOut									=> Exp_KeyOut
		);



	-- latch TU_Index on TagHit
--	TU_Index_us		<= unsigned(TU_Index) when rising_edge(Clock) AND (TU_TagHit = '1');

	-- NewDataChunkIndex counter
	process(Clock)
	begin
		if rising_edge(Clock) then
			if (NewDataChunkIndex_en = '0') then
				if (DATA_BYTE_ORDER = LITTLE_ENDIAN) then
					NewDataChunkIndex_us			<= resize(unsigned(TU_NewIndex) * 6, NewDataChunkIndex_us'length);
					NewDataChunkIndex_max_us	<= resize(unsigned(TU_NewIndex) * 6, NewDataChunkIndex_us'length) + to_unsigned((DATACHUNKS - 1), NewDataChunkIndex_us'length);
				else
					NewDataChunkIndex_us			<= resize(unsigned(TU_NewIndex) * 6, NewDataChunkIndex_us'length) + to_unsigned((DATACHUNKS - 1), NewDataChunkIndex_us'length);
					NewDataChunkIndex_max_us	<= resize(unsigned(TU_NewIndex) * 6, NewDataChunkIndex_us'length);
				end if;
			else
				if (DATA_BYTE_ORDER = LITTLE_ENDIAN) then
					NewDataChunkIndex_us	<= NewDataChunkIndex_us + 1;
				else
					NewDataChunkIndex_us	<= NewDataChunkIndex_us - 1;
				end if;
			end if;
		end if;
	end process;

	-- DataChunkIndex counter
	process(Clock, TU_Index)
		variable temp		: unsigned(DataChunkIndex_us'range);
	begin
		if (DATA_BYTE_ORDER = LITTLE_ENDIAN) then
			temp	:= resize(unsigned(TU_Index) * 6, DataChunkIndex_us'length);
		else
			temp	:= resize(unsigned(TU_Index) * 6, DataChunkIndex_us'length) + to_unsigned((DATACHUNKS - 1), DataChunkIndex_us'length);
		end if;

		if rising_edge(Clock) then
			if (TU_TagHit = '1') then
				DataChunkIndex_us				<= temp;
				DataChunkIndex_l_us			<= temp;
			elsif (MACAddress_rst = '1') then
				DataChunkIndex_us				<= DataChunkIndex_l_us;
			elsif (MACAddress_nxt = '1') then
				if (DATA_BYTE_ORDER = LITTLE_ENDIAN) then
					DataChunkIndex_us			<= DataChunkIndex_us + 1;
				else
					DataChunkIndex_us			<= DataChunkIndex_us - 1;
				end if;
			end if;
		end if;
	end process;

	-- Cache Memory - port 1
	Memory_ReadWrite	<= ReadWrite;

	process(Clock)
	begin
		if rising_edge(Clock) then
			if (CacheMemory_we = '1') then
				CacheMemory(to_integer(NewDataChunkIndex_us))	<= NewCacheLine_Data;
			end if;
		end if;
	end process;

	CacheHit					<= TU_TagHit;
	CacheMiss					<= TU_TagMiss;
	MACAddress_Data		<= CacheMemory(to_index(DataChunkIndex_us, CacheMemory'high));
end architecture;