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;
		TRANSLATE_KSEG0_1	: boolean := true;
		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 blk_hit					: unsigned(NUM_ENTRIES-1 downto 0);
	signal blk_entry_lo				: tlb_entry_lo_t;
	
	constant C_ENTRY_LO_DEFAULT		: tlb_entry_lo_t := 
	(
		N => '0', D => '0', V => '1', G => '0', PFN => (others => '0')
	);

	signal kseg1_force_cached		: STD_LOGIC;
	signal kseg_0_1_pfn				: unsigned (31 downto lg2(TLB_PAGE_SIZE));
	signal hit_umc					: STD_LOGIC;
	signal hit_umuc					: STD_LOGIC;
	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 reg_ctrl_entry_lo_din	: reg_lo_t;
	signal reg_ctrl_entry_lo_dout	: reg_lo_t;
	signal reg_qry_entry_lo_dout	: reg_lo_t;
	signal reg_qry_entry_lo_addr	: unsigned(lg2(NUM_ENTRIES)-1 downto 0);
	signal reg_ctrl_entry_lo_addr	: unsigned(lg2(NUM_ENTRIES)-1 downto 0);

	signal reg_ctrl_entry_hi_din	: reg_hi_t;
	signal reg_ctrl_entry_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;
	
	function decode2(x : unsigned; N : integer) return unsigned is
	variable result : unsigned (lg2(N)-1 downto 0);
	begin
		result := (others => '0');
		for i in 0 to N-1 loop
			result := result or ((lg2(N)-1 downto 0 => x(i)) and to_unsigned(i, lg2(N)));
		end loop;
	
		return result;
		
	end decode2;

begin
	kseg1_force_cached				<= '1' when CACHE_KSEG1 else '0';
	kseg_0_1_pfn					<= "000" & vaddr_reg(28 downto lg2(TLB_PAGE_SIZE)) when TRANSLATE_KSEG0_1 else vaddr_reg(31 downto lg2(TLB_PAGE_SIZE));

	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;
	qry_res.idx						<= (hit_umc or hit_umuc) & reg_qry_entry_lo_addr;
	
	query_out						<= qry_res;
	ctrl_out.stat					<= stat_res;

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

	hit_idx							<= decode(blk_hit, NUM_ENTRIES);
	
	blk_entry_lo					<= to_entry_lo(to_mips_01(reg_qry_entry_lo_dout));
--	reg_qry_entry_lo_addr			<= to_unsigned(hit_idx, lg2(NUM_ENTRIES));
	reg_qry_entry_lo_addr			<= decode2(blk_hit, NUM_ENTRIES);
	reg_ctrl_entry_lo_din			<= to_reg_lo(ctrl_in.entry_lo);
	
	ctrl_out.entry_hi				<= to_entry_hi(to_mips_01(reg_ctrl_entry_hi_dout));
	reg_ctrl_entry_hi_din			<= to_reg_hi(ctrl_in.entry_hi);

inst_reg_entry_lo: entity work.reg_dual
	GENERIC MAP
	(
		addr_width	=>	lg2(NUM_ENTRIES),
		data_width	=>	reg_lo_t'length
	)
	PORT MAP (
		clk_w	=>	clk,
		we		=>	ctrl_in.entry_we,
		en		=>	'1',
		wptr	=>	ctrl_in.entry_wr_idx(lg2(NUM_ENTRIES)-1 downto 0),
		din		=>	reg_ctrl_entry_lo_din,
		rptr_a	=>	ctrl_in.entry_rd_idx(lg2(NUM_ENTRIES)-1 downto 0),
		rptr_b	=>	reg_qry_entry_lo_addr,
		dout_a	=>	reg_ctrl_entry_lo_dout,
		dout_b	=>	reg_qry_entry_lo_dout
	);

inst_reg_entry_hi: entity work.dpram_1w1r2c_ra
	GENERIC MAP 
	(
		addr_width	=>	lg2(NUM_ENTRIES),
		data_width	=>	reg_hi_t'length
	)
	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_ctrl_entry_hi_din,
		dout_b	=>	reg_ctrl_entry_hi_dout
	);

inst_cam_va: entity work.cam
	GENERIC MAP
	(
		NUM_ENTRIES			=>	NUM_ENTRIES,
		DATA_WIDTH			=>	20,
		CAM_RAM_MAX_WIDTH	=>	15
	)
	PORT MAP
	(
		rst			=>	rst,
		clk			=>	clk,
		clk_rd		=>  clk_rd,
		rdy			=>	cam_rdy,

		re			=>	query_in.vld,
		we			=>	ctrl_in.entry_we,
		vld			=>	'1',
		addr		=>	ctrl_in.entry_wr_idx,
		tag_wr		=>	ctrl_in.entry_hi.VPN,

		tag_rd		=>	query_in.vaddr(31 downto lg2(TLB_PAGE_SIZE)),
		hit			=>	blk_hit(NUM_ENTRIES-1 downto 0),
		hit_vld		=>	cam_hit_vld

	);

proc_entry_lo_umc:
	process(clk)
	variable index : integer;
	begin
		if rising_edge(clk) then
			if query_in.vld = '1' then
				hit_umc		<= '0';
				if query_in.vaddr(31 downto 29) = "100" then	-- 80000000 to  9FFFFFFF
					hit_umc		<= '1';
				end if;
			end if;
		end if;
	end process;	

proc_entry_lo_umuc:
	process(clk)
	variable index : integer;
	begin
		if rising_edge(clk) then
			if query_in.vld = '1' then
				hit_umuc		<= '0';
				if query_in.vaddr(31 downto 29) = "101" then	-- A0000000 to  BFFFFFFF
					hit_umuc		<= '1';
				end if;
			end if;
		end if;
	end process;	

tlb_entry_lo_result_translate:
if USE_TLB = true generate
	tlb_entry_lo_result:
		process(blk_entry_lo, hit_umc, hit_umuc, kseg_0_1_pfn, kseg1_force_cached)
		begin
			entry_lo_res <= blk_entry_lo;
			if hit_umc = '1' or hit_umuc = '1' then
				entry_lo_res.N		<= hit_umuc and not kseg1_force_cached;
				entry_lo_res		<= C_ENTRY_LO_DEFAULT;
				entry_lo_res.PFN	<= kseg_0_1_pfn;
			end if;

		end process;
end generate;

tlb_entry_lo_result_notranslate:
if USE_TLB = false generate
	tlb_entry_lo_result:
		process(blk_entry_lo, vaddr_reg, hit_umc, hit_umuc, kseg_0_1_pfn, kseg1_force_cached)
		begin
			entry_lo_res		<= C_ENTRY_LO_DEFAULT;
			entry_lo_res.PFN	<= vaddr_reg(31 downto lg2(TLB_PAGE_SIZE));
			if hit_umc = '1' or hit_umuc = '1' then
				entry_lo_res.N		<= hit_umuc and not kseg1_force_cached;
				entry_lo_res		<= C_ENTRY_LO_DEFAULT;
				entry_lo_res.PFN	<= kseg_0_1_pfn;
			end if;
		end process;
end generate;

tlb_hit_combine_translate:
if USE_TLB = true generate
	tlb_hit_combine:
		process(blk_entry_lo, hit_umc, hit_umuc)
			variable t_v : std_logic;
		begin
			t_v := blk_entry_lo.V;
			t_v := t_v or hit_umc or hit_umuc;
			hit	<= t_v;

		end process;
end generate;

tlb_hit_combine_notranslate:
if USE_TLB = false generate
	tlb_hit_combine:
		process(blk_entry_lo, hit_umc, hit_umuc)
			variable t_v : std_logic;
		begin
			t_v := hit_umc or hit_umuc;
			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_lo		<= to_entry_lo(to_mips_01(reg_ctrl_entry_lo_dout));
					ctrl_out.entry_vld		<= '1';
				end if;
			end if;
		end if;
	end process;



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