--------------------------------------------------------------------------
-- Project: JIPS, a portable 32-bit RISC CPU written in VHDL
-- This file: The pipeline
--
-- 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;
use work.mips_instr.all;

entity pipeline is
	Port
	(
		rst			: in  STD_LOGIC;
		clk			: in  STD_LOGIC;
		ce			: in  STD_LOGIC;
		imem_err	: in  STD_LOGIC;
		imem_rdy	: in  STD_LOGIC;
		imem_en		: out STD_LOGIC;
		imem_addr	: out word_t;
		imem_data	: in  word_t;
		dmem_err	: in  STD_LOGIC;
		dmem_rdy	: in  STD_LOGIC;
		dmem_en		: out STD_LOGIC;
		dmem_we		: out STD_LOGIC;
		dmem_be		: out unsigned(3 downto 0);
		dmem_addr	: out word_t;
		dmem_din	: in  word_t;
		dmem_dout	: out word_t;
		cop_ir		: out word_t;
		cop_ir_en	: out STD_LOGIC;
		cop_din		: in  word_t;
		cop_dout	: out word_t;
		c0_ctrl_out	: out cop0_ctrl_in_t;
		c0_ctrl_in	: in  cop0_ctrl_out_t
		
	);
end pipeline;

architecture Behavioral of pipeline is

--------------------------------------------------------------------------
	COMPONENT reg_dual is
	Generic
	(
		addr_width : integer := 3;
		data_width : integer := 8
	);
	Port 
	(
		clk_w		: in  STD_LOGIC;
		en			: in  STD_LOGIC;
		we			: in  STD_LOGIC;
		wptr		: in  unsigned (addr_width-1 downto 0);
		din			: in  unsigned (data_width-1 downto 0);
		rptr_a		: in  unsigned (addr_width-1 downto 0);
		rptr_b		: in  unsigned (addr_width-1 downto 0);
		dout_a		: out unsigned (data_width-1 downto 0);
		dout_b		: out unsigned (data_width-1 downto 0)
	);
	END COMPONENT;

--------------------------------------------------------------------------
	COMPONENT shifter is
	Generic 
	(
		data_width : integer
	);
	Port 
	(
		shift_ctrl	: in  shift_ctrl_t;
		din			: in  unsigned (data_width-1 downto 0);
		dout		: out unsigned (data_width-1 downto 0)
	);
	END COMPONENT;
	
--------------------------------------------------------------------------
	COMPONENT alu is
	Generic 
	(
		data_width : integer
	);
	Port 
	(
		op1_in		: in  unsigned (data_width-1 downto 0);
		op2_in		: in  unsigned (data_width-1 downto 0);
		op2_shifted	: in  unsigned (data_width-1 downto 0);
		ctrl		: in  alu_ctrl_t;
		result		: out unsigned (data_width-1 downto 0);
		flags		: out alu_flags_t
	);
	END COMPONENT;

--------------------------------------------------------------------------
	COMPONENT bcu is
	Generic 
	(
		data_width : integer
	);
	Port 
	(
		op1_in		: in  unsigned (data_width-1 downto 0);
		op2_in		: in  unsigned (data_width-1 downto 0);
		flags		: out bcu_flags_t
	);
	END COMPONENT;
	
--------------------------------------------------------------------------
	COMPONENT muldiv is
	Port 
	(
		rst			: in  std_logic;
		clk			: in  std_logic;
		hilo_we		: in  std_logic;
		din_hi		: in  word_t;
		din_lo		: in  word_t;
		mul_divn	: in  std_logic;
		start		: in  std_logic;
		s_un		: in  std_logic;
		hilo_sel	: in  std_logic;
		busy		: out std_logic;
		dout		: out word_t
	);
	END COMPONENT;

--------------------------------------------------------------------------
	signal ID_stage			: ID_t;
	signal EX_stage			: EX_t;
	signal MEM_stage		: MEM_t;
	signal WB_stage			: WB_t;
	signal clk_2, clk_1		: STD_LOGIC;
	signal hdu				: hdu_t;
	signal sdu				: sdu_t;
	signal reg_a			: word_t;
	signal reg_b			: word_t;
	signal branch_ce		: STD_LOGIC;
	signal cpu_run			: STD_LOGIC;
	signal alu_result		: word_t;
	signal mul_result		: word_t;
	signal mul_busy			: STD_LOGIC;
	signal EX_events_instr	: event_t;
	signal EX_events_alu	: event_t;
	signal EX_events_mem	: event_t;
	signal EX_events		: event_t;
	signal MEM_events		: event_t;
	signal bcu_op_a			: word_t;
	signal bcu_op_b			: word_t;
	signal bcu_flags		: bcu_flags_t;
	signal vaddr			: word_t;
	signal dmem_src			: word_t;
	signal stage_rst		: unsigned(3 downto 0);
	signal pipe_rst			: STD_LOGIC;
	
	signal pc : pc_t;
	
--------------------------------------------------------------------------
begin
	
	clk_1					<= clk;
	clk_2					<= not clk;
	cpu_run					<= ce;
	
	-- Stall Detection Unit ---------------------------------------------------
	sdu.ID_nop				<= sdu.imem_dep or c0_ctrl_in.exc_pending or ID_stage.exc or EX_stage.exc or MEM_stage.exc;
	sdu.EX_nop				<= sdu.mul_dep or ID_stage.nop or ID_stage.exc;
	sdu.MEM_nop				<= EX_stage.nop or EX_stage.exc;
	sdu.WB_nop				<= sdu.dmem_dep or MEM_stage.nop;

	sdu.stall_all			<= sdu.dmem_dep;
	sdu.ID_stall			<= sdu.stall_all or sdu.imem_dep or sdu.mul_dep or c0_ctrl_in.exc_exit;
	sdu.EX_stall			<= sdu.stall_all;
	sdu.MEM_stall			<= sdu.stall_all;
	sdu.WB_stall			<= '0';

	sdu.mul_dep				<= ID_stage.ctrl.mul_access and (EX_stage.ctrl.mul_start or mul_busy);
	sdu.imem_dep			<= not imem_rdy;
	sdu.dmem_dep			<= not dmem_rdy and MEM_stage.ctrl.dmem_en;
	---------------------------------------------------------------------------
	imem_en 				<= cpu_run and not (sdu.mul_dep or sdu.dmem_dep or c0_ctrl_in.exc_commit);

--------------------------------------------------------------------------
-- Coprocessor assignments
--------------------------------------------------------------------------
	c0_ctrl_out.bd_wb		<= WB_stage.bd;
	c0_ctrl_out.epc_mem		<= MEM_stage.pcn;
	c0_ctrl_out.epc_wb		<= MEM_stage.epc;
	c0_ctrl_out.imem_addr	<= EX_stage.epc;
	c0_ctrl_out.dmem_addr	<= MEM_stage.va;
	c0_ctrl_out.sdu			<= sdu;
	c0_ctrl_out.events		<= WB_stage.events;
	c0_ctrl_out.exc_req		<= MEM_stage.exc;
	c0_ctrl_out.exc_ack		<= WB_stage.exc;
	cop_ir					<= ID_stage.IR;
	cop_ir_en				<= ID_stage.ctrl.cop_instr_en;
	cop_dout				<= dmem_din when MEM_stage.ctrl.dmem_en = '1' else MEM_stage.ex_result;

--------------------------------------------------------------------------
-- Muldiv
--------------------------------------------------------------------------
inst_muldiv: muldiv
	PORT MAP
	(
		rst			=> rst,
		clk			=> clk,
		hilo_we		=> EX_stage.ctrl.mul_hilo_we,
		din_hi		=> EX_stage.reg_a,
		din_lo		=> EX_stage.reg_b,
		mul_divn	=> EX_stage.ctrl.mul_mul_divn,
		start		=> EX_stage.ctrl.mul_start,
		s_un		=> EX_stage.ctrl.mul_s_un,
		hilo_sel	=> EX_stage.ctrl.mul_hilo_sel,
		busy		=> mul_busy,
		dout		=> mul_result
	);

--------------------------------------------------------------------------
-- IF stage
--------------------------------------------------------------------------
	imem_addr		<= pc.curr;

	pipe_rst		<= stage_rst(0);
	
proc_stage_reset:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if rst = '1' then
				stage_rst <= (others => '1');
			else
				stage_rst <= stage_rst(stage_rst'left-1 downto 0) & '0';
			end if;	
		end if;	
	end process;

proc_stage_pc:
	process(pc)
	begin
		if pc.branch_take = '1' then
			pc.curr	<= pc.pc_branch after 2 ns;
		else
			pc.curr	<= pc.nxt after 2 ns;
		end if;	
	end process;

proc_stage_pc_branch:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if sdu.ID_stall = '0' then
				pc.pc_branch	<= pc.curr + ID_stage.bimm18;
			end if;	
		end if;	
	end process;

proc_stage_branch_ce:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if pipe_rst = '1' then
				branch_ce	<= '1';
			else
				branch_ce	<= c0_ctrl_in.exc_pending;
				if sdu.ID_stall = '0' and c0_ctrl_in.exc_inject = '0' then
					branch_ce	<= '1';
				end if;
			end if;
		end if;	
	end process;

proc_stage_pc_next:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if c0_ctrl_in.exc_inject = '1' then
				pc.nxt	<= c0_ctrl_in.exc_vec;
			elsif sdu.ID_stall = '0' then
				pc.last	<= pc.curr;
				if ID_stage.ctrl.jump = '1' then
					pc.nxt	<= ID_stage.jimm32;
				elsif ID_stage.ctrl.jump_long = '1' then
					pc.nxt	<= ID_stage.reg_a;
				else
					pc.nxt	<= pc.curr + 4;
				end if;
			end if;
		end if;	
	end process;

proc_stage_branch:
	process(clk_2)
	begin
		if rising_edge(clk_2) and branch_ce = '1' then
			pc.branch_take	<= '0';
			if EX_stage.ctrl.branch = '1' then

				case EX_stage.ctrl.bc_src is

				when bc_eq_ne =>
					pc.branch_take <= EX_stage.ctrl.bc_not xor bcu_flags.eq;

				when bc_lez_gtz =>
					pc.branch_take <= EX_stage.ctrl.bc_not xor (bcu_flags.eq or bcu_flags.ltz);

				when bc_ltz_gez =>
					pc.branch_take <= EX_stage.ctrl.bc_not xor bcu_flags.ltz;

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

--------------------------------------------------------------------------
-- ID stage
--------------------------------------------------------------------------
	ID_stage.IR					<= to_01(imem_data);
	ID_stage.pcn				<= pc.curr;
	ID_stage.op					<= decode_op(ID_stage.IR);
	ID_stage.jimm32				<= extract_jimm32(ID_stage.IR, ID_stage.pcn);
	ID_stage.bimm18				<= extract_bimm18(ID_stage.IR, ID_stage.pcn);
	ID_stage.shamt				<= extract_shamt(ID_stage.IR);
	ID_stage.reg_a_rptr			<= extract_rs(ID_stage.IR);
	ID_stage.reg_b_rptr			<= extract_rt(ID_stage.IR);
	ID_stage.ctrl				<= opcode_ctrl_lines(ID_stage.IR) when sdu.ID_nop = '0' else ctrl_lines_default;
	ID_stage.reg_write			<= ID_stage.ctrl.reg_write;
	ID_stage.epc				<= pc.last;
	ID_stage.exc				<= event_is_active(ID_stage.events); -- Todo: works
	ID_stage.nop				<= sdu.ID_nop;
	
proc_ID_except:
	process(ID_stage, c0_ctrl_in, pc)
	begin	
		ID_stage.events					<= events_clr;
		ID_stage.events.inst_load_err	<= not c0_ctrl_in.exc_pending and (pc.nxt(1) or pc.nxt(0));
		ID_stage.events.inst_priv_addr	<= not c0_ctrl_in.exc_pending and (pc.nxt(word_t'left) and c0_ctrl_in.user_mode);
	end process;
	
proc_stage_hdu:
	process(ID_stage, EX_stage, MEM_stage, WB_stage)
	variable read_a, read_b : boolean;
	variable raw_a_EX, raw_a_MEM, raw_a_WB : boolean;
	variable raw_b_EX, raw_b_MEM, raw_b_WB : boolean;
	variable reg_ptr_a : reg_ptr_t;
	variable reg_ptr_b : reg_ptr_t;
	begin
		
		reg_ptr_a			:= ID_stage.reg_a_rptr;
		reg_ptr_b			:= ID_stage.reg_b_rptr;
		raw_a_EX			:= reg_ptr_a = EX_stage.reg_wptr and EX_stage.wreg_we = '1';
		raw_a_MEM			:= reg_ptr_a = MEM_stage.reg_wptr and MEM_stage.wreg_we = '1';
		raw_a_WB			:= reg_ptr_a = WB_stage.reg_wptr and WB_stage.wreg_we = '1';
		raw_b_EX			:= reg_ptr_b = EX_stage.reg_wptr and EX_stage.wreg_we = '1';
		raw_b_MEM			:= reg_ptr_b = MEM_stage.reg_wptr and MEM_stage.wreg_we = '1';
		raw_b_WB			:= reg_ptr_b = WB_stage.reg_wptr and WB_stage.wreg_we = '1';

		hdu.alu_fwd_a_ex	<= raw_a_EX after 1 ns;
		hdu.alu_fwd_a_mem	<= raw_a_MEM after 1 ns;
		hdu.alu_fwd_a_wb	<= raw_a_WB after 1 ns;
		hdu.alu_fwd_b_ex	<= raw_b_EX after 1 ns;
		hdu.alu_fwd_b_mem	<= raw_b_MEM after 1 ns;
		hdu.alu_fwd_b_wb	<= raw_b_WB after 1 ns;

	end process;
	
proc_stage_fwd_a:
	process(reg_a, hdu, EX_stage, MEM_stage, WB_stage)
	variable data : word_t;
	begin
		data		:= reg_a;
-- 		if hdu.alu_fwd_a_ex then
-- 			data	:= EX_stage.result;
-- 		elsif hdu.alu_fwd_a_mem then
-- 			data 	:= MEM_stage.data;
-- 		elsif hdu.alu_fwd_a_wb then
-- 			data 	:= WB_stage.data;
-- 		end if;
		
		for i in 0 to 31 loop
			if data(i) = '1' then
				ID_stage.reg_a(i)	<= '1' after 2 ns;
			else 
				ID_stage.reg_a(i)	<= '0' after 2 ns;
			end if;
		end loop;

	end process;

proc_stage_fwd_b:
	process(reg_b, hdu, EX_stage, MEM_stage, WB_stage)
	variable data : word_t;
	begin
		data		:= reg_b;
-- 		if hdu.alu_fwd_b_ex then
-- 			data	:= EX_stage.result;
-- 		elsif hdu.alu_fwd_b_mem then
-- 			data	:= MEM_stage.data;
-- 		elsif hdu.alu_fwd_b_wb then
-- 			data	:= WB_stage.data;
-- 		end if;

		for i in 0 to 31 loop
			if data(i) = '1' then
				ID_stage.reg_b(i)	<= '1' after 2 ns;
			else 
				ID_stage.reg_b(i)	<= '0' after 2 ns;
			end if;
		end loop;

	end process;
	
proc_imm_mux:
	process(ID_stage)
	variable data : word_t;
	begin
		data := extract_uimm16(ID_stage.IR);

		case ID_stage.ctrl.imm_src is

		when src_imm32 =>
			data := extract_simm32(ID_stage.IR);

		when src_imm16 =>
			data := extract_uimm16(ID_stage.IR);

		when src_imm16_high =>
			data := ID_stage.IR(word_t'length/2-1 downto 0) & (word_t'length/2-1 downto 0 => '0');
			
		when others => null;

		end case;

		ID_stage.imm <= data after 2 ns;

	end process;

--------------------------------------------------------------------------
inst_reg_dual: reg_dual
	GENERIC MAP
	(
		addr_width	=> reg_ptr_t'length,
		data_width	=> word_t'length
	)
	PORT MAP
	(
		clk_w		=> clk_1,
		we			=> WB_stage.wreg_we,
		en			=> '1',
		wptr		=> WB_stage.reg_wptr,
		din			=> WB_stage.data,
		rptr_a		=> ID_stage.reg_a_rptr,
		rptr_b		=> ID_stage.reg_b_rptr,
		dout_a		=> reg_a,
		dout_b		=> reg_b
	);

--------------------------------------------------------------------------
-- EX stage
--------------------------------------------------------------------------
	EX_stage.reg_a_rptr		<= extract_rs(EX_stage.IR);
	EX_stage.reg_b_rptr		<= extract_rt(EX_stage.IR);
	EX_stage.result			<= alu_result when EX_stage.ctrl.mul_access = '0' else mul_result;
	
proc_stage_ID_EX_1:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if stage_rst(1) = '1' then
				EX_stage.op						<= NOP;
				EX_stage.IR						<= (others => '0');
				EX_stage.ctrl					<= ctrl_lines_default;
				EX_stage.reg_write				<= '0';
				EX_stage.epc					<= (others => '0');
				EX_stage.pcn					<= (others => '0');
				EX_stage.events_in				<= events_clr;
			elsif sdu.EX_stall = '0' then
				EX_stage.reg_a			<= ID_stage.reg_a;
				EX_stage.reg_b			<= ID_stage.reg_b;
				EX_stage.events_in		<= ID_stage.events;
				EX_stage.op				<= ID_stage.op;
				EX_stage.ctrl			<= ID_stage.ctrl;
				EX_stage.reg_write		<= ID_stage.reg_write;
				EX_stage.nop			<= sdu.EX_nop;
				EX_stage.IR				<= ID_stage.IR;
				EX_stage.pcn			<= ID_stage.pcn;
				EX_stage.epc			<= ID_stage.epc;
				if sdu.EX_nop = '1' then
					EX_stage.op				<= NOP;
					EX_stage.IR				<= (others => '0');
					EX_stage.ctrl			<= ctrl_lines_default;
					EX_stage.reg_write		<= '0';
				end if;
			end if;	
		end if;
	end process;

proc_stage_EX_instr_except:
	process(EX_stage)
	begin
		EX_events_instr					<= events_clr;
		EX_events_instr.illegal			<= EX_stage.ctrl.exc_illegal;
		EX_events_instr.break			<= EX_stage.ctrl.exc_break;
		EX_events_instr.syscall			<= EX_stage.ctrl.exc_syscall;
	end process;

proc_stage_EX_alu_except:
	process(EX_stage)
	begin
		EX_events_alu	<= events_clr;
		if EX_stage.ctrl.alu_exc_en = '1' then
			if EX_stage.alu_flags.ovf = '1' then
				EX_events_alu.alu_ovf	<= '1';
			end if;	
			if EX_stage.alu_flags.uvf = '1' then
				EX_events_alu.alu_uvf	<= '1';
			end if;	
		end if;	
	end process;

	vaddr			<= ID_stage.reg_a + extract_simm32(ID_stage.IR);
	
proc_stage_EX_mem_except:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if pipe_rst = '1' then
				dmem_en		<= '0';
			elsif sdu.EX_stall = '0' then
				EX_events_mem	<= events_clr;
				dmem_en		<= '0';
				if ID_stage.ctrl.dmem_en = '1' then
					dmem_en		<= '1';
					if ID_stage.ctrl.except_en = '1' then
						if ID_stage.ctrl.word2_en = '1' then
							if vaddr(0) = '1' then
								EX_events_mem.data_load_err		<= not ID_stage.ctrl.dmem_we;
								EX_events_mem.data_store_err	<= ID_stage.ctrl.dmem_we;
								dmem_en							<= '0';
							end if;
						elsif vaddr(1 downto 0) /= "00" then
							EX_events_mem.data_load_err		<= not ID_stage.ctrl.dmem_we;
							EX_events_mem.data_store_err	<= ID_stage.ctrl.dmem_we;
							dmem_en							<= '0';
						end if;	
					end if;
				end if;
			end if;
		end if;
	end process;

proc_stage_DMEM_ADDR:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if sdu.EX_stall = '0' then
				EX_stage.va		<= vaddr;
				if c0_ctrl_in.EB = '1' then
					if ID_stage.ctrl.word2_en = '0' then
						EX_stage.pa_off	<= not vaddr(1 downto 0);
					else	
						EX_stage.pa_off	<= not vaddr(1) & vaddr(0);
					end if;
				else	
					EX_stage.pa_off	<= vaddr(1 downto 0);
				end if;
			end if;
		end if;
	end process;
	dmem_src	<= cop_din when EX_stage.ctrl.cop_instr_en = '1' else EX_stage.reg_b;
	dmem_be		<= store_be(EX_stage.pa_off, EX_stage.ctrl.dmem_en, EX_stage.ctrl.word2_en, EX_stage.ctrl.word4_en, EX_stage.ctrl.align_left, EX_stage.ctrl.shift_byp) after 1 ns;
	dmem_we		<= EX_stage.ctrl.dmem_we;
	dmem_dout	<= store_shift(dmem_src, EX_stage.pa_off, EX_stage.ctrl.shift_offset, EX_stage.ctrl.shift_byp) after 1ns;
	dmem_addr	<= EX_stage.va;
	EX_stage.events			<= EX_events_instr or EX_events_mem or EX_events_alu or EX_stage.events_in;
	EX_stage.exc			<= event_is_active(EX_stage.events);
	
--------------------------------------------------------------------------
proc_wptr_mux:
	process(EX_stage)
	variable opclass : opcode_t;
	variable reg_wptr : reg_ptr_t;
	begin
	
		opclass := extract_opc(EX_stage.IR);
		case opclass is
		when "000000" =>
			reg_wptr					:= extract_rd(EX_stage.IR);
		when others =>
			reg_wptr					:= extract_rt(EX_stage.IR);
		end case;
		
		EX_stage.wreg_we <= EX_stage.reg_write after 1 ns;
		if reg_wptr = "00000" then
			EX_stage.wreg_we <= '0' after 1 ns;
		end if;	

		EX_stage.reg_wptr	<= reg_wptr after 1 ns;
		case EX_stage.ctrl.wptr_srcsel is
			when wptr_src_imm =>
				EX_stage.reg_wptr	<= reg_wptr after 1 ns;

			when wptr_src_const =>
				EX_stage.reg_wptr	<= to_unsigned(31, reg_ptr_t'length) after 1 ns;
				EX_stage.wreg_we <= '1' after 1 ns;

			when others => null;
		end case;

	end process;

--------------------------------------------------------------------------
proc_stage_bcu_op:
	process(clk_1)
	begin
		if rising_edge(clk_1) and sdu.EX_stall = '0' then
			bcu_op_a		<= ID_stage.reg_a;
			bcu_op_b		<= ID_stage.reg_b;
		end if;
	end process;

EX_stage.alu_op1 <= EX_stage.reg_a;

alu_op2_mux:
	process(clk_1)
	variable data : word_t;
	begin

		if rising_edge(clk_1) and sdu.EX_stall = '0' then
			data := ID_stage.reg_b;

			case ID_stage.ctrl.alu.op2_src is

			when alu_src_reg =>
				data := ID_stage.reg_b;

			when alu_src_imm =>
				data := ID_stage.imm;

			when others => null;

			end case;

			EX_stage.alu_op2 <= data;
		end if;

	end process;

shifter_sa_mux:
	process(clk_1)
	variable data 		: shamt_t;
	variable data_inv 	: shamt_t;

	begin
		if rising_edge(clk_1) and sdu.EX_stall = '0' then
			data 		:= ID_stage.reg_a(4 downto 0);
			case ID_stage.ctrl.shamt2_srcsel is

				when sa_src_reg =>
					data := ID_stage.reg_a(4 downto 0);

				when sa_src_imm =>
					data := ID_stage.shamt;

				when others => null;

			end case;
			data_inv := not data + 1;
			if ID_stage.ctrl.alu.shift_right = '0' then
				EX_stage.shift_ctrl.shamt_rnd	<= data_inv after 2 ns;
			else	
				EX_stage.shift_ctrl.shamt_rnd	<= data after 2 ns;
			end if;
			EX_stage.shift_ctrl.shamt_nrm	<= data after 1 ns;
			EX_stage.shift_ctrl.shift_right	<= ID_stage.ctrl.alu.shift_right;
			EX_stage.shift_ctrl.shift_arith	<= ID_stage.ctrl.alu.shift_arith;
		end if;
		
	end process;

--------------------------------------------------------------------------
inst_shifter: shifter
	GENERIC MAP
	(
		data_width	=> word_t'length
	)
	PORT MAP
	(
		shift_ctrl	=> EX_stage.shift_ctrl,
		din			=> EX_stage.reg_b,
		dout		=> EX_stage.alu_op2_s
	);

inst_alu: alu
	GENERIC MAP
	(
		data_width	=> word_t'length
	)
	PORT MAP
	(
		op1_in			=> EX_stage.alu_op1,
		op2_in			=> EX_stage.alu_op2,
		op2_shifted		=> EX_stage.alu_op2_s,
		ctrl			=> EX_stage.ctrl.alu,
		result			=> alu_result,
		flags			=> EX_stage.alu_flags
	);

inst_bcu: bcu
	GENERIC MAP
	(
		data_width	=> word_t'length
	)
	PORT MAP
	(
		op1_in		=> bcu_op_a,
		op2_in		=> bcu_op_b,
		flags		=> bcu_flags
	);

--------------------------------------------------------------------------
-- MEM stage
--------------------------------------------------------------------------
proc_stage_MEM_n:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if stage_rst(2) = '1' then
				MEM_stage.op		<= NOP;
				MEM_stage.wreg_we	<= '0';
				MEM_stage.ctrl		<= ctrl_lines_default;
				MEM_stage.pa_off	<= (others => '0');
				MEM_stage.events_in	<= events_clr;
			elsif sdu.MEM_stall = '0' then
				MEM_stage.events_in		<= EX_stage.events;
				MEM_stage.op			<= EX_stage.op;
				MEM_stage.wreg_we		<= EX_stage.wreg_we;
				MEM_stage.ctrl			<= EX_stage.ctrl;
				if EX_stage.ctrl.dmem_en = '1' then
					MEM_stage.va			<= EX_stage.va;
				end if;	
				MEM_stage.pa_off		<= EX_stage.pa_off;
				MEM_stage.reg_wptr		<= EX_stage.reg_wptr;
				MEM_stage.nop			<= sdu.MEM_nop;
				if MEM_stage.reg_wptr = EX_stage.reg_wptr then
					if (EX_stage.ctrl.dmem_en and MEM_stage.ctrl.dmem_en) = '1' then
						MEM_stage.ex_result	<= MEM_stage.data;
					end if;
				else
					MEM_stage.ex_result		<= EX_stage.reg_b;
				end if;
				if EX_stage.ctrl.cop_instr_en = '1' then
					if EX_stage.ctrl.cop_read = '1' then
						MEM_stage.ex_result	<= cop_din;
					end if;	
				elsif EX_stage.ctrl.dmem_en = '0' then
					MEM_stage.ex_result		<= EX_stage.result;
				end if;	
				if sdu.MEM_nop = '1' then
					MEM_stage.op		<= NOP;
					MEM_stage.wreg_we	<= '0';
					MEM_stage.ctrl		<= ctrl_lines_default;
				else
					MEM_stage.pcn			<= EX_stage.pcn;
					MEM_stage.epc			<= EX_stage.epc;
				end if;
			end if;	
		end if;
	end process;

proc_stage_MEM_mux:
	process(MEM_stage, dmem_din)
	variable temp1 : word_t;
	variable temp2 : word_t;
	variable data : word_t;
	variable be : unsigned(3 downto 0);
	begin
		data	:= MEM_stage.ex_result;
		if MEM_stage.ctrl.reg_link = '1' then
			data		:= MEM_stage.epc + 8;
		elsif MEM_stage.ctrl.dmem_en = '1' then
			temp1		:= load_shift(dmem_din,  MEM_stage.pa_off, MEM_stage.ctrl.shift_offset, MEM_stage.ctrl.shift_byp);
			temp2		:= load_sign_ext(temp1, MEM_stage.ctrl.sign_ext_byp, MEM_stage.ctrl.load_signed, MEM_stage.ctrl.word2_en, MEM_stage.ctrl.word4_en);
			be			:= load_be(MEM_stage.pa_off, MEM_stage.ctrl.align_left, MEM_stage.ctrl.byte_en_byp);
			if be(0) = '1' then
				data(7 downto 0)	:= temp2(7 downto 0);
			end if;	
			if be(1) = '1' then
				data(15 downto 8)	:= temp2(15 downto 8);
			end if;	
			if be(2) = '1' then
				data(23 downto 16)	:= temp2(23 downto 16);
			end if;	
			if be(3) = '1' then
				data(31 downto 24)	:= temp2(31 downto 24);
			end if;	
		end if;
		MEM_stage.data				<= data after 1 ns;
		
	end process;

proc_stage_MEM_except:
	process(MEM_stage, c0_ctrl_in)
	begin
		MEM_stage.events				<= MEM_stage.events_in;
		MEM_stage.events.Int			<= c0_ctrl_in.int;
		MEM_stage.events.NMI			<= c0_ctrl_in.NMI;
	end process;

	MEM_stage.exc	<= event_is_active(MEM_stage.events);

--------------------------------------------------------------------------
-- WB stage
--------------------------------------------------------------------------
proc_stage_WB_p:
	process(clk_1)
	begin
		if rising_edge(clk_1) then
			if stage_rst(3) = '1' then
				WB_stage.op				<= NOP;
				WB_stage.wreg_we		<= '1';
				WB_stage.reg_wptr		<= (others => '0');
				WB_stage.data			<= (others => '0');
				WB_stage.events			<= events_clr;
			elsif sdu.WB_stall = '0' then
				WB_stage.op				<= MEM_stage.op;
				WB_stage.wreg_we		<= MEM_stage.wreg_we;
				WB_stage.reg_wptr		<= MEM_stage.reg_wptr;
				WB_stage.data			<= MEM_stage.data;
				WB_stage.exc			<= '0';
				if sdu.stall_all = '0' then
					WB_stage.exc			<= MEM_stage.exc;
					if MEM_stage.exc = '1' then
						WB_stage.events			<= MEM_stage.events;
					end if;	
				end if;	
				WB_stage.nop			<= sdu.WB_nop;
				if sdu.WB_nop = '1' then
					WB_stage.op				<= NOP;
					WB_stage.wreg_we		<= '0';
				else
					WB_stage.bd				<= MEM_stage.ctrl.branch or MEM_stage.ctrl.jump or MEM_stage.ctrl.jump_long; -- Todo: works
				end if;	
			end if;	
		end if;	
	end process;

--------------------------------------------------------------------------
end Behavioral;
