9086/system/peripherals/I2C_driver.v

348 lines
6.8 KiB
Verilog

/* 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 wire 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 */
input wire ignore_ack,
output reg [15:0] i2c_data_read=16'h4141
);
//assign i2c_data_read=16'h0042;
reg DIR_latched;
reg SDA; // TODO make SDA_output a reg and rename everything
assign SDA_output=SDA;
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<=1;
SCL<=1;
i2c_state<=6'b000001;
end
6'b000001:begin
SDA<=0;
SCL<=1;
i2c_state<=6'b000010;
end
6'b000010:begin
SDA<=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<=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<=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
i2c_state<=6'b111111; //TODO: Don't freeze the entire driver if a single error occurs!! (here and on the second ack as well)
end else begin
i2c_state<=6'b001111;
data_bit_counter<=0;
end
end
/****** separator ********/
6'b001111:begin
SCL<=0;
SDA<=0;
i2c_state<=6'b010000;
end
6'b010000:begin
SCL<=0;
i2c_state<=6'b010001;
end
6'b010001:begin
SCL<=0;
SDA<=1;
i2c_state<=6'b010010;
end
6'b010010:begin
SCL<=0;
SDA<=1;
i2c_state<=6'b010011;
end
6'b010011:begin
SCL<=0;
SDA<=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<=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<=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==0||DIR_latched==1'b1)begin
if(trans_width_latch==1'b1)begin
i2c_state<=6'b100101;
data_bit_counter<=4'd0;
end else
i2c_state<=6'b011100;
end else begin
i2c_state<=6'b111111;
end
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<=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==0||ignore_ack==1'b1)begin
i2c_state<=6'b011100;
SDA_direction<=1;
end else begin
i2c_state<=6'b111111;
end
end
/****** separator ********/
6'b011100:begin
SCL<=0;
SDA<=0;
i2c_state<=6'b011101;
end
6'b011101:begin
SCL<=0;
i2c_state<=6'b011110;
end
6'b011110:begin
SCL<=0;
SDA<=1;
i2c_state<=6'b011111;
end
6'b011111:begin
SCL<=0;
SDA<=1;
i2c_state<=6'b100000;
end
6'b100000:begin
SCL<=0;
SDA<=0;
i2c_state<=6'b100001;
end
/****** stop bit *******/
6'b100001:begin
SCL<=1;
SDA<=0;
i2c_state<=6'b100010;
end
6'b100010:begin
SCL<=1;
SDA<=1;
i2c_state<=6'b100011;
end
6'b100011:begin
SCL<=1;
SDA<=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;
end
end
default:begin
SCL<=0;
SDA<=0;
end
endcase
end
endmodule