--------------------------------------------------------------------------
-- 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 dcache 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_we		: in  STD_LOGIC;
		cpu_be		: in  unsigned(3 downto 0);
		cpu_addr	: in  word_t;
		cpu_din		: in  word_t;
		cpu_dout	: out word_t;
		cpu_busy	: out STD_LOGIC
	);
END dcache;

ARCHITECTURE behavior OF dcache IS


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;
		re_a		: in  STD_LOGIC;
		re_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 dcache_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_dcache_entry(x : tag_ram_data_t) return dcache_entry_t is
	variable result : dcache_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_dcache_entry;

	function to_tag_ram_data(x : dcache_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_init, invalidate, flush, mem_request, mem_access, mem_data, rd_cache);
	signal s, sn : cache_state_t;
	
	signal	cache_req			: std_logic;
	signal	cache_ack			: std_logic;
	signal	cache_busy			: std_logic;
	signal	cache_hit			: std_logic;
	signal	tag_match			: std_logic;
	signal	cache_hit_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		: dcache_entry_t;
	signal	cache_entry_out		: dcache_entry_t;
	signal	cache_entry_out_inv	: dcache_entry_t;
	signal  cpu_data_ram_addr	: unsigned(lg2(cache_size)-1 downto 0);
	signal  cpu_data_ram_dout	: word_t;
	signal  cpu_data_reg		: word_t;
	signal  cpu_be_reg			: unsigned(3 downto 0);
	signal  cpu_we_reg			: std_logic;
	signal  ctrl_data_ram_addr	: unsigned(lg2(cache_size)-1 downto 0);
	signal	ctrl_data_ram_we	: unsigned(3 downto 0);
	signal	cpu_data_ram_we		: unsigned(3 downto 0);
	signal	cpu_we2				: std_logic;

	signal  tag_ram_addr_rd		: unsigned(cache_index_width-1 downto 0);
	signal  tag_ram_dout		: tag_ram_data_t;
	signal  tag_ram_dout_inv	: tag_ram_data_t;
	signal  tag_ram_addr_wr		: unsigned(cache_index_width-1 downto 0);
	signal  tag_ram_din			: 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_load_en		: std_logic;
	signal  flush_addr_preset	: unsigned(cache_index_width-1 downto 0);
	signal	flush_count_preset	: natural range 0 to 2**cache_index_width-1;
	signal  flush_addr			: unsigned(cache_index_width-1 downto 0);
	signal	flush_count			: natural range 0 to 2**cache_index_width-1;
	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	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	cpu_hit_we			: std_logic;
	signal	instant_raw			: std_logic;
	signal  hit_cache_index		: unsigned(cache_index_width-1 downto 0);

	signal  DAT_I_r				: word_t;
	signal  ACK_I_r				: 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

	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);

	cpu_hit_we	<= cpu_we2 and cache_hit;
	
data_in_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			ACK_I_r	<= ACK_I;
			if ACK_I = '1' then
				DAT_I_r <= DAT_I;
			end if;
		end if;
	end process;	
	
fill_address_register:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			if fill_count_en = '1' then
				if ACK_I_r = '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
				cpu_we_reg	<= '0';
				cache_req	<= '0';
			elsif cpu_en = '1' then
				if cache_busy = '0' then
					cpu_we2				<= cpu_we;
					cache_req			<= '1';
					cpu_data_reg		<= cpu_din;
					cpu_be_reg			<= cpu_be;
					cpu_we_reg			<= cpu_we;
					if cpu_we = '1' then
						hit_cache_index		<= cpu_cache_index;
					end if;
				end if;	
			elsif cache_ack = '1' then
				cache_req			<= '0';
				cpu_we2				<= '0';
			end if;
		end if;
	end process;	

instant_raw_logic:
	process(CLK_I)
	begin
		if rising_edge(CLK_I) then
			instant_raw <= '0';
			if cpu_word_index = fill_word_index and cpu_cache_index = hit_cache_index then
				instant_raw	<= cpu_hit_we and cpu_en and not cpu_we;
			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,
		re_a	=> '1',
		re_b	=> '1',
		we_a	=> tag_ram_we,
		we_b	=> '0',
		addr_a	=> tag_ram_addr_wr,
		addr_b	=> tag_ram_addr_rd,
		din_a	=> tag_ram_din,
		din_b	=> tag_ram_din,
		dout_a	=> tag_ram_dout_inv,
		dout_b	=> tag_ram_dout
	);

gen_data_ram:
	for i in 0 to 3 generate
	begin
	
	inst_data_ram : dpram_2w2r2c_ra
		GENERIC MAP 
		(
			addr_width	=> lg2(cache_size),
			data_width	=> word_t'length/4
		)
		PORT MAP 
		(
			clk_a	=> CLK_I,
			clk_b	=> CLK_I,
			re_a	=> '1',
			re_b	=> '1',
			we_a	=> cpu_data_ram_we(i),
			we_b	=> ctrl_data_ram_we(i),
			addr_a	=> ctrl_data_ram_addr,
			addr_b	=> cpu_data_ram_addr,
			din_a	=> cpu_data_reg(8*(i+1)-1 downto 8*i),
			din_b	=> DAT_I_r(8*(i+1)-1 downto 8*i),
			dout_a	=> open,
			dout_b	=> cpu_data_ram_dout(8*(i+1)-1 downto 8*i)
		);
	end generate;	
	
	
	
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;	

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				<= request_addr;
	
	cpu_busy			<= cache_busy;
	cpu_dout			<= cpu_data_ram_dout;
	
	tag_match			<= '1' when fill_tag = cache_entry_out.tag else '0';
	cache_hit			<= tag_match and cache_entry_out.valid;
	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;
	tag_ram_din			<= to_tag_ram_data(cache_entry_in);
	tag_ram_addr_wr		<= flush_addr when invalidate_en = '1' else fill_cache_index;
	tag_ram_addr_rd		<= cpu_cache_index when (was_miss = '0' and instant_raw = '0') else fill_cache_index;

	cache_entry_out		<= to_dcache_entry(tag_ram_dout);
	cache_entry_out_inv	<= to_dcache_entry(tag_ram_dout_inv);
	
	cpu_data_ram_addr	<= (cpu_cache_index & cpu_word_index) when (was_miss = '0' and instant_raw = '0' and fill_count_en = '0') else (fill_cache_index & fill_word_index);
	ctrl_data_ram_addr	<= fill_cache_index & fill_word_index;
	ctrl_data_ram_we	<= (others => fill_count_en and ACK_I_r);
	cpu_data_ram_we		<= cpu_be_reg when (cpu_hit_we = '1') else (others => '0');
	
cache_state:
	process(s, cache_req, instant_raw, cache_hit, cache_hit_inv, cache_index_inv, flush_count_rdy, fill_count_rdy, request_count_rdy, fill_tag, SRDY_I, cpu_we_reg, invalidate_req, invalidate_all)
	begin
		cache_busy 				<= cache_req;
		cache_ack 				<= '0';
		tag_ram_we				<= '0';
		request_count_en		<= '0';
		fill_count_en			<= '0';
		CYC_O					<= '0';
		STB_O					<= '0';
		was_miss				<= '0';

		invalidate_en			<= '0';
		invalidate_ack			<= '0';

		flush_load_en			<= '0';
		flush_count_en			<= '0';
		flush_addr_preset		<= (others=>'0'); -- all
		flush_count_preset		<= 2**cache_index_width-1;

		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_init;
				invalidate_en	<= '1';
				flush_load_en	<= '1';
				if invalidate_all = '1' then
					flush_addr_preset		<= (others=>'0'); -- all
					flush_count_preset		<= 2**cache_index_width-1;
				else
					flush_addr_preset		<= cache_index_inv; -- at
					flush_count_preset		<= 0;
				end if;	
			else	
				if cache_req = '1' then
					if cache_hit = '0' and cpu_we_reg = '0' then
						sn						<= mem_request;
						CYC_O					<= '1';
					else
						cache_busy 		<= instant_raw;
						cache_ack 		<= not instant_raw;
					end if;	
				end if;	
			end if;	

		when invalidate_init =>
			invalidate_en			<= '1';
			sn	<= invalidate;
		
		when invalidate =>
			sn	<= rd_cache;
			invalidate_en			<= '1';
			invalidate_ack			<= '1';
			if cache_hit_inv = '1' or invalidate_all = '1' then
				sn					<= flush;
			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 =>
			fill_count_en		<= '1';
			request_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 flush_load_en = '1' then
				flush_count_rdy	<= '0';
				flush_count		<= flush_count_preset;
				flush_addr		<= flush_addr_preset;
			elsif flush_count_en = '1' then
				if flush_count /= 0 then
					flush_count	<= flush_count - 1;
					flush_addr	<= flush_addr + 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_r = '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;
