--------------------------------------------------------------------------
-- 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_types.all;

ENTITY icache IS
	Generic
	(
		cache_size	: natural := 2048;	-- words
		line_size	: natural := 8		-- words
	);
	Port
	(
		RST_I		: in  STD_LOGIC;
		CLK_I		: in  STD_LOGIC;
		ACK_I		: in  STD_LOGIC;
		SRDY_I		: in  STD_LOGIC;
		MRDY_O		: out STD_LOGIC;
		ADDR_O		: out word_t;
		DAT_I		: in  word_t;
		STB_O		: out STD_LOGIC;
		CYC_O		: out STD_LOGIC;
		ctrl		: in  cache_ctrl_t;
		cpu_en		: in  STD_LOGIC;
		cpu_addr	: in  word_t;
		cpu_dout	: out word_t;
		cpu_busy	: out 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;

	type cache_state_t is (init, ready, invalidate, flush, mem_request, mem_access, mem_data, rd_cache, upd_cache);
	signal s, sn : cache_state_t;
	
	signal	cache_req			: std_logic;
	signal	cache_ack			: std_logic;
	signal	cache_busy			: std_logic;
	signal	cache_miss			: std_logic;
	signal	tag_match			: std_logic;
	signal	cache_miss_inv		: std_logic;
	signal	tag_match_inv		: std_logic;
	signal  request_addr		: unsigned(addr_width-1 downto 0);
	signal  fill_addr			: unsigned(addr_width-1 downto 0);
	
	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  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	was_miss			: std_logic;
	signal	invalidate_all		: std_logic;
	signal	invalidate_ack		: std_logic;
	signal  invalidate_en		: std_logic;
	signal  invalidate_req		: std_logic;
	
	alias cpu_word_index is cpu_addr(word_index_width+1 downto 2);
	alias cpu_cache_index is cpu_addr(cache_index_width+word_index_width+1 downto word_index_width+2);
	alias cpu_tag is cpu_addr(tag_width+cache_index_width+word_index_width+1 downto cache_index_width+word_index_width+2);

	alias fill_word_index is fill_addr(word_index_width+1 downto 2);
	alias fill_cache_index is fill_addr(cache_index_width+word_index_width+1 downto word_index_width+2);
	alias fill_tag is fill_addr(tag_width+cache_index_width+word_index_width+1 downto cache_index_width+word_index_width+2);

	alias req_word_index is request_addr(word_index_width+1 downto 2);
	alias req_cache_index is request_addr(cache_index_width+word_index_width+1 downto word_index_width+2);
	alias req_tag is request_addr(tag_width+cache_index_width+word_index_width+1 downto cache_index_width+word_index_width+2);

begin

	ram_read_en		<= cpu_en or was_miss;
	cache_index_inv	<= ctrl.inv_addr(cache_index_width+word_index_width+1 downto word_index_width+2);
	tag_inv			<= ctrl.inv_addr(tag_width+cache_index_width+word_index_width+1 downto cache_index_width+word_index_width+2);

	
fill_address_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if fill_count_en = '1' then
				if ACK_I = '1' then
					fill_word_index	<= fill_word_index + 1;
				end if;	
			elsif cache_busy = '0' then
				fill_addr	<= cpu_addr(addr_width-1 downto 2) & "00";
			end if;
		end if;
	end process;	

request_address_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if request_count_en = '1'then
				if SRDY_I = '1' then
					req_word_index	<= req_word_index + 1;
				end if;	
			elsif cache_busy = '0' then
				request_addr	<= cpu_addr(addr_width-1 downto 2) & "00";
			end if;
		end if;
	end process;	

cpu_request_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				cache_req	<= '0';
			elsif cpu_en = '1' then
				if cache_busy = '0' then
					cache_req			<= '1';
				end if;	
			elsif cache_ack = '1' then
				cache_req			<= '0';
			end if;
		end if;
	end process;	

cache_invalidate_request:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' or ctrl.inv_all = '1' or ctrl.inv_at = '1' then
				invalidate_req	<= '1';
				invalidate_all	<= ctrl.inv_all or RST_I;
				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_I,
		clk_b	=> CLK_I,
		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_I,
		clk_b	=> CLK_I,
		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_I)
	begin
		if rising_edge(CLK_I) then
			if RST_I = '1' then
				s	<= init;
			else
				s	<= sn;
			end if;
		end if;
	end process;	

	MRDY_O			<= fill_count_en;
	ADDR_O				<= fill_tag & fill_cache_index & req_word_index & "00";
	
	cpu_busy			<= cache_busy;
	cpu_dout			<= data_ram_data_rd;
	
	cache_entry_out		<= to_icache_entry(tag_ram_data_rd);
	tag_match			<= '1' when fill_tag = cache_entry_out.tag else '0';
	cache_miss			<= not (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_miss_inv		<= not (tag_match_inv and cache_entry_out_inv.valid);

	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 fill_cache_index;
	tag_ram_addr_rd		<= cpu_cache_index when was_miss = '0' else fill_cache_index;
	data_ram_addr_rd	<= (cpu_cache_index & cpu_word_index) when was_miss = '0' else (fill_cache_index & fill_word_index);
	data_ram_addr_wr	<= fill_cache_index & fill_word_index;
	data_ram_data_wr	<= DAT_I;
	data_ram_we			<= fill_count_en and ACK_I;

cache_state:
	process(s, cache_req, cache_miss, cache_miss_inv, flush_count_rdy, fill_count_rdy, request_count_rdy, fill_tag, SRDY_I, invalidate_req, invalidate_all)
	begin
		cache_busy 				<= cache_req;
		cache_ack 				<= '0';
		tag_ram_we				<= '0';
		flush_count_en			<= '0';
		flush_count_rst			<= '0';
		invalidate_en			<= '0';
		request_count_en		<= '0';
		fill_count_en			<= '0';
		CYC_O					<= '0';
		STB_O					<= '0';
		was_miss				<= '0';
		invalidate_ack			<= '0';

		cache_entry_in.tv_p		<= (others => '0');
		cache_entry_in.tag		<= fill_tag;
		cache_entry_in.valid	<= '0';
		sn	<= s;
		
		case s is
		when init =>
			sn <= ready;
		when ready =>
			if invalidate_req = '1' then
				sn 				<= invalidate;
				invalidate_en	<= '1';
			elsif cache_req = '1' then
				if cache_miss = '1' then
					sn						<= mem_request;
					CYC_O					<= '1';
				else
					cache_busy 		<= '0';
					cache_ack 		<= '1';
				end if;	
			end if;	
		when invalidate =>
			sn <= rd_cache;
			invalidate_en			<= '1';
			invalidate_ack			<= '1';
			if invalidate_all = '1' then
				sn					<= flush;
				flush_count_rst		<= '1';
			elsif cache_miss_inv = '0' then
				tag_ram_we				<= '1';
				cache_entry_in.valid	<= '0';
				cache_entry_in.tag		<= (others => '0');
				sn						<= rd_cache;
			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';
				sn					<= rd_cache;
			end if;
		when mem_request =>
			CYC_O		<= '1';
			if SRDY_I = '1' then
				sn <= mem_access;
			end if;	
		when mem_access =>
			request_count_en	<= '1';
			fill_count_en		<= '1';
			CYC_O				<= '1';
			STB_O				<= '1';
			if request_count_rdy = '1' then
				STB_O			<= '0';
				sn				<= mem_data;
			end if;	
		when mem_data =>
			CYC_O			<= '1';
			fill_count_en	<= '1';
			if fill_count_rdy = '1' then
				tag_ram_we				<= '1';
				cache_entry_in.valid	<= '1';
				sn						<= rd_cache;
			end if;	
		when rd_cache =>
			was_miss	<= '1';
			sn			<= ready;
		when others => 
			sn	<= ready;
		end case;

	end process;

flush_counter:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if ctrl.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_I)
	begin
		if rising_edge(CLK_I) then
			if request_count_en = '0' then
				request_count_rdy	<= '0';
				request_count		<= 2**word_index_width-1;
			else	
				if SRDY_I = '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_I)
	begin
		if rising_edge(CLK_I) then
			if fill_count_en = '0' then
				fill_count_rdy	<= '0';
				fill_count		<= 2**word_index_width-1;
			else	
				if ACK_I = '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;
