LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

library work;
use work.mips_types.all;
use work.mips_util_pkg.all;

ENTITY tlb IS
	Generic
	(
		NUM_ENTRIES			: natural := 8;
		CACHE_KSEG1			: boolean := false;
		USE_TLB				: boolean := false
	);
	Port
	(
		rst				: in  STD_LOGIC;
		clk				: in  STD_LOGIC;
		ce				: in  STD_LOGIC;

		rdy				: out STD_LOGIC;
		
		ctrl_in			: in  tlb_ctrl_in_t;
		ctrl_out		: out tlb_ctrl_out_t;
		
		query_in		: in  tlb_query_in_t;
		query_out		: out tlb_query_out_t

	);
END tlb;

ARCHITECTURE behavior OF tlb IS

	signal clk_rd					: STD_LOGIC;
	signal cam_entry_lo				: tlb_entry_lo_t;
	
	
	signal entry_lo_UMC				: tlb_entry_lo_t;
	signal entry_lo_UMUC			: tlb_entry_lo_t;
	signal entry_lo_res				: tlb_entry_lo_t;

	signal qry_res					: tlb_query_out_t;
	signal stat_res					: tlb_status_t;

	signal pa						: word_t;
	signal hit						: STD_LOGIC;
	signal valid					: STD_LOGIC;

	signal cam_rdy					: STD_LOGIC;
	signal cam_hit_vld				: STD_LOGIC;

	signal vaddr_reg				: word_t;

	subtype reg_lo_t is unsigned(23 downto 0);
	subtype reg_hi_t is unsigned(25 downto 0);
	
	signal cam_lo_dout				: reg_lo_t;
	signal reg_lo_din				: reg_lo_t;
	signal reg_lo_dout				: reg_lo_t;
	signal reg_hi_din				: reg_hi_t;
	signal reg_hi_dout				: reg_hi_t;

	function to_entry_lo(x : reg_lo_t) return tlb_entry_lo_t is
	variable result : tlb_entry_lo_t;
	begin
		result.PFN	:= x(23 downto 4);
		result.N 	:= x(3);
		result.D	:= x(2);
		result.V	:= x(1);
		result.G 	:= x(0);
		
		return result;
		
	end to_entry_lo;
	
	function to_entry_hi(x : reg_hi_t) return tlb_entry_hi_t is
	variable result : tlb_entry_hi_t;
	begin
		result.VPN		:= x(25 downto 6);
		result.ASID 	:= x(5 downto 0);
	
		return result;

	end to_entry_hi;

	function to_reg_lo(x : tlb_entry_lo_t) return reg_lo_t is
	variable result : reg_lo_t;
	begin
		result(23 downto 4)						:= x.PFN;
		result(3)								:= x.N;
		result(2)								:= x.D;
		result(1)								:= x.V;
		result(0)								:= x.G;

		return result;

	end to_reg_lo;

	function to_reg_hi(x : tlb_entry_hi_t) return reg_hi_t is
	variable result : reg_hi_t;
	begin
		result(25 downto 6)						:= x.VPN;
		result(5 downto 0)						:= x.ASID;
		
		return result;
		
	end to_reg_hi;

	signal hit_idx		: integer;
	function decode(x : unsigned; N : integer) return integer is
	variable result : integer;
	begin
		result := 0;
		for i in 0 to N-1 loop
			if x(i) = '1' then
				result := i;
			end if;
		end loop;
	
		return result;
		
	end decode;
	
begin
	clk_rd							<= clk;
	rdy								<= cam_rdy;
	
	stat_res.exc_vld				<= valid;
	stat_res.exc_miss				<= not hit;
	stat_res.exc_dirty				<= hit and qry_res.flags.D and query_in.write;
	stat_res.write					<= query_in.write;

	qry_res.paddr					<= pa;
	qry_res.hit						<= hit;
	qry_res.flags.N					<= entry_lo_res.N;
	qry_res.flags.D					<= entry_lo_res.D;
	qry_res.flags.V					<= entry_lo_res.V;
	qry_res.exc						<= stat_res.exc_miss or stat_res.exc_dirty;
	qry_res.vld						<= valid;

	query_out						<= qry_res;
	ctrl_out.stat					<= stat_res;

	pa								<= entry_lo_res.PFN & vaddr_reg(lg2(TLB_PAGE_SIZE)-1 downto 0);
	cam_entry_lo					<= to_entry_lo(to_mips_01(cam_lo_dout));

inst_reg_entry_lo: entity work.dpram_1w1r2c_ra
	GENERIC MAP
	(
		addr_width	=>	lg2(NUM_ENTRIES),
		data_width	=>	24
	)
	PORT MAP
	(
		clk_a	=>	clk,
		clk_b	=>	clk,
		en_a	=>	'1',
		en_b	=>	ctrl_in.entry_re,
		we_a	=>	ctrl_in.entry_we,
		addr_a	=>	ctrl_in.entry_wr_idx(lg2(NUM_ENTRIES)-1 downto 0),
		addr_b	=>	ctrl_in.entry_rd_idx(lg2(NUM_ENTRIES)-1 downto 0),
		din_a	=>	reg_lo_din,
		dout_b	=>	reg_lo_dout
	);
	reg_lo_din						<= to_reg_lo(ctrl_in.entry_lo);
	ctrl_out.entry_lo				<= to_entry_lo(to_mips_01(reg_lo_dout));

inst_reg_entry_hi: entity work.dpram_1w1r2c_ra
	GENERIC MAP 
	(
		addr_width	=>	lg2(NUM_ENTRIES),
		data_width	=>	26
	)
	PORT MAP
	(
		clk_a	=>	clk,
		clk_b	=>	clk,
		en_a	=>	'1',
		en_b	=>	ctrl_in.entry_re,
		we_a	=>	ctrl_in.entry_we,
		addr_a	=>	ctrl_in.entry_wr_idx(lg2(NUM_ENTRIES)-1 downto 0),
		addr_b	=>	ctrl_in.entry_rd_idx(lg2(NUM_ENTRIES)-1 downto 0),
		din_a	=>	reg_hi_din,
		dout_b	=>	reg_hi_dout
	);

	reg_hi_din						<= to_reg_hi(ctrl_in.entry_hi);
	ctrl_out.entry_hi				<= to_entry_hi(to_mips_01(reg_hi_dout));

inst_cam_va: entity work.cam2
	GENERIC MAP
	(
		NUM_ENTRIES			=>	NUM_ENTRIES,
		ENTRY_WIDTH			=>	reg_lo_t'length,
		DATA_WIDTH			=>	20,
		CAM_RAM_MAX_WIDTH	=>	8
	)
	PORT MAP
	(
		rst			=>	rst,
		clk			=>	clk,
		
		rdy			=>	cam_rdy,

		re			=>	query_in.vld,
		we			=>	ctrl_in.entry_we,
		vld			=>	'1',
		addr		=>	ctrl_in.entry_wr_idx,
		entry_din	=>	reg_lo_din,
		entry_dout	=>	cam_lo_dout,

		tag_wr		=>	ctrl_in.entry_hi.VPN,
		tag_rd		=>	query_in.vaddr(31 downto lg2(TLB_PAGE_SIZE)),

		hit_vld		=>	cam_hit_vld

	);


proc_entry_lo_umc:
	process(clk)
	variable index : integer;
	begin
		if rising_edge(clk) then
			entry_lo_UMC.N		<= '0';
			entry_lo_UMC.D		<= '0';
			entry_lo_UMC.G		<= '0';
			if query_in.vld = '1' then
				entry_lo_UMC.V		<= '0';
				if query_in.vaddr(31 downto 29) = "100" then	-- 80000000 to  9FFFFFFF
					entry_lo_UMC.V		<= '1';
					entry_lo_UMC.PFN	<= "000" & query_in.vaddr(28 downto lg2(TLB_PAGE_SIZE));
				end if;
			end if;
		end if;
	end process;	

proc_entry_lo_umuc:
	process(clk)
	variable index : integer;
	begin
		if rising_edge(clk) then
			if CACHE_KSEG1 then
				entry_lo_UMUC.N		<= '0';
			else
				entry_lo_UMUC.N		<= '1';
			end if;
			entry_lo_UMUC.D		<= '0';
			entry_lo_UMUC.G		<= '0';
			if query_in.vld = '1' then
				entry_lo_UMUC.V		<= '0';
				if query_in.vaddr(31 downto 29) = "101" then	-- A0000000 to  BFFFFFFF
					entry_lo_UMUC.V		<= '1';
					entry_lo_UMUC.PFN	<= "000" & query_in.vaddr(28 downto lg2(TLB_PAGE_SIZE));
				end if;
			end if;
		end if;
	end process;	

tlb_entry_lo_result_translate:
if USE_TLB = true generate
	tlb_entry_lo_result:
		process(cam_entry_lo, entry_lo_umc, entry_lo_umuc)
		begin
			entry_lo_res <= cam_entry_lo;
			if entry_lo_umc.V = '1' then
				entry_lo_res <= entry_lo_umc;
			end if;
			if entry_lo_umuc.V = '1' then
				entry_lo_res <= entry_lo_umuc;
			end if;

		end process;
end generate;

tlb_entry_lo_result_notranslate:
if USE_TLB = false generate
	tlb_entry_lo_result:
		process(cam_entry_lo, vaddr_reg, entry_lo_umc, entry_lo_umuc)
		begin
			entry_lo_res.N		<= '0';
			entry_lo_res.D		<= '0';
			entry_lo_res.V		<= '1';
			entry_lo_res.PFN	<= vaddr_reg(31 downto lg2(TLB_PAGE_SIZE));
			if entry_lo_umc.V = '1' then
				entry_lo_res <= entry_lo_umc;
			end if;
			if entry_lo_umuc.V = '1' then
				entry_lo_res <= entry_lo_umuc;
			end if;

		end process;
end generate;

tlb_hit_combine_translate:
if USE_TLB = true generate
	tlb_hit_combine:
		process(cam_hit_vld, entry_lo_umc, entry_lo_umuc)
			variable t_v : std_logic;
		begin
			t_v := cam_hit_vld;
			t_v := t_v or entry_lo_umc.V or entry_lo_umuc.V;
			hit	<= t_v;

		end process;
end generate;

tlb_hit_combine_notranslate:
if USE_TLB = false generate
	tlb_hit_combine:
		process(cam_entry_lo, entry_lo_umc, entry_lo_umuc)
			variable t_v : std_logic;
		begin
			t_v := entry_lo_umc.V or entry_lo_umuc.V;
			hit	<= t_v;

		end process;
end generate;

registered_output:
	process(clk)
	begin
		if rising_edge(clk) then
			if query_in.vld = '1' then
				vaddr_reg		<= query_in.vaddr;
			end if;
			valid	<= query_in.vld;
			if ce = '1' then
				if ctrl_in.entry_re = '1' then
					ctrl_out.entry_vld		<= '1';
				end if;
			end if;
		end if;
	end process;



------------------------------------------------------------------
end behavior;
