--------------------------------------------------------------------------
-- 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;
		eb			: in  STD_LOGIC;
		int			: in  unsigned(5 downto 0);
		nmi			: 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;
		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 imem_addr		: word_t;
	signal imem_addr_we		: STD_LOGIC;
	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 epc_reg_we		: STD_LOGIC;
	signal code_reg_we		: STD_LOGIC;
	signal ip_reg_we		: STD_LOGIC;
	signal bd				: STD_LOGIC;
	signal sw_int			: unsigned(1 downto 0);
	signal ip				: unsigned(7 downto 0);
	signal im				: unsigned(7 downto 0);
	signal status_save		: STD_LOGIC;
	signal status_rest		: STD_LOGIC;
	signal eflags			: exc_flags_t;
	signal EB_reg			: STD_LOGIC;
	signal icache_info		: unsigned(7 downto 0);
	signal dcache_info		: unsigned(7 downto 0);
	signal reg_rptr			: reg_ptr_t;
	signal reg_wptr			: reg_ptr_t;
	signal inject_exc		: STD_LOGIC;
	signal latch_vect_en	: STD_LOGIC;
	signal int_nmi			: STD_LOGIC;
	signal nmi_asserted		: std_logic;
	
	type cop_pipe_t is record
		opc			: opcode_t;
		RFE			: std_logic;
		CO			: std_logic;
		reg_ptr		: reg_ptr_t;
		rs			: reg_ptr_t;
		rd			: reg_ptr_t;
		rt			: 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_flags : unsigned) return STD_LOGIC is
	variable result : STD_LOGIC;
	begin
			result := '0';
			for i in ip_flags'range loop
				result := result or ip_flags(i);
			end loop;

			return result;
	end eval_int;

	type exc_state_t is (exc_idle, exc_pipe_flush, exc_commit, exc_inject, exc_enter, exc_leave, exc_left);
	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.opc					<= extract_opc(ir);
	cop_pipe_ID.rs					<= extract_rs(ir);
	cop_pipe_ID.rd					<= extract_rd(ir);
	cop_pipe_ID.rt					<= extract_rt(ir);
	cop_pipe_ID.func				<= extract_func(ir);
	cop_pipe_ID.CO					<= ir(25);
	cop_pipe_ID.re					<= cop_pipe_ID.cs when (cop_pipe_ID.opc = "111000" or (cop_pipe_ID.opc = "010000" and cop_pipe_ID.rs = "00000")) else '0';
	cop_pipe_ID.we					<= cop_pipe_ID.cs when (cop_pipe_ID.opc = "110000" or (cop_pipe_ID.opc = "010000" and cop_pipe_ID.rs = "00100")) else '0';
	cop_pipe_ID.RFE					<= cop_pipe_ID.cs when (cop_pipe_ID.CO = '1' and cop_pipe_ID.func = "010000") else '0';
	ctrl_out.EB						<= EB_reg xor (status(25) and status(1));
	ctrl_out.user_mode				<= status(1);
	ctrl_out.exc_inject				<= inject_exc or rst;
	cop_pipe_ID.reg_ptr				<= cop_pipe_ID.rd when cop_pipe_ID.opc = "010000" else cop_pipe_ID.rt;
	
	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		<= ctrl_in.events.int;

-- NMI/RST transition detector
proc_nmi_transition:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				int_nmi			<= '0';
				nmi_asserted	<= '0';
			elsif latch_vect_en = '1' then
				int_nmi	<= '0';
			elsif nmi_asserted = '1' then
				if nmi = '0' then
					nmi_asserted	<= '0';
					int_nmi			<= '1';
				end if;	
			elsif nmi = '1' then
				nmi_asserted	<= '1';
			end if;	
		end if;
	end process;


-- Main exception FSM
proc_exc_state:
	process(exc_state, ctrl_in, cop_pipe_ID)
	begin
	
		ctrl_out.exc_commit		<= '0';
		ctrl_out.exc_pending	<= '0';
		ctrl_out.exc_exit 		<= '0';
		epc_reg_we				<= '0';	
		code_reg_we				<= '0';	
		status_save				<= '0';
		status_rest				<= '0';
		inject_exc				<= '0';
		imem_addr_we			<= '0';
		latch_vect_en			<= '0';

		exc_staten	<= exc_state;
		
		case exc_state is
		
		when exc_idle =>
			if cop_pipe_ID.RFE = '1' then
				exc_staten			<= exc_leave;
				ctrl_out.exc_exit 	<= '1';
				ctrl_out.exc_commit	<= '1';
			elsif ctrl_in.exc_req = '1' then
				exc_staten			<= exc_pipe_flush;
				imem_addr_we		<= '1';
			end if;
			
		when exc_pipe_flush =>
			ctrl_out.exc_pending	<= '1';
			ctrl_out.exc_commit		<= '1';
			if ctrl_in.exc_ack = '1' then
				exc_staten			<= exc_commit;
			end if;
			
		when exc_commit =>
			latch_vect_en			<= '1';
			ctrl_out.exc_pending	<= '1';
			ctrl_out.exc_commit		<= '1';
			if ctrl_in.sdu.ID_stall = '0' then
				exc_staten			<= exc_inject;
			end if;

		when exc_inject =>
			ctrl_out.exc_pending	<= '1';
			ctrl_out.exc_commit		<= '1';
			code_reg_we				<= '1';
			status_save				<= '1';
			epc_reg_we				<= '1';
			inject_exc				<= '1';
			exc_staten				<= exc_enter;

		when exc_enter =>
			ctrl_out.exc_pending	<= '1';
			exc_staten				<= exc_idle;

		when exc_leave =>
			exc_staten				<= exc_left;
			status_rest				<= '1';
			ctrl_out.exc_exit 		<= '1';
			ctrl_out.exc_commit		<= '1';

		when exc_left =>
			exc_staten				<= exc_idle;

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

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

-- Endian switch sampled at hard reset
sample_endian:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				EB_reg <= eb;
			end if;	
		end if;
	end process;

-- Sample imem_addr for later use
exception_last_imem_addr:
	process(clk)
	begin
		if rising_edge(clk) then
			if imem_addr_we = '1' then
				imem_addr <= ctrl_in.imem_addr;
			end if;	
		end if;
	end process;

-- EPC reg and BD-bit write
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;

-- BadVaddr and exception code write
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	<= imem_addr;
				elsif eflags.IAdEK = '1'	then
					exc_code	<= "00100";
					BadVAddr	<= 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;

	ctrl_out.icache.inv_addr	<= test_reg;
	ctrl_out.dcache.inv_addr	<= test_reg;
	
-- Custom I/D-Cache operations:  invalidate all/line
cop_cache_op:
	process(clk)
	begin
		if rising_edge(clk) then
			ctrl_out.icache.inv_at	<= '0';
			ctrl_out.icache.inv_all	<= '0';
			ctrl_out.dcache.inv_at	<= '0';
			ctrl_out.dcache.inv_all	<= '0';
			if cop_pipe_ID.cs = '1' and cop_pipe_ID.CO = '1' then
				case cop_pipe_ID.func is
					when "100000" =>
						ctrl_out.icache.inv_all	<= '1';
					when "100001" =>
						ctrl_out.icache.inv_at	<= '1';
					when "100010" =>
						ctrl_out.dcache.inv_all	<= '1';
					when "100011" =>
						ctrl_out.dcache.inv_at	<= '1';

					when others => null;
				end case;
			end if;	
		end if;
	end process;

-- IP register write and signalling Int to pipeline
cop_ip_reg_write:
	process(clk)
	variable ip_v  : unsigned(7 downto 0);
	variable ipm_v : unsigned(7 downto 0);
	begin
		if rising_edge(clk) then
			if rst = '1' then
				sw_int	<= (others => '0');
			elsif ctrl_in.sdu.WB_nop = '0' then
				if ip_reg_we = '1' then
					sw_int	<= din(9 downto 8);
				end if;	
			end if;	
			ip_v			:= (int & sw_int);
			ipm_v			:= ip_v and im;
			ip				<= ip_v;
			ctrl_out.int	<= (eval_int(ipm_v) and status(0));
			ctrl_out.NMI	<= int_nmi;
		end if;
	end process;

-- Exception vector dispatch
cop_exc_vector:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				ctrl_out.exc_vec		<= X"BFC00000";
			elsif latch_vect_en = '1' then
				if ctrl_in.events.nmi = '1' then
					ctrl_out.exc_vec		<= X"BFC00000";
				else
					ctrl_out.exc_vec		<= X"80000080";
					if status(22) = '1' then
						ctrl_out.exc_vec	<= X"BFC00180";
					end if;
				end if;
			end if;
		end if;
	end process;

-- Cop pipeline
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';
			elsif ctrl_in.sdu.stall_all = '0' then
				cop_pipe_EX		<= cop_pipe_ID;
				if cop_pipe_ID.we = '1' then
					reg_wptr	<= cop_pipe_ID.reg_ptr;
				end if;
				if cop_pipe_ID.re = '1' then
					reg_rptr	<= cop_pipe_ID.reg_ptr;
				end if;
			end if;
		end if;
	end process;

-- COP Register read
cop_register_read:
	process(reg_rptr, BadVAddr, status, cause, epc, dcache_info, icache_info, test_reg)
	variable reg : word_t;
	begin
	
		case reg_rptr 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;

	end process;

-- COP Register write strobe generation
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';
			elsif ctrl_in.sdu.stall_all = '0' then
				test_reg_we	<= '0';
				stat_reg_we	<= '0';
				ip_reg_we	<= '0';
				if cop_pipe_EX.we = '1' then

					case reg_wptr is

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

					end case;

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

-- COP Test Register
cop_test_reg_write:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				test_reg	<= (others => '0');
			elsif ctrl_in.sdu.WB_nop = '0' then
				if test_reg_we = '1' then
					test_reg	<= din;
				end if;
			end if;
		end if;
	end process;

-- COP Status Register
cop_status_reg_write:
	process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				status	<= X"00600000";
			elsif status_save = '1' then
				if ctrl_in.events.nmi = '0' then
					status(5 downto 4)	<= status(3 downto 2);
					status(3 downto 2)	<= status(1 downto 0);
				else	
					status(22)	<= '1';
				end if;
				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 ctrl_in.sdu.WB_nop = '0' then
				if stat_reg_we = '1' then
					if status(1) = '0' then
						status(1)			<= din(1);	-- KUc
						status(2)			<= din(2);	-- IEp
						status(3)			<= din(3);	-- KUp
						status(4)			<= din(4);	-- IEo
						status(5)			<= din(5);	-- KUo
					end if;	
					status(0)				<= din(0);	-- IEc
					status(15 downto 8)		<= din(15 downto 8);	-- IM
					status(22)				<= din(22);				-- BEV
					status(25)				<= din(25);				-- RE
				end if;
			end if;	
		end if;
	end process;
	
--------------------------------------------------------------------------
end Behavioral;
