--------------------------------------------------------------------------
-- 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 biu is
	Generic
	(
		icache_size	: natural := 2048;	-- words
		icache_line	: natural := 8;		-- words
		dcache_size	: natural := 2048;	-- words
		dcache_line	: natural := 8		-- words
	);
	Port
	(
		RST_I			: in  STD_LOGIC;
		CLK_I			: in  STD_LOGIC;
		ACK_I			: in  STD_LOGIC;
		SRDY_I			: in  STD_LOGIC;
		ADDR_O			: out unsigned(31 downto 0);
		DAT_I			: in  unsigned(31 downto 0);
		DAT_O			: out unsigned(31 downto 0);
		WE_O			: out STD_LOGIC;
		SEL_O			: out unsigned(3 downto 0);
		CYC_O			: out STD_LOGIC;
		STB_O			: out STD_LOGIC;
		MRDY_O			: out STD_LOGIC;
		cop0_ctrl_in	: in cop0_ctrl_out_t;
		cpu_clk			: in  STD_LOGIC;
		cpu_imem_err	: out STD_LOGIC;
		cpu_imem_rdy	: out STD_LOGIC;
		cpu_imem_en		: in  STD_LOGIC;
		cpu_imem_addr	: in  word_t;
		cpu_imem_din	: out word_t;
		cpu_dmem_err	: out STD_LOGIC;
		cpu_dmem_rdy	: out STD_LOGIC;
		cpu_dmem_en		: in  STD_LOGIC;
		cpu_dmem_we		: in  STD_LOGIC;
		cpu_dmem_be		: in  unsigned(3 downto 0);
		cpu_dmem_dout	: in  word_t;
		cpu_dmem_din	: out word_t;
		cpu_dmem_addr	: in  word_t
	);
end biu;

architecture behavior of biu is

	COMPONENT icache
	GENERIC
	(
		cache_size	: natural;	-- words
		line_size	: natural	-- 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 COMPONENT;

	COMPONENT dcache
	GENERIC
	(
		cache_size	: natural;	-- words
		line_size	: natural	-- 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 COMPONENT;

	type bus_state_t is (init, ready, icache_bus_access, dcache_bus_access, write_bus, read_bus, read_finish);
	
	signal s, sn 			: bus_state_t;
	signal bus_idle			: std_logic;
	signal busy				: std_logic;
	signal dmem_be			: unsigned(3 downto 0);
	signal dcache_dout		: word_t;
	signal dcache_mem_gnt	: std_logic;
	signal icache_mem_gnt 	: std_logic;
	signal dmem_mem_wr_gnt	: std_logic;
	signal dmem_mem_rd_gnt	: std_logic;
	signal dcache_busy		: std_logic;
	signal icache_busy 		: std_logic;
	signal CYC_O_icache		: std_logic;
	signal CYC_O_dcache		: std_logic;
	signal CYC_O_dmem_rd	: std_logic;
	signal CYC_O_dmem_wr	: std_logic;
	signal SRDY_I_icache	: std_logic;
	signal SRDY_I_dcache	: std_logic;
	signal MRDY_O_icache	: std_logic;
	signal MRDY_O_dcache	: std_logic;
	signal ADDR_O_icache	: word_t;
	signal ADDR_O_dcache	: word_t;
	signal ADDR_O_dmem_rd	: word_t;
	signal ADDR_O_dmem_wr	: word_t;
	signal STB_O_icache		: std_logic;
	signal STB_O_dcache		: std_logic;
	signal STB_O_dmem_rd	: std_logic;
	signal STB_O_dmem_wr	: std_logic;
	signal DAT_I_dmem_rd	: word_t;
	signal DAT_O_dmem_wr	: word_t;
	signal SEL_O_dmem_wr	: unsigned(3 downto 0);
	signal SEL_O_dmem_rd	: unsigned(3 downto 0);
	signal dcached			: std_logic;
	signal dcache_en2		: std_logic;
	signal dcache_en		: std_logic;
	signal uncached_access	: std_logic;
	signal ACK				: std_logic;
	signal DAT				: unsigned(31 downto 0);

	type timeout_cnt_t is range 0 to 1E5-1;	
	signal bus_timeout_cnt	: timeout_cnt_t;
	signal bus_timeout		: std_logic;
	
	signal bout_fifo_din	: unsigned(68 downto 0);
	signal bout_fifo_dout	: unsigned(68 downto 0);
	signal bout_fifo_re		: std_logic;
	signal bout_fifo_we		: std_logic;
	signal bout_fifo_full	: std_logic;
	signal bout_fifo_empty	: std_logic;
	signal bout_rdy			: std_logic;

	signal bin_fifo_din		: unsigned(31 downto 0);
	signal bin_fifo_dout	: unsigned(31 downto 0);
	signal bin_fifo_re		: std_logic;
	signal bin_fifo_we		: std_logic;
	signal bin_fifo_full	: std_logic;
	signal bin_fifo_empty	: std_logic;

	alias bout_fifo_addr_in is bout_fifo_din(31 downto 0);
	alias bout_fifo_data_in is bout_fifo_din(63 downto 32);
	alias bout_fifo_sel_in is bout_fifo_din(67 downto 64);
	alias bout_fifo_we_in is bout_fifo_din(68);
	alias bout_fifo_addr_out is bout_fifo_dout(31 downto 0);
	alias bout_fifo_data_out is bout_fifo_dout(63 downto 32);
	alias bout_fifo_sel_out is bout_fifo_dout(67 downto 64);
	alias bout_fifo_we_out is bout_fifo_dout(68);

	signal write_fifo_din	: unsigned(67 downto 0);
	signal write_fifo_dout	: unsigned(67 downto 0);
	signal write_fifo_re	: std_logic;
	signal write_fifo_we	: std_logic;
	signal write_fifo_full	: std_logic;
	signal write_fifo_empty	: std_logic;

	alias write_fifo_addr_in is write_fifo_din(31 downto 0);
	alias write_fifo_data_in is write_fifo_din(63 downto 32);
	alias write_fifo_sel_in is write_fifo_din(67 downto 64);
	alias write_fifo_addr_out is write_fifo_dout(31 downto 0);
	alias write_fifo_data_out is write_fifo_dout(63 downto 32);
	alias write_fifo_sel_out is write_fifo_dout(67 downto 64);

	signal read_cycle	: std_logic;

begin

read_cyc_register:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			if RST_I = '1' then
				read_cycle		<= '0';
			else
				read_cycle 		<= (dmem_mem_rd_gnt and CYC_O_dmem_rd) 
								or (dcache_mem_gnt and CYC_O_dcache) 
								or (icache_mem_gnt and CYC_O_icache);
			end if;
		end if;
	end process;	
					
	MRDY_O			<= not bin_fifo_full;
	CYC_O			<= not bout_fifo_empty or read_cycle;
	STB_O			<= not bout_fifo_empty;
	ADDR_O			<= bout_fifo_addr_out;
	DAT_O			<= bout_fifo_data_out;
	SEL_O			<= bout_fifo_sel_out;
	WE_O			<= bout_fifo_we_out;

	cpu_imem_rdy	<= not icache_busy after 4.5 ns;
	busy			<= CYC_O_dmem_rd or dcache_busy or (write_fifo_full);
	cpu_dmem_rdy	<= not busy after 4.5 ns;
	
inst_icache : icache
	GENERIC MAP
	(
		cache_size	=> icache_size,	-- words
		line_size	=> icache_line	-- words
	)	
	PORT MAP
	(
		CLK_I		=> cpu_clk,
		RST_I		=> RST_I,
		STB_O		=> STB_O_icache,
		CYC_O		=> CYC_O_icache,
		ADDR_O		=> ADDR_O_icache,
		MRDY_O		=> MRDY_O_icache,
		DAT_I		=> DAT,
		ACK_I		=> ACK,
		SRDY_I		=> SRDY_I_icache,
		ctrl		=> cop0_ctrl_in.icache,
		cpu_en		=> cpu_imem_en,
		cpu_addr	=> cpu_imem_addr,
		cpu_dout	=> cpu_imem_din,
		cpu_busy	=> icache_busy
	);

	SRDY_I_icache	<= bout_rdy and icache_mem_gnt;

inst_dcache : dcache
	GENERIC MAP
	(
		cache_size	=> dcache_size,	-- words
		line_size	=> dcache_line	-- words
	)
	PORT MAP
	(
		CLK_I		=> cpu_clk,
		RST_I		=> RST_I,
		CYC_O		=> CYC_O_dcache,
		STB_O		=> STB_O_dcache,
		ADDR_O		=> ADDR_O_dcache,
		MRDY_O		=> MRDY_O_dcache,
		DAT_I		=> DAT,
		ACK_I		=> ACK,
		SRDY_I		=> SRDY_I_dcache,
		ctrl		=> cop0_ctrl_in.dcache,
		cpu_en		=> dcache_en,
		cpu_we		=> cpu_dmem_we,
		cpu_be		=> cpu_dmem_be,
		cpu_addr	=> cpu_dmem_addr,
		cpu_din		=> cpu_dmem_dout,
		cpu_dout	=> dcache_dout,
		cpu_busy	=> dcache_busy
	);

	SRDY_I_dcache	<= bout_rdy and dcache_mem_gnt;

	dcached			<= '1' when cpu_dmem_addr(31 downto 29) /= "101" else '0';
	cpu_dmem_din	<= dcache_dout when uncached_access = '0' else DAT_I_dmem_rd;
	dcache_en		<= dcached and cpu_dmem_en and dcache_en2; -- or write_fifo_full);
	
-- Instantiate synchronous FIFO
inst_bin_fifo: entity work.fifo_async
	GENERIC MAP
	(
		addr_width			=> 4,
		data_width			=> 32,
		do_last_read_update	=> true
	)
	PORT MAP
	(
		rst			=>	RST_I,
		clk_w		=>	CLK_I,
		clk_r		=>	cpu_clk,
		we			=>	bin_fifo_we,
		re			=>	bin_fifo_re,
		fifo_full	=>	bin_fifo_full,
		fifo_empty	=>	bin_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  bin_fifo_din,
		data_r		=>  bin_fifo_dout
	);
	
	bin_fifo_din	<= DAT_I;
	DAT				<= bin_fifo_dout;
	ACK				<= not bin_fifo_empty;
	bin_fifo_we		<= ACK_I;
	bin_fifo_re		<= dmem_mem_rd_gnt or MRDY_O_icache or MRDY_O_dcache;
	
-- Instantiate synchronous FIFO
inst_bout_fifo: entity work.fifo_async
	GENERIC MAP
	(
		addr_width			=> 4,
		data_width			=> 69,
		do_last_read_update	=> false
	)
	PORT MAP
	(
		rst			=>	RST_I,
		clk_w		=>	cpu_clk,
		clk_r		=>	CLK_I,
		we			=>	bout_fifo_we,
		re			=>	bout_fifo_re,
		fifo_full	=>	bout_fifo_full,
		fifo_empty	=>	bout_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  bout_fifo_din,
		data_r		=>  bout_fifo_dout
	);
	bout_rdy			<= not bout_fifo_full;
	bout_fifo_re		<= SRDY_I and not bin_fifo_full;
	
	bout_fifo_we		<= STB_O_dmem_wr or STB_O_dmem_rd or STB_O_dcache or STB_O_icache;

	bout_fifo_data_in	<= DAT_O_dmem_wr when dmem_mem_wr_gnt = '1' else (others => '-');
	
	bout_fifo_addr_in	<= ADDR_O_dmem_wr when dmem_mem_wr_gnt = '1' else
						   ADDR_O_dmem_rd when dmem_mem_rd_gnt = '1' else
						   ADDR_O_dcache when dcache_mem_gnt = '1' else
						   ADDR_O_icache when icache_mem_gnt = '1' else (others => '-');

	bout_fifo_sel_in	<= SEL_O_dmem_wr when dmem_mem_wr_gnt = '1' else
						   SEL_O_dmem_rd when dmem_mem_rd_gnt = '1' else (others => '1');

	bout_fifo_we_in		<= '1' when dmem_mem_wr_gnt = '1' else '0';

-- Instantiate synchronous FIFO
inst_write_fifo: entity work.fifo_sync
	GENERIC MAP
	(
		addr_width			=> 4,
		data_width			=> 68,
		do_last_read_update	=> true
	)
	PORT MAP
	(
		rst			=>	RST_I,
		clk			=>	cpu_clk,
		we			=>	write_fifo_we,
		re			=>	write_fifo_re,
		fifo_full	=>	write_fifo_full,
		fifo_empty	=>	write_fifo_empty,
		fifo_afull	=>	open,
		fifo_aempty	=>	open,
		data_w		=>  write_fifo_din,
		data_r		=>  write_fifo_dout
	);

	CYC_O_dmem_wr	<= not write_fifo_empty;
	DAT_O_dmem_wr	<= write_fifo_data_out;
	ADDR_O_dmem_wr	<= write_fifo_addr_out;
	SEL_O_dmem_wr	<= write_fifo_sel_out;

	write_fifo_data_in	<= cpu_dmem_dout;
	write_fifo_addr_in	<= cpu_dmem_addr;
	write_fifo_sel_in	<= cpu_dmem_be;
	write_fifo_re		<= STB_O_dmem_wr;
	write_fifo_we		<= cpu_dmem_en and not busy and cpu_dmem_we;
	
dmem_rd_flags:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			uncached_access	<= '0';
			if RST_I = '1' then
				CYC_O_dmem_rd		<= '0';
				dcache_en2			<= '1';
			else	
				if ACK = '1' and dmem_mem_rd_gnt = '1' then
					uncached_access		<= '1';
					CYC_O_dmem_rd		<= '0';
					dcache_en2			<= '1';
				end if;	
				if cpu_dmem_en = '1' and busy = '0' and cpu_dmem_we = '0' then
					if dcached = '0' then
						CYC_O_dmem_rd	<= '1';
						dcache_en2		<= '0';
					end if;	
				end if;
			end if;
		end if;
	end process;	
	
dmem_rd_data:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			if RST_I = '1' then
				DAT_I_dmem_rd		<= (others => '0');
			elsif ACK = '1' and CYC_O_dmem_rd = '1' then
				DAT_I_dmem_rd		<= DAT;
			end if;
		end if;
	end process;	
	
dmem_rd_regs:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			if cpu_dmem_en = '1' and busy = '0' then
				ADDR_O_dmem_rd		<= cpu_dmem_addr;
				SEL_O_dmem_rd		<= cpu_dmem_be;
			end if;
		end if;
	end process;	
	
bus_state_next:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			if RST_I = '1' then
				s	<= init;
			else
				s	<= sn;
			end if;
		end if;
	end process;	
	
bus_state:
	process(s, CYC_O_icache, CYC_O_dcache, CYC_O_dmem_wr, CYC_O_dmem_rd, bout_rdy, ACK)
	begin
		
		icache_mem_gnt		<= '0';
		dcache_mem_gnt		<= '0';
		dmem_mem_rd_gnt		<= '0';	
		dmem_mem_wr_gnt		<= '0';	
		STB_O_dmem_rd		<= '0';
		STB_O_dmem_wr		<= '0';
		bus_idle			<= '0';
		sn					<= s;
		case s is
		when init =>
			sn		<= ready;
		when ready =>
			bus_idle	<= '1';
			if CYC_O_dmem_wr = '1' then
				sn			<= write_bus;
			elsif CYC_O_dmem_rd = '1' then
				sn			<= read_bus;
			elsif CYC_O_icache = '1' then
				sn			<= icache_bus_access;
			elsif CYC_O_dcache = '1' then
				sn			<= dcache_bus_access;
			end if;
		when icache_bus_access =>
			icache_mem_gnt	<= '1';
			if CYC_O_icache = '0' then
				sn			<= ready;
			end if;	
		when dcache_bus_access =>
			dcache_mem_gnt	<= '1';
			if CYC_O_dcache = '0' then
				sn			<= ready;
			end if;	
		when write_bus =>
			dmem_mem_wr_gnt	<= '1';	
			if CYC_O_dmem_wr = '1' then
				if bout_rdy = '1' then
					STB_O_dmem_wr	<= '1';
				end if;
			else
				sn			<= ready;
			end if;	
		when read_bus =>
			dmem_mem_rd_gnt	<= '1';	
			if bout_rdy = '1' then
				STB_O_dmem_rd	<= '1';
				sn				<= read_finish;
			end if;	
		when read_finish =>
			dmem_mem_rd_gnt	<= '1';	
			if ACK = '1' then
				sn			<= ready;
			end if;
		when others =>
			sn	<= ready;
		end case;		
				
	end process;	

bus_timeout_counter:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			if bus_idle = '0' then
				if bus_timeout_cnt /= 0 then
					bus_timeout_cnt	<= bus_timeout_cnt - 1;
				else
					bus_timeout	<= '1';
				end if;	
			else
				bus_timeout_cnt	<= timeout_cnt_t'high;
				bus_timeout		<= '0';
			end if;
		end if;
	end process;	

bus_err:
	process(cpu_clk)
	begin
		if rising_edge(cpu_clk) then
			if RST_I = '1' then
				cpu_imem_err	<= '0';
				cpu_dmem_err	<= '0';
			elsif bus_timeout = '1' then
				cpu_imem_err	<= icache_mem_gnt;
				cpu_dmem_err	<= dcache_mem_gnt or dmem_mem_wr_gnt or dmem_mem_rd_gnt;
			end if;
		end if;
	end process;	

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