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