/* execute.v - Implements the instruction execution logic

   This file is part of the 9086 project.

   Copyright (c) 2024 Efthymios Kritikos

   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/>.  */

`include "exec_state_def.v"
`include "alu_header.v"
`include "error_header.v"

module execute_unit (
	/*****************   GENERAL   *****************/
	/*                */  input clock, input reset, input write /*TODO: REMOVE!!*/,  output reg [`ERROR_BITS-1:0] ERROR

	/***************  INPUT FROM DE  ***************/
	/* SYNC SIGNALS   */ ,input valid_input,input set_initial_values
	/* INSTR. PARAMS  */ ,input Wbit ,input [2:0] IN_MOD, input [2:0] OUT_MOD,  input [2:0] RM
	/* DATA           */ ,input [15:0] PARAM1_INIT, input [15:0] PARAM2_INIT, input [15:0] ProgCount
	/* STATE CONTROL  */ ,input [`EXEC_STATE_BITS-1:0] init_state
        /* ALU CONTROL    */ ,input [1:0] in_alu_sel1, input [1:0] in_alu_sel2, input [`ALU_OP_BITS-1:0] ALU_OP, output [15:0] _ALU_O_
	/* OTHER          */ ,input memio_address_select

	/**************** OUTPUT TO DE  ****************/
	/* SYNC SIGNALS   */ ,output reg next_exec
	/* FLAGS          */ ,output reg [7:0] EX2DE_FLAGS

	/**************** OUTPUT TO BIU ****************/
	/*                */ ,output reg [15:0] BIU_ADDRESS_INPUT
	/*                */ ,output reg biu_read_request, output reg biu_jump_req,output reg biu_write_request

	/*************** INPUT FROM BIU ****************/
	/*                */ ,input BIU_VALID_DATA,input [15:0] BIU_EX_DATA_READ

	/**************** OUTPUT TO BIU ****************/
	/*                */ ,output [15:0] BIU_EX_DATA_WRITE

	/*****************  REGISTERS  *****************/
	/*                */ ,input [15:0] reg_read_port1_data ,input [15:0] reg_read_port2_data, output reg [3:0] reg_read_port1_addr
	/*                */ ,output reg use_exec_reg_addr, output reg reg_write_we
);

assign _ALU_O_ = ALU_O;

reg [`EXEC_STATE_BITS-1:0] exec_state;
reg [15:0] PARAM1,PARAM2;

assign BIU_EX_DATA_WRITE = memio_address_select ? reg_read_port1_data : ALU_O;

/*############ ALU / Execution units ################################################## */

mux4 #(.WIDTH(16)) MUX16_1A(
/*0*/	PARAM1,
/*1*/	reg_read_port1_data,
/*2*/	ProgCount[15:0],
/*3*/	16'd0, /*0 Constant*/
	in_alu_sel1,
	ALU_A);

mux4 #(.WIDTH(16)) MUX16_1B(
/*0*/	PARAM2,
/*1*/	reg_read_port2_data,
/*2*/	ProgCount[15:0],
/*3*/	16'd0, /*0 Constant*/
	in_alu_sel2,
	ALU_B);

wire [15:0] ALU_A;
wire [15:0] ALU_B;
wire [15:0] ALU_O;
wire [7:0] ALU_FLAGS;

ALU ALU1(
	/*  INPUT 1  */ .A(ALU_A),
	/*  INPUT 2  */ .B(ALU_B),
	/*  OUTPUT   */ .OUT(ALU_O),
	/* OPERATION */ .op(ALU_OP),
	/*   FLAGS   */ .FLAGS(ALU_FLAGS),
	/*   Wbit    */ .Wbit(Wbit)
	);

/*############ Execute logic ########################################################## */

`define unimpl_addressing_mode exec_state <= `EXEC_WAIT;ERROR <= `ERR_UNIMPL_ADDRESSING_MODE;

`define finished_instruction  exec_state <= `EXEC_WAIT;next_exec<=1;

always @(posedge clock) begin
	if ( !reset ) begin
		exec_state <= `EXEC_RESET;
	end else begin
		if ( set_initial_values )begin
			PARAM1 <= PARAM1_INIT;
			PARAM2 <= PARAM2_INIT;
		end
		begin
			case (exec_state)
				`EXEC_RESET: begin
					biu_write_request <= 0;
					biu_read_request <= 0;
					biu_jump_req <= 0;
					reg_write_we <= 1;
					exec_state <= `EXEC_WAIT;
					ERROR <= `ERR_NO_ERROR;
				end
				`EXEC_WAIT:begin
					if(valid_input)begin
						exec_state <= init_state;
						next_exec<=0;
					end else begin
						next_exec<=1;
					end
					biu_jump_req <= 0;
					reg_write_we <= 1;
					use_exec_reg_addr <= 0;
					ERROR<=`ERR_NO_ERROR;
				end
				`EXEC_DE_LOAD_REG_TO_PARAM:begin
					PARAM2<=reg_read_port2_data;
					case(IN_MOD)
						3'b000,3'b001,3'b010: exec_state <= `EXEC_MEMIO_READ;
						default:              exec_state <= `EXEC_WRITE_ENTRY;
					endcase
				end
				`EXEC_MEMIO_READ:begin
					/*Decode MOD R/M, read the data and place it to PARAM1*/
					case (IN_MOD)
						3'b000,
							3'b001,
							3'b010:begin
							case (RM)
								3'b000:begin
									/*[BX]+[SI]*/
									`unimpl_addressing_mode
								end
								3'b001:begin
									/*[BX]+[SI]*/
									`unimpl_addressing_mode
								end
								3'b010:begin
									/*[BP]+[SI]*/
									`unimpl_addressing_mode
								end
								3'b011:begin
									/*[BP]+[DI]*/
									`unimpl_addressing_mode
								end
								3'b100:begin
									/*[SI]*/
									reg_read_port1_addr <= 4'b1110;
									use_exec_reg_addr <= 1;
									exec_state <= `EXEC_MEMIO_READ_SETADDR;
								end
								3'b101:begin
									/*[DI]*/
									reg_read_port1_addr <= 4'b1111;
									use_exec_reg_addr <= 1;
									exec_state <= `EXEC_MEMIO_READ_SETADDR;
								end
								3'b110:begin
									/*d16 */
									`unimpl_addressing_mode
								end
								3'b111:begin
									/*[BX]*/
									reg_read_port1_addr <= 4'b1011;
									use_exec_reg_addr <= 1;
									exec_state <= `EXEC_MEMIO_READ_SETADDR;
								end
							endcase
							if(IN_MOD!=3'b000)begin
								/*Actually check if 01 and add the 8bits or if 10 add the 16bits ....*/
								`unimpl_addressing_mode;
							end
						end
						3'b110:begin /* SP Indirect read*/
							reg_read_port1_addr <= 4'b1100;
							use_exec_reg_addr <= 1;
							exec_state <= `EXEC_MEMIO_READ_SETADDR;
						end
						default:begin
							`unimpl_addressing_mode
						end
					endcase
				end
				`EXEC_MEMIO_READ_SETADDR:begin
					/* if memio_address_select == 0  ADDRESS: reg_read_port1_data   DATA:ALU1_O */
					/* if memio_address_select == 1  ADDRESS: ALU1_O                DATA: reg_read_port1_data */

					if(memio_address_select==0)
						BIU_ADDRESS_INPUT <= reg_read_port1_data[15:0];
					else
						BIU_ADDRESS_INPUT <= ALU_O;

					if ( BIU_VALID_DATA == 1 ) begin
						exec_state <= `EXEC_WRITE_ENTRY;
						PARAM2 <= BIU_EX_DATA_READ;
						biu_read_request <= 0;
					end else begin
						biu_read_request <= 1;
					end
				end
				`EXEC_NEXT_INSTRUCTION:begin
					`finished_instruction
					/*necessary for biu to see we went on another state from decode to give us a new instruction*/
				end
				`EXEC_WRITE_ENTRY:begin
					EX2DE_FLAGS[7:0] <= ALU_FLAGS[7:0];
					case(OUT_MOD)
						3'b000,
							3'b001,
							3'b010 : begin
							if(memio_address_select==1)
								exec_state <= `EXEC_MEMIO_WRITE;
							else
								case (RM) /* Duplicate code with write... */
									3'b000:begin
										/*[BX]+[SI]*/
										`unimpl_addressing_mode
									end
									3'b001:begin
										/*[BX]+[SI]*/
										`unimpl_addressing_mode
									end
									3'b010:begin
										/*[BP]+[SI]*/
										`unimpl_addressing_mode
									end
									3'b011:begin
										/*[BP]+[DI]*/
										`unimpl_addressing_mode
									end
									3'b100:begin
										/*[SI]*/
										reg_read_port1_addr <= 4'b1110;
										use_exec_reg_addr <= 1;
										exec_state <= `EXEC_MEMIO_WRITE;
									end
									3'b101:begin
										/*[DI]*/
										reg_read_port1_addr <= 4'b1111;
										use_exec_reg_addr <= 1;
										exec_state <= `EXEC_MEMIO_WRITE;
									end
									3'b110:begin
										/*d16 */
										`unimpl_addressing_mode
									end
									3'b111:begin
										/*[BX]*/
										reg_read_port1_addr <= 4'b1011;
										use_exec_reg_addr <= 1;
										exec_state <= `EXEC_MEMIO_WRITE;
									end
								endcase
						end
						3'b011:begin
							reg_write_we <= 0;
							`finished_instruction
						end
						3'b100:begin /*No output*/
							`finished_instruction
						end
						3'b101:begin /* Program Counter*/
							BIU_ADDRESS_INPUT <= ALU_O[15:0];
							biu_jump_req <= 1;
							exec_state <= `EXEC_JUMP_RELEASE;
						end
						3'b110:begin /* SP Indirect write*/
							reg_read_port1_addr <= 4'b1100;
							use_exec_reg_addr <= 1;
							exec_state <= `EXEC_MEMIO_WRITE;
						end
						3'b111:begin /* Write to PRAM1 (for microcode calculations) */
							PARAM1 <= ALU_O;
							`finished_instruction
						end
						default:begin
							`unimpl_addressing_mode
						end
					endcase
				end
				`EXEC_JUMP_RELEASE:begin
					biu_jump_req <= 0;
					`finished_instruction
				end
				`EXEC_MEMIO_WRITE:begin
					/* if memio_address_select == 0  ADDRESS: reg_read_port1_data   DATA:ALU1_O */
					/* if memio_address_select == 1  ADDRESS: ALU1_O                DATA: reg_read_port1_data */

					biu_write_request <= 1;

					if(memio_address_select==0)
						BIU_ADDRESS_INPUT <= reg_read_port1_data[15:0];
					else
						BIU_ADDRESS_INPUT <= ALU_O;

					if (write == 0) begin //TODO: don't do it that way or better yet don't do it at all somehow
						biu_write_request <= 0;
						`finished_instruction
					end

				end
				default:begin
				end
			endcase
		end
	end
end

endmodule
`undef unimpl_addressing_mode
