--------------------------------------------------------------------------
-- Project: JIPS, a portable 32-bit RISC CPU written in VHDL
-- This file: The arithmetic logic unit
--
-- 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;

use work.mips_types.all;
use work.mips_instr.all;

entity cop is
	Generic
	(
		icache_size	: natural := 2048;	-- words
		icache_line	: natural := 8;		-- words
		dcache_size	: natural := 2048;	-- words
		dcache_line	: natural := 8		-- words
	);
	Port 
	(
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		ir_en		: in  STD_LOGIC;
		ir			: in  word_t;
		ctrl_in		: in  cop0_ctrl_in_t;
		ctrl_out	: out cop0_ctrl_out_t;
		reg_rd		: out STD_LOGIC;
		din			: in  word_t;
		dout		: out word_t
	);
end cop;

architecture Behavioral of cop is

	signal epc				: word_t;
	signal cause			: word_t;
	signal status			: word_t;
	signal BadVAddr			: word_t;
	signal exc_code			: unsigned(4 downto 0);
	signal test_reg			: word_t;
	signal test_reg_we		: STD_LOGIC;
	signal stat_reg_we		: STD_LOGIC;
	signal eflags_reg_we	: STD_LOGIC;
	signal epc_reg_we		: STD_LOGIC;
	signal code_reg_we		: STD_LOGIC;
	signal ip_reg_we		: STD_LOGIC;
	signal bd				: STD_LOGIC;
	signal ip				: unsigned(7 downto 0);
	signal im				: unsigned(7 downto 0);
	signal status_save		: STD_LOGIC;
	signal status_rest		: STD_LOGIC;
	signal exception		: STD_LOGIC;
	signal exception_end	: STD_LOGIC;
	signal eflags			: exc_flags_t;
	signal exc_enable		: STD_LOGIC;
	signal cop_EX_en		: STD_LOGIC;
	signal icache_info		: unsigned(7 downto 0);
	signal dcache_info		: unsigned(7 downto 0);
	
	type cop_pipe_t is record
		rs			: reg_ptr_t;
		rd			: reg_ptr_t;
		func		: func_t;
		we			: std_logic;
		re			: std_logic;
		cs			: std_logic;
	end record;
	
	signal cop_pipe_ID : cop_pipe_t;
	signal cop_pipe_EX : cop_pipe_t;
	
	function eval_int(ip : unsigned) return STD_LOGIC is
	variable result : STD_LOGIC;
	begin
			result := '0';
			for i in ip'range loop
				result := result or ip(i);
			end loop;

			return result;
	end eval_int;

	type exc_state_t is (exc_init, exc_idle, exc_commit_ID, exc_commit_EX, exc_commit_MEM);
	signal exc_state, exc_staten : exc_state_t;
	
--------------------------------------------------------------------------
begin

	icache_info 					<= "00" & to_unsigned(lg2(icache_line), 3) & to_unsigned(lg2(icache_size)-8, 3);
	dcache_info 					<= "00" & to_unsigned(lg2(dcache_line), 3) & to_unsigned(lg2(dcache_size)-8, 3);
	cop_pipe_ID.cs					<= ir_en;
	cop_pipe_ID.rs					<= extract_rs(ir);
	cop_pipe_ID.rd					<= extract_rd(ir);
	cop_pipe_ID.func				<= extract_func(ir);
	cop_pipe_ID.re					<= cop_pipe_ID.cs when cop_pipe_ID.rs = "00000" else '0';
	cop_pipe_ID.we					<= cop_pipe_ID.cs when cop_pipe_ID.rs = "00100" else '0';
	cop_EX_en						<= exc_enable and not status_save;
	
	ctrl_out.RE						<= status(25);
	ctrl_out.user_mode				<= status(1);
	ctrl_out.int					<= eflags.Int;

	reg_rd							<= cop_pipe_ID.cs and cop_pipe_ID.re after 1 ns;
	
	im				<= status(15 downto 8);
	cause			<= bd & (30 downto 16 => '0') & ip & '0' & exc_code & "00";

	eflags.Ov		<= ctrl_in.events.alu_ovf or ctrl_in.events.alu_uvf;
	eflags.DAdEL	<= ctrl_in.events.data_load_err;
	eflags.DAdES	<= ctrl_in.events.data_store_err;
	eflags.IAdEL	<= ctrl_in.events.inst_load_err;
	eflags.IAdEK	<= ctrl_in.events.inst_priv_addr and status(1);
	eflags.Sys		<= ctrl_in.events.syscall;
	eflags.Bp		<= ctrl_in.events.break;
	eflags.RI		<= ctrl_in.events.illegal;
	eflags.Int		<= eval_int(ip) and status(0);

	exception		<= eflags.Ov or eflags.Sys or eflags.Bp or eflags.RI or eflags.IAdEL or eflags.IAdEK or eflags.DAdEL or eflags.DAdES or eflags.Int after 1 ns;
	
exception_state:
	process(exc_state, ctrl_in, exception)
	begin
	
		exc_enable				<= '0';
		ctrl_out.exc_commit		<= '0';
		ctrl_out.exc_pending	<= '0';
		epc_reg_we				<= '0';	
		code_reg_we				<= '0';	
		status_save				<= '0';
		eflags_reg_we			<= '0';

		exc_staten	<= exc_state;
		
		case exc_state is
		
		when exc_init =>
			exc_staten <= exc_idle;
		
		when exc_idle =>
			exc_enable	<= '1';
			if exception = '1' then
				status_save			<= '1';
				eflags_reg_we		<= '1';
				code_reg_we			<= '1';
				exc_staten			<= exc_commit_ID;
			end if;
			
		when exc_commit_ID =>
			ctrl_out.exc_pending	<= '1';
			ctrl_out.exc_commit		<= '1';
			if ctrl_in.sdu.ID_stall = '0' then
				exc_staten				<= exc_commit_EX;
			end if;
			
		when exc_commit_EX =>
			ctrl_out.exc_pending	<= '1';
			ctrl_out.exc_commit		<= '1';
			if ctrl_in.sdu.EX_stall = '0' then
				exc_staten				<= exc_commit_MEM;
			end if;

		when exc_commit_MEM =>
			ctrl_out.exc_pending	<= '1';
			if ctrl_in.sdu.MEM_stall = '0' then
				epc_reg_we				<= '1';
				exc_staten				<= exc_idle;
			end if;

		when others =>
			exc_staten <= exc_idle;
		
		end case;
	end process;	

exception_state_next:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				exc_state <= exc_init;
			else
				exc_state <= exc_staten;
			end if;	
		end if;
	end process;

cop_exception_epc_write:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				bd			<= '0';
				epc	<= (others => '0');
			elsif epc_reg_we = '1' then
				bd			<= '0';
				epc			<= ctrl_in.epc_mem;
				if ctrl_in.bd_wb = '1' then
					bd			<= '1';
					epc			<= ctrl_in.epc_wb;
				end if;
			end if;
		end if;
	end process;

cop_exception_map:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				exc_code	<= (others => '0');
				BadVAddr	<= (others => '0');
			elsif code_reg_we = '1' then
				if eflags.Ov = '1'	then
					exc_code	<= "01100";
				elsif eflags.Sys = '1'	then
					exc_code	<= "01000";
				elsif eflags.Bp = '1'	then
					exc_code	<= "01001";
				elsif eflags.RI = '1'	then
					exc_code	<= "01010";
				elsif eflags.IAdEL = '1'	then
					exc_code	<= "00100";
					BadVAddr	<= ctrl_in.imem_addr;
				elsif eflags.IAdEK = '1'	then
					exc_code	<= "00100";
					BadVAddr	<= ctrl_in.imem_addr;
				elsif eflags.DAdEL = '1'	then
					exc_code	<= "00100";
					BadVAddr	<= ctrl_in.dmem_addr;
				elsif eflags.DAdES = '1'	then
					exc_code	<= "00101";
					BadVAddr	<= ctrl_in.dmem_addr;
				elsif eflags.Int = '1' then
					exc_code	<= "00000";
				end if;
			end if;
		end if;
	end process;

	status_rest	<= exception_end and ctrl_in.exc_left;

cop_status_restore:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				exception_end		<= '0';
				ctrl_out.exc_exit	<= '0';
			else
				if cop_pipe_ID.func = "10000" and exception_end = '0' then		-- RFE
					exception_end	<= cop_pipe_ID.cs;
					ctrl_out.exc_exit <= cop_pipe_ID.cs;
				elsif exception_end = '1' and ctrl_in.exc_left = '1' then
					exception_end		<= '0';
					ctrl_out.exc_exit	<= '0';
				end if;	
			end if;	
		end if;
	end process;

cop_ip_reg_write:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				ip(1 downto 0) <= (others => '0');
			elsif ip_reg_we = '1' then
				ip(1 downto 0) <= din(9 downto 8) and im(1 downto 0);
			end if;	
			ip(7 downto 2)	<= ctrl_in.events.Int and im(7 downto 2);
		end if;
	end process;

cop_exc_vector:
	process(clk)
	begin
		if rising_edge(clk) then
			ctrl_out.exc_vec		<= X"80000180";
			if status(22) = '1' then
				ctrl_out.exc_vec	<= X"BFC00180";
			end if;
		end if;
	end process;

cop_pipe:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				cop_pipe_EX.cs	<= '0';
				cop_pipe_EX.we	<= '0';
				cop_pipe_EX.re	<= '0';
			else
				if ctrl_in.sdu.EX_stall = '0' then
					cop_pipe_EX		<= cop_pipe_ID;
				end if;
			end if;
		end if;
	end process;

cop_register_read:
	process(clk)
	variable reg : word_t;
	begin
		if rising_edge(clk) and cop_pipe_ID.re = '1' and ctrl_in.sdu.ID_stall = '0' then
			case cop_pipe_ID.rd is
			when "01000" =>			-- BadVAddr
				reg	:= BadVAddr;

			when "01100" =>			-- Status
				reg	:= status;

			when "01101" =>			-- Cause
				reg	:= cause;

			when "01110" =>			-- EPC (Exception Program Counter)
				reg	:= epc;

			when "01111" =>			-- PRId (Processor Revision Register)
				reg	:= dcache_info & icache_info & X"02" & to_unsigned(REVISION ,8);

			when "11111" =>			-- test_reg
				reg	:= test_reg;

			when others =>
				reg	:= (others => '-');

			end case;	
			dout	<= reg after 1 ns;
		end if;
	end process;

cop_we_gen:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				test_reg_we	<= '0';
				stat_reg_we	<= '0';
				ip_reg_we	<= '0';
			else
				test_reg_we	<= '0';
				stat_reg_we	<= '0';
				ip_reg_we	<= '0';
				if (cop_EX_en and cop_pipe_EX.we) = '1' then

					case cop_pipe_EX.rd is

					when "01100" =>
						stat_reg_we	<= not status(1) or status(28);
					when "01101" =>			
						ip_reg_we	<= not status(1) or status(28);
					when "11111" =>
						test_reg_we	<= not status(1) or status(28);
					when others => null;

					end case;

				end if;	
			end if;	
		end if;	
	end process;

cop_test_reg_write:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				test_reg	<= (others => '0');
			else --if ce = '1' then
				if test_reg_we = '1' then
					test_reg	<= din;
				end if;
			end if;
		end if;
	end process;

cop_status_reg_write:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				status	<= X"00400000";
			else
				if status_save = '1' then
					status(5 downto 4)	<= status(3 downto 2);
					status(3 downto 2)	<= status(1 downto 0);
					status(1 downto 0)	<= "00";
				elsif status_rest = '1' then
					status(1 downto 0)	<= status(3 downto 2);
					status(3 downto 2)	<= status(5 downto 4);
				elsif stat_reg_we = '1' then
					status				<= din;
				end if;
			end if;
		end if;
	end process;
	
--------------------------------------------------------------------------
end Behavioral;
