library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.core_pkg.all;
use work.op_pkg.all;

entity exec is
	port (
		clk           : in  std_logic;
		res_n         : in  std_logic;
		stall         : in  std_logic;
		flush         : in  std_logic;

		-- from DEC
		op            : in  exec_op_type;
		pc_in         : in  pc_type;

		-- to MEM
		pc_old_out    : out pc_type;
		pc_new_out    : out pc_type;
		aluresult     : out data_type;
		wrdata        : out data_type;
		zero          : out std_logic;

		memop_in      : in  mem_op_type;
		memop_out     : out mem_op_type;
		wbop_in       : in  wb_op_type;
		wbop_out      : out wb_op_type;

		-- FWD
		exec_op       : out exec_op_type;
		reg_write_mem : in  reg_write_type;
		reg_write_wr  : in  reg_write_type
	);
end entity;

architecture rtl of exec is
	type alu_t is record
		op	: alu_op_type;
		A 	: data_type;
		B 	: data_type;
	end record;

	constant INITIAL_ALU : alu_t := (
		op	=> ALU_NOP,
		A 	=> (others => '0'),
		B 	=> (others => '0')
	);

	type internal_t is record
		op  	: exec_op_type;
		pc  	: pc_type;
		memop : mem_op_type;
		wbop	: wb_op_type;
	end record;


	constant INITIAL_INTERNAL : internal_t := (
		pc 	=> (others => '0'),
		op		=> EXEC_NOP,
		memop	=> MEM_NOP,
		wbop	=> WB_NOP
	);

	signal internal : internal_t;
	signal int_alu	 : alu_t;
	signal pc_a, pc_b : pc_type; 
	signal do_fwd_A, do_fwd_B : std_logic;
	signal fwd_val_A, fwd_val_B : data_type;
	signal rs1_data, rs2_data : data_type;
begin
	-- structural
	alu_inst : entity work.alu
	port map (
		op => int_alu.op,
		A  => int_alu.A,
		B  => int_alu.B,
		R  => aluresult,
		Z  => zero
	);

	
	-- sequential
	sync : process(clk, res_n, flush, stall)
	begin
		if res_n = '0' then
			internal <= INITIAL_INTERNAL;
		elsif stall = '0' and rising_edge(clk) then
			internal.pc    <= pc_in;
			internal.op    <= op;
			internal.memop <= memop_in;
			internal.wbop  <= wbop_in;
		end if;
	end process;
	
	-- FWD
	fwd_A_inst : entity work.fwd
	port map (
		reg_write_mem => reg_write_mem,
		reg_write_wb  => reg_write_wr,
		
		reg    => internal.op.rs1,
		val    => fwd_val_A,
		do_fwd => do_fwd_A
	);
	
	fwd_B_inst : entity work.fwd
	port map (
		reg_write_mem => reg_write_mem,
		reg_write_wb  => reg_write_wr,
		
		reg    => internal.op.rs2,
		val    => fwd_val_B,
		do_fwd => do_fwd_B
	);
	
	-- concurrent
	rs1_data <= fwd_val_A when do_fwd_A = '1' else internal.op.readdata1;
	rs2_data <= fwd_val_B when do_fwd_B = '1' else internal.op.readdata2;
	
	int_alu.op <= internal.op.aluop;
	int_alu.A  <= x"0000" & internal.pc when internal.op.alusrc1 /= '0' else rs1_data;
	int_alu.B  <= internal.op.imm when internal.op.alusrc2 /= '0' else rs2_data;
	
	pc_old_out <= internal.pc;
	pc_a       <= internal.op.readdata1(15 downto 0) when (internal.op.alusrc1 and internal.op.alusrc2 and internal.op.alusrc3) = '1' else internal.pc; --case for JALR
	pc_b       <= internal.op.imm(pc_type'RANGE) 	when internal.op.alusrc3 = '1' else (others => '0');
	pc_new_out <= std_logic_vector(signed(pc_a) + signed(pc_b));
	
	wrdata    <= rs2_data;
	memop_out <= internal.memop when flush = '0' else MEM_NOP;
	wbop_out  <= internal.wbop when flush = '0' else WB_NOP;
	
	exec_op <= internal.op;
end architecture;
