/* biu.v - implementation of the 9086 bus interface unit. The logic that
   controls all external bus functions

   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 "config.v"

`define BIU_HALT			4'b0000
`define BIU_NEXT_ACTION 		4'b0001
`define BIU_READ			4'b0010
`define BIU_RESET1			4'b0011
`define BIU_RESET2			4'b1111

`define BIU_PUT_BYTE			4'b0100
`define BIU_PUT_UNALIGNED_16BIT_DATA	4'b0101
`define BIU_PUT_ALIGNED_16BIT_DATA	4'b0110
`define BIU_PUT_UNALIGNED_PREP_NEXT2	4'b1000
`define BIU_WRITE_EXIT			4'b1001
`define BIU_WRITE_RELEASE		4'b1010

`define BIU_GET_ALIGNED_DATA		4'b1011
`define BIU_GET_UNALIGNED_DATA		4'b1100
`define BIU_GET_SECOND_BYTE		4'b1101
`define BIU_GET_SECOND_BYTE1		4'b1110

module BIU (

	/*****************   GENERAL   *****************/
	/*               */  input clock, input reset

	/**************** OUTSIDE WORLD ****************/
	/*               */ ,output wire [19:0] external_address_bus
	/*               */ ,input [15:0] external_data_bus_read,output [15:0] external_data_bus_write,output reg read, output reg write,output reg BHE,output reg IOMEM
	/*               */ ,input wait_state

	/**************** OUTPUT TO DE  ****************/
	/*               */ ,output reg [31:0] INSTRUCTION, output reg VALID_INSTRUCTION, output reg [15:0] INSTRUCTION_LOCATION
	/*               */ ,output reg VALID_DATA

	/**************** INPUT FROM DE ****************/
	                    ,input Wbit, input MEM_OR_IO, input valid_instruction_ack

	/**************** INPUT FROM EX ****************/
	/*               */ ,input jump_req, input write_request, input read_request
	/*               */ ,input[15:0] ADDRESS_INPUT, input [15:0] DATA_EX_WRITE

	/**************** OUTPUT TO EX *****************/
	/*               */ ,output [15:0] DATA_EX_READ

`ifdef OUTPUT_JSON_STATISTICS
	/***************** STATISTICS  *****************/
	/*               */ ,output  wire [`L1_CACHE_SIZE-1:0] FIFO_SIZE_STAT, output wire VALID_INSTRUCTION_STAT
`endif

);

`ifdef OUTPUT_JSON_STATISTICS
assign FIFO_SIZE_STAT = FIFO_SIZE;
assign VALID_INSTRUCTION_STAT = ((Isit1==1) && (FIFO_SIZE!=0) && `EARLY_VALID_INSTRUCTION_) || ((fifoIsize==2) && (FIFO_SIZE > 1) && `EARLY_VALID_INSTRUCTION_) || ((fifoIsize==3) && (FIFO_SIZE > 2) && `EARLY_VALID_INSTRUCTION_) || (FIFO_SIZE>3);
`endif

reg [15:0] data_bus_output_register;
assign external_data_bus_write=data_bus_output_register; //TODO: should we rename?

reg [15:0] DATA_OUT;
assign DATA_EX_READ=DATA_OUT; //TODO should we rename?

`define FIFO_SIZE_BYTES $rtoi($pow(2,`L1_CACHE_SIZE))

reg [7:0] INPUT_FIFO [`FIFO_SIZE_BYTES-1:0];
reg [`L1_CACHE_SIZE-1:0] FIFO_start; /*inclusive*/
reg [`L1_CACHE_SIZE-1:0] FIFO_end;   /*exclusive*/
wire [`L1_CACHE_SIZE-1:0] FIFO_SIZE = FIFO_end-FIFO_start;

reg [3:0] biu_state;
reg sane;

reg func;
reg [19:0]INSTRUCTION_ADDRESS;
reg [19:0]DATA_ADDRESS;

assign external_address_bus= func ? INSTRUCTION_ADDRESS : DATA_ADDRESS ;

always @(posedge clock) begin
	if ( reset == 0 ) begin
		biu_state <= `BIU_HALT;
		write <= 1;
		sane <= 0;
		biu_state <= `BIU_RESET1;
		FIFO_start <= `L1_CACHE_SIZE'b0;
		FIFO_end <= `L1_CACHE_SIZE'b0;
	end else if ( jump_req ) begin
		FIFO_start <= FIFO_end ;
		INSTRUCTION_ADDRESS <= { 4'hF , ADDRESS_INPUT };
		INSTRUCTION_LOCATION <= ADDRESS_INPUT;
		func <= 1;
		if (biu_state==`BIU_READ)
			biu_state <= `BIU_NEXT_ACTION;
	end else if(valid_instruction_ack) begin
		FIFO_start <= FIFO_start + {{`L1_CACHE_SIZE-3{1'b0}},Isize};
		INSTRUCTION_LOCATION <= INSTRUCTION_LOCATION + {13'd0,Isize};
	end else if (wait_state==1)begin
		// nothing...
	end else begin
		case(biu_state)
			`BIU_HALT: begin
			end
			`BIU_NEXT_ACTION: begin /* decide if we can read, if we are full or if we need to do something else */
				if (write_request) begin
					func<=0;
					DATA_ADDRESS <= { 4'hF , ADDRESS_INPUT };
					IOMEM <= MEM_OR_IO;
					biu_state <= (Wbit==0) ? `BIU_PUT_BYTE : (ADDRESS_INPUT[0:0]?`BIU_PUT_UNALIGNED_16BIT_DATA:`BIU_PUT_ALIGNED_16BIT_DATA) ;
					INSTRUCTION_ADDRESS <= {4'hF,INSTRUCTION_LOCATION} ;
					FIFO_end<=FIFO_start;
				end else if ( read_request ) begin
					func<=0;
					DATA_ADDRESS <= { 4'hF , ADDRESS_INPUT };
					IOMEM <= MEM_OR_IO;
					read <= 0;
					BHE <= 0;
					biu_state <= (ADDRESS_INPUT[0:0])?`BIU_GET_UNALIGNED_DATA:`BIU_GET_ALIGNED_DATA;
				end else begin
					if ( FIFO_SIZE!={`L1_CACHE_SIZE{1'b1}} ) begin
						func<=1;
						biu_state <= `BIU_READ;
						write <= 1;
						read <= 0;
						IOMEM <= 0;
						BHE <= 0;
					end else begin
						biu_state <= `BIU_NEXT_ACTION;
					end
				end



			end

			/*************** INSTRUCTION FIFO READ ***************/
			`BIU_READ: begin
				if(INSTRUCTION_ADDRESS[0:0]==0 && FIFO_SIZE<{{(`L1_CACHE_SIZE-1){1'b1}},1'b0})begin
					INPUT_FIFO[FIFO_end] <= external_data_bus_read[7:0];
					INPUT_FIFO[FIFO_end+`L1_CACHE_SIZE'd1] <= external_data_bus_read[15:8];
					FIFO_end <= FIFO_end+`L1_CACHE_SIZE'd2;
					INSTRUCTION_ADDRESS <= INSTRUCTION_ADDRESS+20'd2;
				end else if(INSTRUCTION_ADDRESS[0:0]==0)begin
					INPUT_FIFO[FIFO_end] <= external_data_bus_read[7:0];
					FIFO_end <= FIFO_end+`L1_CACHE_SIZE'd1;
					INSTRUCTION_ADDRESS <= INSTRUCTION_ADDRESS+20'd1;
				end else begin
					INPUT_FIFO[FIFO_end] <= external_data_bus_read[15:8];
					FIFO_end <= FIFO_end+`L1_CACHE_SIZE'd1;
					INSTRUCTION_ADDRESS <= INSTRUCTION_ADDRESS+20'd1;
				end
				biu_state <= `BIU_NEXT_ACTION;
				read<=1;
			end

			/*************** DATA WRITE ***************/
			`BIU_PUT_UNALIGNED_16BIT_DATA:begin
				`ifdef DEBUG_DATA_READ_WRITES
					$display("Writing 16bit %04x at %04x",DATA_EX_WRITE,DATA_ADDRESS);
				`endif
				BHE <= 0;
				data_bus_output_register <= {DATA_EX_WRITE[7:0],DATA_EX_WRITE[15:8]};
				write <= 0;
				biu_state <= `BIU_PUT_UNALIGNED_PREP_NEXT2;
			end
			`BIU_PUT_UNALIGNED_PREP_NEXT2:begin
				write <= 1;
				DATA_ADDRESS <= DATA_ADDRESS+20'd1;
				BHE <= 1;
				biu_state <= `BIU_WRITE_EXIT;
			end
			`BIU_PUT_ALIGNED_16BIT_DATA:begin
				`ifdef DEBUG_DATA_READ_WRITES
					$display("Writing 16bit %04x at %04x",DATA_EX_WRTIE,DATA_ADDRESS);
				`endif
				data_bus_output_register <= {DATA_EX_WRITE[15:8],DATA_EX_WRITE[7:0]};
				write <= 0;
				biu_state <= `BIU_WRITE_RELEASE;
			end
			`BIU_PUT_BYTE:begin
				`ifdef DEBUG_DATA_READ_WRITES
					$display("Writing 8bit %02x at %04x",DATA_EX_WRITE[7:0],DATA_ADDRESS);
				`endif
				if(ADDRESS_INPUT[0:0]==0) begin
					BHE <= 1;
					data_bus_output_register <= {8'b0,DATA_EX_WRITE[7:0]};
				end else begin
					BHE <= 0;
					data_bus_output_register <= {DATA_EX_WRITE[7:0],8'b0};
				end
				write <= 0;
				biu_state <= `BIU_WRITE_RELEASE;
			end
			`BIU_WRITE_EXIT:begin
				write <= 0;
				biu_state <= `BIU_WRITE_RELEASE;
			end
			`BIU_WRITE_RELEASE:begin
				write <= 1;
				biu_state <= `BIU_NEXT_ACTION;
			end


			/*************** DATA READ ***************/
			`define finished_read  \
				if ( read_request == 0 ) begin \
					biu_state <= `BIU_NEXT_ACTION;\
					VALID_DATA <= 0;\
				end else \
					VALID_DATA <= 1;
			`BIU_GET_ALIGNED_DATA:begin
				`ifdef DEBUG_DATA_READ_WRITES
					if(Wbit==1)
						$display("Reading 16bit %04x from %04x",external_data_bus_read,DATA_ADDRESS);
					else
						$display("Reading 8bit %02x from %04x",external_data_bus_read[7:0],DATA_ADDRESS);
				`endif
				DATA_OUT <= (Wbit==1)? external_data_bus_read : {8'b0,external_data_bus_read[7:0]} ;
				read <=1;
				`finished_read
			end
			`BIU_GET_UNALIGNED_DATA:begin
				`ifdef DEBUG_DATA_READ_WRITES
					if(Wbit==0)
						$display("Reading 8bit %02x from %04x",external_data_bus_read[15:8],DATA_ADDRESS);
				`endif
				DATA_OUT[7:0] <= external_data_bus_read[15:8];
				read <=1;
				if(Wbit==1) begin
					biu_state <= `BIU_GET_SECOND_BYTE;
				end else begin
					`finished_read
				end
			end
			`BIU_GET_SECOND_BYTE:begin
				DATA_ADDRESS <= DATA_ADDRESS+20'd1;
				biu_state <= `BIU_GET_SECOND_BYTE1;
				read <=0;
			end
			`BIU_GET_SECOND_BYTE1:begin
				`ifdef DEBUG_DATA_READ_WRITES
						$display("Reading 16bit %02x from %04x",{external_data_bus_read[7:0],DATA_OUT[7:0]},DATA_ADDRESS-1);//read started a byte earlier
				`endif
				DATA_OUT[15:8] <= external_data_bus_read[7:0];
				`finished_read
				read <=1;
			end

			/*************** HOUSE KEEPING ***************/
			`BIU_RESET1: begin
				biu_state <= `BIU_RESET2;
				VALID_DATA <= 0;
			end
			`BIU_RESET2: begin
				FIFO_start <= `L1_CACHE_SIZE'b0;
				FIFO_end <= `L1_CACHE_SIZE'b0;
				biu_state <= `BIU_NEXT_ACTION;
				INSTRUCTION_ADDRESS <= 20'hFFFF0;
				INSTRUCTION_LOCATION <= 16'hFFF0;
				VALID_DATA <= 0;
				sane<=1;
			end
			default: begin
				biu_state <= `BIU_NEXT_ACTION;/*Should be unreachable*/
			end
		endcase
	end
end

/* update VALID_INSTRUCTION and INSTRUCTION */
always @( posedge clock) begin

	if(jump_req==1)begin
		VALID_INSTRUCTION <= 0;
	end else if(sane==1) begin
		//if(VALID_INSTRUCTION == 1 ) begin
		//	`ifdef DOUBLE_INSTRUCTION_LOAD
		//		if(FIFO_SIZE>`L1_CACHE_SIZE'd3+{{`L1_CACHE_SIZE-3{1'b0}},Isize})begin
		//			if((fifoIsize2==2) && (FIFO_SIZE > `L1_CACHE_SIZE'd1+{{`L1_CACHE_SIZE-3{1'b0}},fifoIsize}))begin
		//				VALID_INSTRUCTION <= 1;
		//				INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
		//				INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
		//			end else if((fifoIsize2==3) && (FIFO_SIZE > `L1_CACHE_SIZE'd2+{{`L1_CACHE_SIZE-3{1'b0}},fifoIsize}))begin
		//				VALID_INSTRUCTION <= 1;
		//				INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
		//				INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
		//				INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd2];
		//			end else if(FIFO_SIZE>`L1_CACHE_SIZE'd3+{{`L1_CACHE_SIZE-3{1'b0}},fifoIsize})begin
		//				VALID_INSTRUCTION <= 1;
		//				INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
		//				INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
		//				INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd2];
		//				INSTRUCTION[ 7: 0] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd3];
		//			end else
		//				VALID_INSTRUCTION <= 0;
		//		end else begin
		//			VALID_INSTRUCTION <= 0;
		//		end
		//	`else
		//		VALID_INSTRUCTION <= 0;
		//	`endif
		//end else begin
			//$display("trig fifoIsize=%d   %d/%d [%02x %02x]",fifoIsize,FIFO_start,FIFO_end,INPUT_FIFO[FIFO_start],INPUT_FIFO[FIFO_start+1]);
			`ifdef EARLY_VALID_INSTRUCTION
				if(FIFO_start==FIFO_end) begin
					/*TODO: I would use FIFO_SIZE==0 here or better yet add an else at the
						end but since FIFO_start and FIFO_end are updated in a blocking
						manner it seems that the assign statement updating FIFO_SIZE
						doesn't work. PLEASE CLEAN UP THIS MESS */
					VALID_INSTRUCTION <= 0;
				end else if((Isit1==1) && (FIFO_SIZE!=0))begin
					VALID_INSTRUCTION <= 1;
					INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
				end else if((fifoIsize==2) && (FIFO_SIZE > `L1_CACHE_SIZE'd1))begin
					VALID_INSTRUCTION <= 1;
					INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
					INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
				end else if((fifoIsize==3) && (FIFO_SIZE > `L1_CACHE_SIZE'd2))begin
					VALID_INSTRUCTION <= 1;
					INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
					INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
					INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd2];
				end else if(FIFO_SIZE > `L1_CACHE_SIZE'd3)begin
					VALID_INSTRUCTION <= 1;
					INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
					INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
					INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd2];
					INSTRUCTION[ 7: 0] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd3];
				end else
					VALID_INSTRUCTION <= 0;
			`else
				if(FIFO_start==FIFO_end) begin
					/*TODO: Same as on the first statement on the other side of the `ifdef */
					VALID_INSTRUCTION <= 0;
				end else if(FIFO_SIZE>`L1_CACHE_SIZE'd3)begin
					VALID_INSTRUCTION <= 1;
					INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
					INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1];
					INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd2];
					INSTRUCTION[ 7: 0] <= INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd3];
				end else
					VALID_INSTRUCTION <= 0;
			`endif
		//end
	end
end

wire [2:0] Isize;
InstrSize InstrSize({INSTRUCTION[31:24],INSTRUCTION[21:19]},Isize);

`ifdef INCLUDE_EARLY_CALC_CIRCUIT
wire [2:0] fifoIsize;
wire Isit1;
InstrSize fifoInstrSize({INPUT_FIFO[FIFO_start][7:0],INPUT_FIFO[FIFO_start+`L1_CACHE_SIZE'd1][5:3]},fifoIsize);
Is1 Is1(INPUT_FIFO[FIFO_start][7:0],Isit1);
`endif

`ifdef DOUBLE_INSTRUCTION_LOAD
wire [2:0] fifoIsize2;
InstrSize fifoInstrSize2(
	{ INPUT_FIFO[FIFO_start+{{`L1_CACHE_SIZE-3{1'b0}},fifoIsize}][7:0], INPUT_FIFO[FIFO_start+{{`L1_CACHE_SIZE-3{1'b0}},fifoIsize}+`L1_CACHE_SIZE'd1][5:3]}
	,fifoIsize2
	);
`endif

endmodule
