/*
The MIT License (MIT)

Copyright (c) 2017 Ruchira Hasaranga

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

// R8CPU (C) 2017 Hasaranga
// Uses behaviour modeling.

// 11 bit address bus
// 8 bit databus
// CISC machine
// boots from address 001 00000000

// ax = 8bit register

// instructions format: mov dst,src
// addr=11bits, value=8bits

//    reading:	mov1 ax, addr (total 3 bytes)
//    writing:	mov2 addr, ax (total 3 bytes)
//	        mov3 addr, value (total 4 bytes)			
//  flow cont:	jmp1 addr (total 3 bytes)
//		je addr (total 3 bytes)
//		jne addr (total 3 bytes)
//		jl addr (total 3 bytes) ( jump if value is less than ax)
//		jle addr (total 3 bytes) ( jump if value is less than or equal to ax)
//		jg addr (total 3 bytes) ( jump if value is greater than ax)
//		jge addr (total 3 bytes) ( jump if value is greater than or equal to ax)
// arithmetic: inc ax (total 1 byte)
//		cmp ax, value (total 2 bytes) (compare value with ax)

/*
Writing to device:
	put value into addrbus
	put value into databus
	set en HIGH then wait for 2 cycles
		set wr HIGH for 2 cycles
		set wr LOW for 2 cycles	
		wait till busy LOW
	set en LOW for 2 cycles (optional)
	
Reading from device:
	put value into addrbus
	set en HIGH then wait for 2 cycles
		set rd HIGH for 2 cycles
		set rd LOW for 2 cycles	
		wait till busy LOW	
		set oe HIGH for 1 cycle
		databus is valid now (still oe HIGH)
		set oe LOW for 1 cycle	
	set en LOW for 2 cycles (optional)
	
device oe response must be instant (use combinational logic).

*/

module R8CPU ( 

	input clk, 
	input reset,
	input	hlt,
	input deviceBusy, 
	inout [7:0] databus, 
	output reg [10:0] addressbus,
	output reg wr,
	output reg rd,
	output reg oe,
	output reg hlt_indicator
);


reg databusIsOutput=1'b0;
reg [7:0] r_databus;

assign databus = databusIsOutput ? r_databus : 8'bZZZZZZZZ;

reg [7:0] ax;
reg [10:0] pc;
reg [7:0] opcode;
reg [7:0] arg1;
reg [7:0] arg2;
reg [7:0] arg3;
reg ef,gtf,ltf; // ef= equal flag, gtf= greater than flag, ltf= less than flag

parameter s_RESET						= 8'd0; // power on, or reset btn press
parameter s_FETCH_OPCODE					= 8'd1;
parameter s_READ_DATA						= 8'd2;
parameter s_READ_DATA_ENABLE_RD					= 8'd3;	
parameter s_READ_DATA_DISABLE_RD				= 8'd4;
parameter s_READ_DATA_BUSY					= 8'd5;
parameter s_READ_DATA_ACQUIRE_DATA				= 8'd6;
parameter s_READ_DATA_DONE					= 8'd7;
parameter s_WAIT						= 8'd8;
parameter s_FETCH_OPCODE_DONE					= 8'd9;
parameter s_DECODE_OPCODE					= 8'd10;
parameter s_EXEC_MOV1						= 8'd11;
parameter s_EXEC_MOV1_DONE					= 8'd12;
parameter s_FETCH_2ARGS						= 8'd13;
parameter s_FETCH_2ARGS_1DONE					= 8'd14;
parameter s_FETCH_2ARGS_2DONE					= 8'd15;
parameter s_CPU_HLT						= 8'd16;
parameter s_EXEC_MOV2						= 8'd17;
parameter s_EXEC_MOV2_DONE					= 8'd18;
parameter s_WRITE_DATA						= 8'd19;
parameter s_WRITE_DATA_ENABLE_WR				= 8'd20;
parameter s_WRITE_DATA_DISABLE_WR				= 8'd21;
parameter s_WRITE_DATA_BUSY					= 8'd22;
parameter s_EXEC_MOV3 						= 8'd23;
parameter s_EXEC_MOV3_DONE 					= 8'd24;
parameter s_FETCH_3ARGS 					= 8'd25;
parameter s_FETCH_3ARGS_1DONE 					= 8'd26;
parameter s_FETCH_3ARGS_2DONE 					= 8'd27;
parameter s_FETCH_3ARGS_3DONE 					= 8'd28;
parameter s_EXEC_JMP1 						= 8'd29;
parameter s_EXEC_JMP1_DONE					= 8'd30;
parameter s_EXEC_CMPAX						= 8'd31;
parameter s_EXEC_JE						= 8'd32;
parameter s_EXEC_JNE						= 8'd33;
parameter s_EXEC_JL						= 8'd34;
parameter s_EXEC_JLE						= 8'd35;
parameter s_EXEC_JG						= 8'd36;
parameter s_EXEC_JGE						= 8'd37;

parameter op_MOV1		=8'd1; // do not use 8'd0. it is used to detect non responding device!
parameter op_MOV2		=8'd2;
parameter op_MOV3		=8'd3;
parameter op_JMP1		=8'd4;
parameter op_INCAX 		=8'd5;
parameter op_CMPAX 		=8'd6;
parameter op_JE			=8'd7;
parameter op_JNE		=8'd8;
parameter op_JL			=8'd9;
parameter op_JLE		=8'd10;
parameter op_JG			=8'd11;
parameter op_JGE		=8'd12;

reg [7:0] SM_Main = s_RESET;
reg [3:0] SM_Wait_Repeat_Index = 4'd1;
reg [3:0] SM_Wait_Cycles;
reg [7:0] SM_After_Wait;
reg [7:0] SM_After_Read;
reg [7:0] SM_After_Args_Read;
reg [7:0] SM_After_Write;

always @ ( posedge clk)
begin
	if(reset == 1'b1) // reset btn pressed
	begin
		SM_Main <= s_RESET;		
	end
	else if(hlt == 1'b1)
	begin
		SM_Main <= s_CPU_HLT;		
	end
	else
	begin
		case (SM_Main)
		
			s_RESET:
			begin
				databusIsOutput <= 1'b0;
				r_databus <= 8'd0;
				addressbus <= 11'd0;
				pc <= 11'b00100000000;
				ax <= 8'd0;
				wr <= 1'b0;
				rd <= 1'b0;
				oe <= 1'b0;	
				ef <= 1'b0;
				gtf <= 1'b0;
				ltf <= 1'b0;
				hlt_indicator <= 1'b0;
				SM_Wait_Repeat_Index <= 4'd1;
				
				SM_Main <= s_FETCH_OPCODE;
			end
			
			s_FETCH_OPCODE: // put pc value into addressbus, read from that address
			begin
				addressbus <= pc;		
				SM_After_Read <= s_FETCH_OPCODE_DONE;
				SM_Main <= s_READ_DATA;
			end
			
			s_FETCH_OPCODE_DONE:
			begin
				opcode <= r_databus;
				SM_Main <= s_DECODE_OPCODE;
			end

			s_DECODE_OPCODE:
			begin
				case (opcode)
				
					op_MOV1: // mov1 ax, addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_MOV1;
						SM_Main <= s_FETCH_2ARGS;				
					end

					op_MOV2: // mov2 addr,ax (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_MOV2;
						SM_Main <= s_FETCH_2ARGS;				
					end
					
					op_MOV3: // mov3 addr, value (read next 3 bytes)	
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_MOV3;
						SM_Main <= s_FETCH_3ARGS;							
					end
					
					op_JMP1: // jmp1 addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JMP1;
						SM_Main <= s_FETCH_2ARGS;						
					end
					
					op_INCAX:
					begin
						ax <= ax + 8'd1;
						pc <= pc + 11'd1; // increase pc by 1
						SM_Main <= s_FETCH_OPCODE;	
					end
					
					op_CMPAX: // read next 1 byte
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Read <= s_EXEC_CMPAX;
						SM_Main <= s_READ_DATA;						
					end
					
					op_JE: // je addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JE;
						SM_Main <= s_FETCH_2ARGS;						
					end
					
					op_JNE: // jne addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JNE;
						SM_Main <= s_FETCH_2ARGS;						
					end
					
					op_JL: // jl addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JL;
						SM_Main <= s_FETCH_2ARGS;						
					end
					
					op_JLE: // jle addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JLE;
						SM_Main <= s_FETCH_2ARGS;						
					end
					
					op_JG: // jg addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JG;
						SM_Main <= s_FETCH_2ARGS;					
					end
					
					op_JGE: // jge addr (read next 2 bytes)
					begin
						addressbus <= addressbus + 11'd1;
						SM_After_Args_Read <= s_EXEC_JGE;
						SM_Main <= s_FETCH_2ARGS;					
					end
					
					default: // invalid opcode
					begin						
						SM_Main <= s_CPU_HLT;
					end
					
				endcase
			end
						
//========================================================================

			s_EXEC_MOV1:
			begin			
				addressbus <= {arg1[2:0], arg2};
				SM_After_Read <= s_EXEC_MOV1_DONE;
				SM_Main <= s_READ_DATA;
			end
			
			s_EXEC_MOV1_DONE:
			begin
				ax <= r_databus;				
				pc <= pc + 11'd3; // increase pc by 3 bytes
				SM_Main <= s_FETCH_OPCODE;
			end

//========================================================================
		
			s_EXEC_MOV2:
			begin
				addressbus <= {arg1[2:0], arg2};
				r_databus <= ax;
				SM_After_Write <= s_EXEC_MOV2_DONE;
				SM_Main <= s_WRITE_DATA;
			end
			
			s_EXEC_MOV2_DONE:
			begin
				pc <= pc + 11'd3; // increase pc by 3 bytes
				SM_Main <= s_FETCH_OPCODE;				
			end

//========================================================================
			
			s_EXEC_MOV3:
			begin
				addressbus <= {arg1[2:0], arg2};
				r_databus <= arg3;
				SM_After_Write <= s_EXEC_MOV3_DONE;
				SM_Main <= s_WRITE_DATA;				
			end
			
			s_EXEC_MOV3_DONE:
			begin
				pc <= pc + 11'd4; // increase pc by 4 bytes
				SM_Main <= s_FETCH_OPCODE;				
			end

//========================================================================
			
			s_EXEC_JMP1:
			begin
				pc <= {arg1[2:0], arg2};
				SM_Main <= s_FETCH_OPCODE;
			end

//========================================================================
			
			s_EXEC_CMPAX:
			begin
				if(r_databus == ax) ef <= 1'b1;				
				else ef <= 1'b0;			
				
				if(r_databus < ax) ltf <= 1'b1;				
				else ltf <= 1'b0;	
			
				if(r_databus > ax) gtf <= 1'b1;				
				else gtf <= 1'b0;	
	
				pc <= pc + 11'd2; // increase pc by 2 bytes
				SM_Main <= s_FETCH_OPCODE;					
			end

//========================================================================
			
			s_EXEC_JE:
			begin
				if(ef == 1'b1) 
					pc <= {arg1[2:0], arg2};
				else 
					pc <= pc + 11'd3;
				
				SM_Main <= s_FETCH_OPCODE;			
			end

//========================================================================
			
			s_EXEC_JNE:
			begin
				if(ef == 1'b0) 
					pc <= {arg1[2:0], arg2};
				else 
					pc <= pc + 11'd3;
				
				SM_Main <= s_FETCH_OPCODE;			
			end	
	
//========================================================================

			s_EXEC_JL:
			begin
				if(ltf == 1'b1) 
					pc <= {arg1[2:0], arg2};
				else 
					pc <= pc + 11'd3;
				
				SM_Main <= s_FETCH_OPCODE;				
			end

//========================================================================
			
			s_EXEC_JLE:
			begin
				if( (ltf == 1'b1) | (ef == 1'b1) )
					pc <= {arg1[2:0], arg2};
				else 
					pc <= pc + 11'd3;
				
				SM_Main <= s_FETCH_OPCODE;				
			end

//========================================================================
			
			s_EXEC_JG:
			begin
				if(gtf == 1'b1) 
					pc <= {arg1[2:0], arg2};
				else 
					pc <= pc + 11'd3;
				
				SM_Main <= s_FETCH_OPCODE;				
			end

//========================================================================
			
			s_EXEC_JGE:
			begin
				if( (gtf == 1'b1) | (ef == 1'b1) )
					pc <= {arg1[2:0], arg2};
				else 
					pc <= pc + 11'd3;
				
				SM_Main <= s_FETCH_OPCODE;				
			end
			
//========================================================================
			
			s_FETCH_2ARGS: // set addressbus, SM_After_Args_Read, then call this. results will be in arg1 & arg2
			begin
				SM_After_Read <= s_FETCH_2ARGS_1DONE;
				SM_Main <= s_READ_DATA;				
			end
			
			s_FETCH_2ARGS_1DONE:
			begin
				arg1 <= r_databus;
				addressbus <= addressbus + 11'd1;
				SM_After_Read <= s_FETCH_2ARGS_2DONE;
				SM_Main <= s_READ_DATA;						
			end
			
			s_FETCH_2ARGS_2DONE:
			begin
				arg2 <= r_databus;
				SM_Main <= SM_After_Args_Read;	
			end

//========================================================================
			
			s_FETCH_3ARGS: // set addressbus, SM_After_Args_Read, then call this. results will be in arg1, arg2 & arg3
			begin
				SM_After_Read <= s_FETCH_3ARGS_1DONE;
				SM_Main <= s_READ_DATA;					
			end
			
			s_FETCH_3ARGS_1DONE:
			begin
				arg1 <= r_databus;
				addressbus <= addressbus + 11'd1;
				SM_After_Read <= s_FETCH_3ARGS_2DONE;
				SM_Main <= s_READ_DATA;					
			end	
			
			s_FETCH_3ARGS_2DONE:
			begin
				arg2 <= r_databus;
				addressbus <= addressbus + 11'd1;
				SM_After_Read <= s_FETCH_3ARGS_3DONE;
				SM_Main <= s_READ_DATA;				
			end
			
			s_FETCH_3ARGS_3DONE:
			begin
				arg3 <= r_databus;
				SM_Main <= SM_After_Args_Read;				
			end
			
//========================================================================
			
			s_CPU_HLT:
			begin
				hlt_indicator <= 1'b1;
				addressbus <= 11'd0;
				databusIsOutput <= 1'b0;
				wr <= 1'b0;
				rd <= 1'b0;
				oe <= 1'b0;					
				SM_Main <= s_CPU_HLT;
			end

//========================================================================

			s_WRITE_DATA: // put value into addressbus, r_databus & SM_After_Write then call this
			begin
				databusIsOutput <= 1'b1;
				SM_Wait_Cycles <= 4'd1;
				SM_After_Wait <= s_WRITE_DATA_ENABLE_WR;
				SM_Main <= s_WAIT;				
			end
			
			s_WRITE_DATA_ENABLE_WR:
			begin
				wr <= 1'b1;
				SM_Wait_Cycles <= 4'd1;
				SM_After_Wait <= s_WRITE_DATA_DISABLE_WR;
				SM_Main <= s_WAIT;				
			end
			
			s_WRITE_DATA_DISABLE_WR:
			begin
				wr <= 1'b0;
				SM_Wait_Cycles <= 4'd1;
				SM_After_Wait <= s_WRITE_DATA_BUSY;
				SM_Main <= s_WAIT;			
			end
			
			s_WRITE_DATA_BUSY:
			begin
				if(deviceBusy == 1'b0)
				begin
					databusIsOutput <= 1'b0;
					SM_Main <= SM_After_Write;	
				end
				else
				begin
					SM_Main <= s_WRITE_DATA_BUSY;		
				end				
			end
			
//========================================================================
			
			s_READ_DATA: // put value into addressbus, set SM_After_Read, then call this. result will be in r_databus.
			begin
				SM_Wait_Cycles <= 4'd1;
				SM_After_Wait <= s_READ_DATA_ENABLE_RD;
				SM_Main <= s_WAIT;
			end
			
			s_READ_DATA_ENABLE_RD:		
			begin
				rd <= 1'b1;
				SM_Wait_Cycles <= 4'd1;
				SM_After_Wait <= s_READ_DATA_DISABLE_RD;
				SM_Main <= s_WAIT;			
			end
			
			s_READ_DATA_DISABLE_RD:
			begin
				rd <= 1'b0;
				SM_Wait_Cycles <= 4'd1;
				SM_After_Wait <= s_READ_DATA_BUSY;
				SM_Main <= s_WAIT;				
			end
			
			s_READ_DATA_BUSY:
			begin
				if(deviceBusy == 1'b0)
				begin
					oe <= 1'b1;
					SM_Main <= s_READ_DATA_ACQUIRE_DATA;	
				end
				else
				begin
					SM_Main <= s_READ_DATA_BUSY;		
				end			
			end
			
			s_READ_DATA_ACQUIRE_DATA:
			begin
				r_databus <= databus;
				SM_Main <= s_READ_DATA_DONE;		
			end
			
			s_READ_DATA_DONE:
			begin
				oe <= 1'b0;
				SM_Main <= SM_After_Read;
			end
			
//========================================================================
			
			s_WAIT: //  set SM_Wait_Cycles and SM_After_Wait then call this to wait given clock cycles
			begin				
				if(SM_Wait_Repeat_Index == SM_Wait_Cycles)
				begin
					SM_Wait_Repeat_Index <= 4'd1;
					SM_Main <= SM_After_Wait;
				end
				else
				begin
					SM_Wait_Repeat_Index <= SM_Wait_Repeat_Index + 4'd1;
					SM_Main <= s_WAIT;
				end
			end
			
//========================================================================
			
			default:
				SM_Main <= s_RESET;
				
		endcase
	end
end

endmodule
