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_ALL			: boolean := false;
		USE_CACHE			: 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

	constant UNMAPPED_CACHED	: natural := NUM_ENTRIES;
	constant UNMAPPED_UNCACHED	: natural := NUM_ENTRIES+1;

	signal blk_entry_we		: unsigned(NUM_ENTRIES-1 downto 0);
	signal blk_hit			: unsigned(NUM_ENTRIES-1+2 downto 0);
	
	type flags_array_t is array (0 to NUM_ENTRIES-1+2) of tlb_flags_t;
	signal blk_flags		: flags_array_t;

	type word_array_t is array (0 to NUM_ENTRIES-1+2) of word_t;
	signal blk_pfn			: word_array_t;

	type tlb_entry_lo_array_t is array (0 to NUM_ENTRIES-1) of tlb_entry_lo_t;
	signal blk_entry_lo			: tlb_entry_lo_array_t;

	type tlb_entry_hi_array_t is array (0 to NUM_ENTRIES-1) of tlb_entry_hi_t;
	signal blk_entry_hi			: tlb_entry_hi_array_t;
	
	signal qry_res_no_translate		: tlb_query_out_t;
	signal qry_res					: tlb_query_out_t;

	signal stat_res_no_translate	: tlb_status_t;
	signal stat_res					: tlb_status_t;

	signal pa						: word_t;
	signal hit						: STD_LOGIC;
	signal flags					: tlb_flags_t;


begin

	rdy								<= '1';

	blk_hit(UNMAPPED_CACHED)		<= '1' when query_in.vaddr(31 downto 29) = "100" else '0';
	blk_pfn(UNMAPPED_CACHED)		<= "000" & query_in.vaddr(28 downto lg2(TLB_PAGE_SIZE)) & (lg2(TLB_PAGE_SIZE)-1 downto 0 => '0');
	blk_flags(UNMAPPED_CACHED).N	<= '0';
	blk_flags(UNMAPPED_CACHED).D	<= '0';
	blk_flags(UNMAPPED_CACHED).V	<= '1';

	blk_hit(UNMAPPED_UNCACHED)		<= '1' when query_in.vaddr(31 downto 29) = "101" else '0';
	blk_pfn(UNMAPPED_UNCACHED)		<= "000" & query_in.vaddr(28 downto lg2(TLB_PAGE_SIZE)) & (lg2(TLB_PAGE_SIZE)-1 downto 0 => '0');
	blk_flags(UNMAPPED_UNCACHED).N	<= '1';
	blk_flags(UNMAPPED_UNCACHED).D	<= '0';
	blk_flags(UNMAPPED_UNCACHED).V	<= '1';
	
	stat_res.exc_vld				<= '0';
	stat_res.exc_miss				<= not hit;
	stat_res.exc_dirty				<= hit and flags.D and query_in.write;
	stat_res.write					<= query_in.write;

	stat_res_no_translate.exc_vld	<= '0';
	stat_res_no_translate.exc_miss	<= '0';
	stat_res_no_translate.exc_dirty	<= '0';
	stat_res_no_translate.write		<= query_in.write;

	qry_res.paddr					<= pa;
	qry_res.hit						<= hit;
	qry_res.flags					<= flags;
	qry_res.exc						<= stat_res.exc_miss or stat_res.exc_dirty;
	qry_res.vld						<= '0';

	qry_res_no_translate.vld		<= '0';
	qry_res_no_translate.paddr		<= query_in.vaddr;
	qry_res_no_translate.hit		<= '1';
	qry_res_no_translate.exc		<= '0';
	qry_res_no_translate.flags.N	<= '0' when CACHE_ALL = true else '1' when USE_CACHE = false else '1' when query_in.vaddr(31 downto 29) = "101" else '0';
	qry_res_no_translate.flags.D	<= '0';
	qry_res_no_translate.flags.V	<= '1';


gen_tlb:
for i in 0 to NUM_ENTRIES-1 generate
	
	inst_tlb_block : entity work.tlb_block
	Port map
	(
		clk					=> clk,
		rst					=> rst,

		entry_we			=> blk_entry_we(i),
		entry_hi_in			=> ctrl_in.entry_hi,
		entry_lo_in			=> ctrl_in.entry_lo,
		
		entry_hi_out		=> blk_entry_hi(i),
		entry_lo_out		=> blk_entry_lo(i),
		
		va					=> query_in.vaddr,
		pfn					=> blk_pfn(i),
		flags				=> blk_flags(i),
		hit					=> blk_hit(i)
	);

end generate;

tlb_blk_write_mux:
	process(ctrl_in)
	begin
		blk_entry_we			<= (others => '0');
		blk_entry_we(to_integer(ctrl_in.entry_wr_idx(lg2(NUM_ENTRIES)-1 downto 0)))	<= ctrl_in.entry_we;
	end process;
	
tlb_pa_combine:
	process(blk_hit, blk_pfn, query_in.vaddr)
		variable t_v : word_t;
	begin
		for i in 0 to NUM_ENTRIES-1+2 loop
			if blk_hit(i) = '1' then
				t_v := blk_pfn(i);
			end if;
		end loop;
		pa	<= t_v(31 downto lg2(TLB_PAGE_SIZE)) & query_in.vaddr(lg2(TLB_PAGE_SIZE)-1 downto 0);
		
	end process;

tlb_flags_combine:
	process(blk_hit, blk_flags)
		variable t_v : tlb_flags_t;
	begin
		for i in 0 to NUM_ENTRIES-1+2 loop
			if blk_hit(i) = '1' then
				t_v := blk_flags(i);
			end if;
		end loop;
		flags	<= t_v;
	
	end process;

tlb_hit_combine:
	process(blk_hit, blk_flags)
		variable t_v : std_logic;
	begin
		t_v := '0';
		for i in 0 to NUM_ENTRIES-1+2 loop
			t_v := t_v or (blk_hit(i) and blk_flags(i).V);
		end loop;
		hit		<= t_v;
		
	end process;

registered_output:
	process(clk)
	begin
		if rising_edge(clk) then
			ctrl_out.entry_vld		<= '0';
			if query_in.vld = '1' then
				if USE_TLB then
					query_out			<= qry_res;
					ctrl_out.stat		<= stat_res;
				else
					query_out			<= qry_res_no_translate;
					ctrl_out.stat		<= stat_res_no_translate;
				end if;
			end if;
			query_out.vld						<= query_in.vld;
			ctrl_out.stat.exc_vld				<= query_in.vld;
			if ce = '1' then
				if ctrl_in.entry_re = '1' then
					ctrl_out.entry_lo		<= blk_entry_lo(to_integer(ctrl_in.entry_rd_idx(lg2(NUM_ENTRIES)-1 downto 0)));
					ctrl_out.entry_hi		<= blk_entry_hi(to_integer(ctrl_in.entry_rd_idx(lg2(NUM_ENTRIES)-1 downto 0)));
					ctrl_out.entry_vld		<= '1';
				end if;
			end if;
		end if;
	end process;



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