/* I2C_driver.v - Implements an I2C interface

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

module I2C_driver (
	input wire clock,
	input wire SDA_input,
	output reg  SDA_output,
	output reg SDA_direction, //1:output 0:input
	output reg SCL,

	input wire [6:0] address,
	output reg I2C_BUSY=0,
	input wire I2C_TRANSACT,
	input wire DIR,
	input wire [15:0] i2c_data_write,
	input wire transact_width, /* 0=byte 1=word */
	/* verilator lint_off UNUSEDSIGNAL */
	input wire ignore_ack,
	/* verilator lint_on UNUSEDSIGNAL */
	output reg [15:0] i2c_data_read=16'h4141,

	output reg error=0
);

//assign  i2c_data_read=16'h0042;

reg DIR_latched;

reg [5:0] i2c_state = 6'b100100;

reg [3:0] data_bit_counter;
reg [15:0] data_internal;
reg [6:0]address_internal;

reg trans_width_latch;

always @(posedge clock) begin
	case (i2c_state)
	/***** start sequence ******/
		6'b000000:begin
			SDA_direction<=1;
			SDA_output<=1;
			SCL<=1;
			i2c_state<=6'b000001;
		end
		6'b000001:begin
			SDA_output<=0;
			SCL<=1;
			i2c_state<=6'b000010;
		end
		6'b000010:begin
			SDA_output<=0;
			SCL<=0;
			i2c_state<=6'b000011;
			data_bit_counter<=0;
		end
	/****** Set address ********/
		6'b000011:begin
			SCL<=0;
			i2c_state<=6'b000100;
		end
		6'b000100:begin
			SCL<=0;
			SDA_output<=address_internal[6:6];
			address_internal[6:0]<={address_internal[5:0],1'b0};
			data_bit_counter<=data_bit_counter+1;
			i2c_state<=6'b000101;
		end
		6'b000101:begin
			SCL<=1;
			i2c_state<=6'b000110;
		end
		6'b000110:begin
			SCL<=1;
			if(data_bit_counter==4'd7)
				i2c_state<=6'b000111;
			else
				i2c_state<=6'b000011;
		end
	/****** Read/Write *********/
		6'b000111:begin
			SCL<=0;
			i2c_state<=6'b001000;
		end
		6'b001000:begin
			SCL<=0;
			SDA_output<=DIR_latched;/*Write=0 Read=1*/
			i2c_state<=6'b001001;
		end
		6'b001001:begin
			SCL<=1;
			i2c_state<=6'b001010;
		end
		6'b001010:begin
			SCL<=1;
			i2c_state<=6'b001011;
		end
	/****** Acknowledge ********/
		6'b001011:begin
			SCL<=0;
			SDA_direction<=0;
			i2c_state<=6'b001100;
		end
		6'b001100:begin
			SCL<=0;
			i2c_state<=6'b001101;
		end
		6'b001101:begin
			SCL<=1;
			i2c_state<=6'b001110;
		end
		6'b001110:begin
			SCL<=1;
			if (SDA_input==1)begin
				error<=1'b1;
				i2c_state<=6'b100100;
			end
			i2c_state<=6'b001111;
			data_bit_counter<=0;
		end
	/****** separator ********/
		6'b001111:begin
			SCL<=0;
			SDA_output<=0;
			i2c_state<=6'b010000;
		end
		6'b010000:begin
			SCL<=0;
			i2c_state<=6'b010001;
		end
		6'b010001:begin
			SCL<=0;
			SDA_output<=1;
			i2c_state<=6'b010010;
		end
		6'b010010:begin
			SCL<=0;
			SDA_output<=1;
			i2c_state<=6'b010011;
		end
		6'b010011:begin
			SCL<=0;
			SDA_output<=0;
			i2c_state<=6'b010100;
		end
	/****** Send data ********/
		6'b010100:begin
			if(DIR_latched==1'b1)begin
				SDA_direction<=0;
			end else begin
				SDA_direction<=1;
			end
			SCL<=0;
			i2c_state<=6'b010101;
		end
		6'b010101:begin
			SCL<=0;
			if(DIR_latched==1'b0)begin
				SDA_output<=data_internal[7:7];
				data_internal[7:0]<={data_internal[6:0],1'b0};
			end
			data_bit_counter<=data_bit_counter+1;
			i2c_state<=6'b010110;
		end
		6'b010110:begin
			SCL<=1;
			if(DIR_latched==1'b1)begin
				i2c_data_read[15:0]<={8'h0,i2c_data_read[6:0],SDA_input};
			end
			i2c_state<=6'b010111;
		end
		6'b010111:begin
			SCL<=1;
			if(data_bit_counter==4'd8)begin
				i2c_state<=6'b011000;
			end else begin
				i2c_state<=6'b010100;
			end
		end
	/****** Acknowledge ********/
		6'b011000:begin
			// Note: If we read we want to send an ack,
			//       If we write we want to read an ack so it's reversed here
			if(DIR_latched==1'b1)begin
				SDA_direction<=1;
			end else begin
				SDA_direction<=0;
			end
			SCL<=0;
			i2c_state<=6'b011001;
		end
		6'b011001:begin
			SCL<=0;
			if(DIR_latched==1'b1)begin
				SDA_output<=1'b0;
			end
			i2c_state<=6'b011010;
		end
		6'b011010:begin
			SCL<=1;
			i2c_state<=6'b011011;
		end
		6'b011011:begin
			SCL<=1;
			if (SDA_input==1 && DIR_latched==1'b0)begin
				error<=1'b1;
			end
			if(trans_width_latch==1'b1)begin
				i2c_state<=6'b100101;
				data_bit_counter<=4'd0;
			end else
				i2c_state<=6'b011100;
		end
	/****** Send data (16bit) ********/
		6'b100101:begin
			SCL<=0;
			if(DIR_latched==1'b1)begin
				SDA_direction<=0;
			end else begin
				SDA_direction<=1;
			end
			i2c_state<=6'b100110;
		end
		6'b100110:begin
			SCL<=0;
			if(DIR_latched==1'b0)begin
				SDA_output<=data_internal[15:15];
				data_internal[15:8]<={data_internal[14:8],1'b0};
			end
			data_bit_counter<=data_bit_counter+1;
			i2c_state<=6'b100111;
		end
		6'b100111:begin
			SCL<=1;
			if(DIR_latched==1'b1)begin
				i2c_data_read[15:0]<={i2c_data_read[14:8],SDA_input,i2c_data_read[7:0]};
			end
			i2c_state<=6'b101000;
		end
		6'b101000:begin
			SCL<=1;
			if(data_bit_counter==4'd8)begin
				i2c_state<=6'b101001;
			end else
				i2c_state<=6'b100101;
		end
	/****** Acknowledge (16bit) ********/
		6'b101001:begin
			SDA_direction<=0;
			SCL<=0;
			i2c_state<=6'b101010;
		end
		6'b101010:begin
			SCL<=0;
			i2c_state<=6'b101011;
		end
		6'b101011:begin
			SCL<=1;
			i2c_state<=6'b101100;
		end
		6'b101100:begin
			SCL<=1;
			if ( SDA_input==1 && DIR_latched==1'b0 ) begin
				error<=1'b1;
			end
			i2c_state<=6'b011100;
			SDA_direction<=1;
		end
	/****** separator ********/
		6'b011100:begin
			SCL<=0;
			SDA_output<=0;
			i2c_state<=6'b011101;
		end
		6'b011101:begin
			SCL<=0;
			i2c_state<=6'b011110;
		end
		6'b011110:begin
			SCL<=0;
			SDA_output<=1;
			i2c_state<=6'b011111;
		end
		6'b011111:begin
			SCL<=0;
			SDA_output<=1;
			i2c_state<=6'b100000;
		end
		6'b100000:begin
			SCL<=0;
			SDA_output<=0;
			i2c_state<=6'b100001;
		end
	/****** stop bit *******/
		6'b100001:begin
			SCL<=1;
			SDA_output<=0;
			i2c_state<=6'b100010;
		end
		6'b100010:begin
			SCL<=1;
			SDA_output<=1;
			i2c_state<=6'b100011;
		end
		6'b100011:begin
			SCL<=1;
			SDA_output<=1;
			i2c_state<=6'b100100;
			I2C_BUSY<=0;
		end
		6'b100100:begin
			if(I2C_TRANSACT==1)begin
				I2C_BUSY<=1;
				i2c_state<=0;
				data_internal<=i2c_data_write;
				address_internal<=address;
				trans_width_latch<=transact_width;
				DIR_latched<=DIR;
				error<=1'b0;
			end
		end
		default:begin
			SCL<=0;
			SDA_output<=0;
		end

	endcase
end

endmodule
