--------------------------------------------------------------------------
-- Project: JIPS, a portable 32-bit RISC CPU written in VHDL
-- This file: JIPS top file 
--
-- Copyright (C) 2008 J. Ahrensfeld
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- For questions and ideas, please contact the author at jens@jayfield.org
--
--------------------------------------------------------------------------

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

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

ENTITY icache IS
	Generic
	(
		CACHE_SIZE	: natural := 1024;	-- words
		LINE_SIZE	: natural := 8;		-- words
		CACHE_ALL	: boolean := false;
		USE_CACHE	: boolean := true;
		USE_TLB		: boolean := false
	);
	Port
	(
		rst					: in  STD_LOGIC;
		clk					: in  STD_LOGIC;

		-- CPU control/data
		en				: in  STD_LOGIC;
		addr			: in  word_t;
		dout			: out word_t;
		rdy				: out STD_LOGIC;

		-- Cache control
		ctrl_in				: in  cache_ctrl_t;

		tlb_query_out		: out tlb_query_in_t;
		tlb_query_in		: in  tlb_query_out_t;

		-- busmaster data
		bus_din				: in word_t;
		bus_din_rdy			: out std_logic;
		bus_din_vld			: in std_logic;

		-- busmaster command
		bus_cmd_rdy			: in std_logic;
		bus_cmd_we			: out std_logic;
		bus_cmd_cycle_en	: out std_logic;
		bus_cmd_out			: out bm_cmd_t;
		bus_cyc_complete	: in std_logic
	);
END icache;

ARCHITECTURE behavior OF icache IS

COMPONENT dpram_1w1r2c_ra
	GENERIC
	(
		addr_width : integer := 3;
		data_width : integer := 8
	);
	PORT (
		clk_a		: in  STD_LOGIC;
		clk_b		: in  STD_LOGIC;
		en_a		: in  STD_LOGIC;
		en_b		: in  STD_LOGIC;
		we_a		: in  STD_LOGIC;
		addr_a		: in  unsigned (addr_width-1 downto 0);
		addr_b		: in  unsigned (addr_width-1 downto 0);
		din_a		: in  unsigned (data_width-1 downto 0);
		dout_b		: out unsigned (data_width-1 downto 0)
	);
END COMPONENT;

COMPONENT dpram_2w2r2c_ra is
	GENERIC 
	(
		addr_width : integer := 3;
		data_width : integer := 8
	);
	PORT
	(
		clk_a		: in  STD_LOGIC;
		clk_b		: in  STD_LOGIC;
		en_a		: in  STD_LOGIC;
		en_b		: in  STD_LOGIC;
		we_a		: in  STD_LOGIC;
		we_b		: in  STD_LOGIC;
		addr_a		: in  unsigned (addr_width-1 downto 0);
		addr_b		: in  unsigned (addr_width-1 downto 0);
		din_a		: in  unsigned (data_width-1 downto 0);
		din_b		: in  unsigned (data_width-1 downto 0);
		dout_a		: out unsigned (data_width-1 downto 0);
		dout_b		: out unsigned (data_width-1 downto 0)
	);
END COMPONENT;

	constant addr_width			: natural := 32;
	constant word_index_width	: natural := lg2(LINE_SIZE);
	constant cache_index_width	: natural := lg2(CACHE_SIZE) - word_index_width;
	constant tag_width			: natural := addr_width - word_index_width - cache_index_width - 2;
	constant tag_parity_width	: natural := 3;
	constant tag_ram_data_width	: natural := 1 + tag_parity_width + tag_width;
	constant tag_ram_addr_width	: natural := cache_index_width;
	
	subtype	tag_ram_data_t is unsigned (tag_ram_data_width-1 downto 0);

	type icache_entry_t is record
		valid	: std_logic;
		tv_p	: unsigned(tag_parity_width-1 downto 0);
		tag		: unsigned(tag_width-1 downto 0);
	end record;
	
	function to_icache_entry(x : tag_ram_data_t) return icache_entry_t is
	variable result : icache_entry_t;
	begin
		result.valid	:= x(0);
		result.tv_p		:= x(3 downto 1);
		result.tag		:= x(tag_width+3 downto 4);
		
		return result;
	end to_icache_entry;

	function to_tag_ram_data(x : icache_entry_t) return tag_ram_data_t is
	variable result : tag_ram_data_t;
	begin
		result(0)										:= x.valid;
		result(3 downto 1)								:= x.tv_p;
		result(tag_width+3 downto 4)					:= x.tag;
		
		return result;
	end to_tag_ram_data;

	function to_word_index(x : word_t) return unsigned is
	variable result : unsigned(word_index_width-1 downto 0);
	begin
		result := x(word_index_width+1 downto 2);
		
		return result;
		
	end to_word_index;
	

	function to_cache_index(x : word_t) return unsigned is
	variable result : unsigned(cache_index_width-1 downto 0);
	begin
		result := x(cache_index_width+word_index_width+1 downto word_index_width+2);
		
		return result;
		
	end to_cache_index;

	function to_tag(x : word_t) return unsigned is
	variable result : unsigned(tag_width-1 downto 0);
	begin
		result := x(tag_width+cache_index_width+word_index_width+1 downto cache_index_width+word_index_width+2);
		
		return result;
		
	end to_tag;

	type cache_busy_state_t is (init, ready, busy);
	signal cache_busy_state, cache_busy_state_next : cache_busy_state_t;

	type cache_ctrl_state_t is (init, ready, invalidate, invalidate_post, flush, bus_request, mem_read, mem_read_single, mem_read_data, mem_read_data_single, rd_cache, upd_cache);
	signal cache_ctrl_state, cache_ctrl_state_next : cache_ctrl_state_t;
	
	type debug_t is record
		ready			: std_logic;
		req_strobe		: std_logic;
		req_read		: std_logic;
		req_va			: word_t;
		req_pa			: word_t;
		dout_reg		: word_t;
	end record;

	signal debug		: debug_t;

	signal	req_strobe				: std_logic;
	signal	cache_req_rd			: std_logic;
	signal	read_rdy				: std_logic;
	signal	cache_rdy				: std_logic;
	signal	cache_hit				: std_logic;
	signal  request_va				: word_t;
	signal  request_pa				: word_t;

	signal	tag_match				: std_logic;
	signal	cache_hit_inv			: std_logic;
	signal	tag_match_inv			: std_logic;
	
	signal	mem_fetch_req			: std_logic;
	signal	mem_fetch_ack			: std_logic;
	signal	single_read_en			: std_logic;
	signal	single_read_set			: std_logic;
	signal	single_read_reg_vld		: std_logic;

	signal  cache_index_inv			: unsigned(cache_index_width-1 downto 0);
	signal  tag_inv					: unsigned(tag_width-1 downto 0);
	signal  tag_reg_inv				: unsigned(tag_width-1 downto 0);
	
	signal	cache_entry_in			: icache_entry_t;
	signal	cache_entry_out			: icache_entry_t;
	signal	cache_entry_out_inv		: icache_entry_t;
	signal  data_ram_addr_rd		: unsigned(lg2(CACHE_SIZE)-1 downto 0);
	signal  data_ram_data_rd		: word_t;
	signal  data_ram_addr_wr		: unsigned(lg2(CACHE_SIZE)-1 downto 0);
	signal  data_ram_data_wr		: word_t;
	signal	data_ram_we				: std_logic;
	signal  single_read_reg			: word_t;
	signal  cache_dout				: word_t;

	signal  tag_ram_addr_rd			: unsigned(cache_index_width-1 downto 0);
	signal  tag_ram_data_rd			: tag_ram_data_t;
	signal  tag_ram_data_rd_inv		: tag_ram_data_t;
	signal  tag_ram_addr_wr			: unsigned(cache_index_width-1 downto 0);
	signal  tag_ram_data_wr			: tag_ram_data_t;
	signal	tag_ram_we				: std_logic;

	signal	fill_count				: natural range 0 to 2**word_index_width-1;
	signal	fill_count_en			: std_logic;
	signal	fill_count_rdy			: std_logic;
	signal	flush_count				: natural range 0 to 2**cache_index_width-1;
	signal	flush_count_rst			: std_logic;
	signal	flush_count_en			: std_logic;
	signal	flush_count_rdy			: std_logic;
	signal	request_count			: natural range 0 to 2**word_index_width-1;
	signal	request_count_en		: std_logic;
	signal	request_count_rdy		: std_logic;
	signal  ram_read_en				: std_logic;
	signal  ram_write_en			: std_logic;
	signal	was_miss				: std_logic;
	signal	invalidate_all			: std_logic;
	signal	invalidate_ack			: std_logic;
	signal  invalidate_en			: std_logic;
	signal  invalidate_req			: std_logic;

	signal	fill_word_index			: unsigned(word_index_width-1 downto 0);
	signal	req_word_index			: unsigned(word_index_width-1 downto 0);

	signal	tlb_exc					: std_logic;
	signal	tlb_exc_type			: unsigned(1 downto 0);
	
	signal tlb_result_no_translate	: tlb_query_out_t;
	signal tlb_result				: tlb_query_out_t;

begin

	tlb_query_out.vaddr					<= request_va;
	tlb_result_no_translate.paddr		<= request_va;
	tlb_result_no_translate.hit_idx		<= (others => '0');
	tlb_result_no_translate.hit			<= '1';
	tlb_result_no_translate.flags.N		<= '0' when CACHE_ALL = true else '1' when USE_CACHE = false else '1' when request_va(31 downto 29) = "101" else '0';
	tlb_result_no_translate.flags.D		<= '0';
	tlb_result_no_translate.flags.V		<= '1';

	cache_index_inv						<= ctrl_in.inv_addr(cache_index_width+word_index_width+1 downto word_index_width+2);
	tag_inv								<= ctrl_in.inv_addr(tag_width+cache_index_width+word_index_width+1 downto cache_index_width+word_index_width+2);

	tlb_result							<= tlb_query_in when USE_TLB else tlb_result_no_translate;
	request_pa							<= tlb_result.paddr;

	tlb_exc								<= cache_req_rd and not tlb_result.hit;
	tlb_exc_type						<= "10" when tlb_exc = '1' else "00";
	
	debug.req_strobe	<= req_strobe;
	debug.req_read		<= cache_req_rd;
	debug.req_va		<= request_va;
	debug.req_pa		<= request_pa;
	debug.dout_reg		<= cache_dout;
	debug.ready			<= cache_rdy;
	
-------------------------------------------------
-- ctrl
-------------------------------------------------
req_strobe	<= cache_rdy and en;

ctrl_request_register:
	process(clk)
	begin
		if rising_edge(clk) then
			if cache_rdy = '1' then
				cache_req_rd	<= en;
			end if;
		end if;
	end process;	

-------------------------------------------------
fill_address_register:
	process(clk)
	begin
		if rising_edge(clk) then
			if req_strobe = '1' then
				request_va	<= addr;
				fill_word_index	<= to_word_index(addr);
			elsif bus_din_vld = '1' and fill_count_en = '1' then
				fill_word_index	<= fill_word_index + 1;
			end if;
		end if;
	end process;	

bus_request_address_register:
	process(clk)
	begin
		if rising_edge(clk) then
			if req_strobe = '1' then
				req_word_index	<= to_word_index(addr);
			elsif request_count_en = '1' and bus_cmd_rdy = '1' then
				req_word_index	<= req_word_index + 1;
			end if;	
		end if;
	end process;	

single_read_register:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				single_read_en <= '0';
				single_read_reg_vld <= '0';
			elsif single_read_en = '0' then
				if single_read_set = '1' then
					single_read_en		<= '1';
				end if;
			elsif single_read_reg_vld = '1' then
				if en = '1' then
					single_read_en <= '0';
					single_read_reg_vld <= '0';
				end if;	
			elsif bus_din_vld = '1' and single_read_en = '1' then
				single_read_reg <= bus_din;
				single_read_reg_vld <= '1';
			end if;
		end if;
	end process;	

cache_invalidate_request:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' or ctrl_in.inv_all = '1' or ctrl_in.inv_at = '1' then
				invalidate_req	<= '1';
				invalidate_all	<= ctrl_in.inv_all or rst;
				tag_reg_inv	<= tag_inv;
			elsif invalidate_ack = '1' then
				invalidate_req	<= '0';
				invalidate_all	<= '0';
			end if;
		end if;
	end process;	

inst_tag_ram : dpram_2w2r2c_ra
	GENERIC MAP 
	(
		addr_width	=> tag_ram_addr_width,
		data_width	=> tag_ram_data_width
	)
	PORT MAP
	(
		clk_a	=> clk,
		clk_b	=> clk,
		en_a	=> '1',
		en_b	=> ram_read_en,
		we_a	=> tag_ram_we,
		we_b	=> '0',
		addr_a	=> tag_ram_addr_wr,
		addr_b	=> tag_ram_addr_rd,
		din_a	=> tag_ram_data_wr,
		din_b	=> tag_ram_data_wr,
		dout_a	=> tag_ram_data_rd_inv,
		dout_b	=> tag_ram_data_rd
	);

inst_data_ram : dpram_1w1r2c_ra
	GENERIC MAP
	(
		addr_width	=> lg2(CACHE_SIZE),
		data_width	=> word_t'length
	)
	PORT MAP
	(
		clk_a	=> clk,
		clk_b	=> clk,
		en_a	=> '1',
		en_b	=> ram_read_en,
		we_a	=> data_ram_we,
		addr_a	=> data_ram_addr_wr,
		addr_b	=> data_ram_addr_rd,
		din_a	=> data_ram_data_wr,
		dout_b	=> data_ram_data_rd
	);
	
cache_state_next:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				cache_ctrl_state	<= init;
				cache_busy_state	<= init;
			else
				cache_ctrl_state	<= cache_ctrl_state_next;
				cache_busy_state	<= cache_busy_state_next;
			end if;
		end if;
	end process;	

	bus_din_rdy						<= fill_count_en or single_read_en;
	bus_cmd_out.addr				<= to_tag(request_pa) & to_cache_index(request_pa) & req_word_index & "00";
	bus_cmd_out.rw					<= '0';
	bus_cmd_out.sel					<= (others => '1');

	cache_rdy						<= read_rdy;
	rdy								<= cache_rdy;
	
	cache_dout						<= single_read_reg when single_read_en = '1' else data_ram_data_rd;
	dout							<= cache_dout;

	cache_entry_out					<= to_icache_entry(tag_ram_data_rd);
	tag_match						<= '1' when to_tag(request_pa) = cache_entry_out.tag else '0';
	cache_hit						<= tag_match and cache_entry_out.valid;

	cache_entry_out_inv				<= to_icache_entry(tag_ram_data_rd_inv);
	tag_match_inv					<= '1' when tag_reg_inv = cache_entry_out_inv.tag else '0';
	cache_hit_inv					<= tag_match_inv and cache_entry_out_inv.valid;

	ram_read_en						<= en or was_miss;
	tag_ram_data_wr					<= to_tag_ram_data(cache_entry_in);
	tag_ram_addr_wr					<= to_unsigned(flush_count, cache_index_width) when invalidate_en = '1' else to_cache_index(request_va);
	tag_ram_addr_rd					<= to_cache_index(addr) when was_miss = '0' else to_cache_index(request_va);
	data_ram_addr_rd				<= (to_cache_index(addr) & to_word_index(addr)) when was_miss = '0' else (to_cache_index(request_va) & fill_word_index);
	data_ram_addr_wr				<= to_cache_index(request_va) & fill_word_index;
	data_ram_data_wr				<= bus_din;
	data_ram_we						<= ram_write_en and bus_din_vld;

cache_busy_state_fsm:
	process(cache_busy_state, tlb_result, cache_req_rd, cache_hit, mem_fetch_ack, single_read_en, single_read_reg_vld)
	begin

		read_rdy 				<= '0';
		mem_fetch_req			<= '0';
		single_read_set			<= '0';
		cache_busy_state_next	<= cache_busy_state;
		
		case cache_busy_state is

		when init =>
			cache_busy_state_next <= ready;
			
		when ready =>
			if single_read_en = '1' then
				read_rdy			<= single_read_reg_vld;
			else
				read_rdy 				<= '1';
				if cache_req_rd = '1' then
					if tlb_result.hit = '1' then
						if tlb_result.flags.N = '1' or cache_hit = '0' then
							mem_fetch_req			<= '1';	
							read_rdy				<= '0';
							single_read_set			<= tlb_result.flags.N;
							cache_busy_state_next 	<= busy;
						end if;
					end if;
				end if;
			end if;

		when busy =>
			mem_fetch_req	<= '1';	
			if mem_fetch_ack = '1' then
				cache_busy_state_next <= ready;
			end if;
		
		when others => 
			cache_busy_state_next <= ready;
				
		end case;
		
	end process;

cache_ctrl_state_fsm:
	process(cache_ctrl_state, mem_fetch_req, tlb_result, bus_din_vld, cache_hit_inv, flush_count_rdy, fill_count_rdy, request_count_rdy, request_pa, bus_cmd_rdy, invalidate_req, invalidate_all)
	begin
		tag_ram_we				<= '0';
		flush_count_en			<= '0';
		flush_count_rst			<= '0';
		invalidate_en			<= '0';
		request_count_en		<= '0';
		fill_count_en			<= '0';
		ram_write_en			<= '0';
		bus_cmd_cycle_en		<= '0';
		bus_cmd_we				<= '0';
		was_miss				<= '0';
		invalidate_ack			<= '0';
		mem_fetch_ack			<= '0';
		
		cache_entry_in.tv_p		<= (others => '0');
		cache_entry_in.tag		<= to_tag(request_pa);
		cache_entry_in.valid	<= '0';
		cache_ctrl_state_next	<= cache_ctrl_state;
		
		case cache_ctrl_state is

		when init =>
			cache_ctrl_state_next <= ready;

		when ready =>
			if invalidate_req = '1' then
				cache_ctrl_state_next 	<= invalidate;
				invalidate_en	<= '1';
			elsif mem_fetch_req = '1' then
				cache_ctrl_state_next	<= bus_request;
				bus_cmd_cycle_en		<= '1';
			end if;

		when invalidate =>
			cache_ctrl_state_next <= rd_cache;
			invalidate_en			<= '1';
			invalidate_ack			<= '1';
			if invalidate_all = '1' then
				cache_ctrl_state_next					<= flush;
				flush_count_rst		<= '1';
			elsif cache_hit_inv = '1' then
				tag_ram_we				<= '1';
				cache_entry_in.valid	<= '0';
				cache_entry_in.tag		<= (others => '0');
				cache_ctrl_state_next	<= invalidate_post;
			end if;	
				
		when flush =>
			flush_count_en			<= '1';
			invalidate_en			<= '1';
			tag_ram_we				<= '1';
			cache_entry_in.valid	<= '0';
			cache_entry_in.tag		<= (others => '0');
			if flush_count_rdy = '1' then
				tag_ram_we			<= '0';
				cache_ctrl_state_next	<= invalidate_post;
			end if;

		when invalidate_post =>
			was_miss	<= '1';
			cache_ctrl_state_next		<= ready;

		when bus_request =>
			bus_cmd_cycle_en	<= '1';
			if bus_cmd_rdy = '1' then
				cache_ctrl_state_next <= mem_read;
				if tlb_result.flags.N = '1' then
					cache_ctrl_state_next <= mem_read_single;
				end if;	
			end if;

		when mem_read =>
			bus_cmd_cycle_en	<= '1';
			bus_cmd_we			<= '1';
			request_count_en	<= '1';
			ram_write_en		<= '1';
			fill_count_en		<= '1';
			if request_count_rdy = '1' then
				bus_cmd_we		<= '0';
				cache_ctrl_state_next	<= mem_read_data;
			end if;	

		when mem_read_single =>
			mem_fetch_ack		<= '1';
			bus_cmd_cycle_en	<= '1';
			bus_cmd_we			<= '1';
			cache_ctrl_state_next <= mem_read_data_single;

		when mem_read_data =>
			bus_cmd_cycle_en	<= '1';
			fill_count_en		<= '1';
			ram_write_en		<= '1';
			if fill_count_rdy = '1' then
				tag_ram_we				<= '1';
				cache_entry_in.valid	<= '1';
				cache_ctrl_state_next	<= rd_cache;
			end if;	

		when mem_read_data_single =>
			bus_cmd_cycle_en	<= '1';
			if bus_din_vld = '1' then
				cache_ctrl_state_next	<= ready;
			end if;	

		when rd_cache =>
			mem_fetch_ack	<= '1';
			was_miss	<= '1';
			cache_ctrl_state_next		<= ready;
			
		when others => 
			cache_ctrl_state_next		<= ready;
		end case;

	end process;

flush_counter:
	process(clk)
	begin
		if rising_edge(clk) then
			if ctrl_in.inv_at = '1' then
				flush_count		<= to_integer(cache_index_inv);
			elsif flush_count_rst = '1' then
				flush_count_rdy	<= '0';
				flush_count		<= 2**cache_index_width-1;
			elsif flush_count_en = '1' then
				if flush_count /= 0 then
					flush_count	<= flush_count - 1;
				else
					flush_count_rdy	<= '1';
				end if;	
			end if;
		end if;
	end process;	
		
request_counter:
	process(clk)
	begin
		if rising_edge(clk) then
			if request_count_en = '0' then
				request_count_rdy	<= '0';
				request_count		<= 2**word_index_width-1;
			else	
				if bus_cmd_rdy = '1' then
					if request_count /= 0 then
						request_count 	<= request_count - 1;
					else
						request_count_rdy	<= '1';
					end if;
				end if;	
			end if;	
		end if;
	end process;	

fill_counter:
	process(clk)
	begin
		if rising_edge(clk) then
			if fill_count_en = '0' then
				fill_count_rdy	<= '0';
				fill_count		<= 2**word_index_width-1;
			else	
				if bus_din_vld = '1' then
					if fill_count /= 0 then
						fill_count 	<= fill_count - 1;
					else
						fill_count_rdy	<= '1';
					end if;
				end if;
			end if;	
		end if;
	end process;	

end behavior;
