237 lines
7.5 KiB
Verilog
237 lines
7.5 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
module i2c_master (
|
|
input clk,
|
|
input rst_n,
|
|
input [6:0] addr,
|
|
input rw,
|
|
input [7:0] data_in,
|
|
input start_tx,
|
|
output reg [7:0] data_out,
|
|
output reg busy,
|
|
output reg ack_err,
|
|
inout scl,
|
|
inout sda
|
|
);
|
|
localparam IDLE = 4'd0;
|
|
localparam START_A = 4'd1;
|
|
localparam START_B = 4'd2;
|
|
localparam ADDR_DRIVE = 4'd3;
|
|
localparam ADDR_SAMPLE = 4'd4;
|
|
localparam ACK1 = 4'd5;
|
|
localparam WRITE_DRIVE = 4'd6;
|
|
localparam WRITE_SAMPLE = 4'd7;
|
|
localparam READ = 4'd8;
|
|
localparam ACK2 = 4'd9;
|
|
localparam STOP_A = 4'd10;
|
|
localparam STOP_B = 4'd11;
|
|
localparam CLOCK_STRETCH = 4'd12;
|
|
localparam ARBITRATION_LOST = 4'd13;
|
|
localparam ERROR = 4'd14;
|
|
|
|
reg [3:0] state;
|
|
reg [2:0] bit_count;
|
|
reg [7:0] tx_shift;
|
|
reg [7:0] rx_shift;
|
|
reg scl_drive_low;
|
|
reg sda_drive_low;
|
|
reg latched_rw;
|
|
|
|
wire scl_in;
|
|
wire sda_in;
|
|
|
|
assign scl = scl_drive_low ? 1'b0 : 1'bz;
|
|
assign sda = sda_drive_low ? 1'b0 : 1'bz;
|
|
assign scl_in = scl;
|
|
assign sda_in = sda;
|
|
|
|
always @(posedge clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
state <= IDLE;
|
|
bit_count <= 3'd0;
|
|
tx_shift <= 8'd0;
|
|
rx_shift <= 8'd0;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
latched_rw <= 1'b0;
|
|
data_out <= 8'd0;
|
|
busy <= 1'b0;
|
|
ack_err <= 1'b0;
|
|
end else begin
|
|
case (state)
|
|
IDLE: begin
|
|
busy <= 1'b0;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
bit_count <= 3'd7;
|
|
rx_shift <= 8'd0;
|
|
if (start_tx) begin
|
|
busy <= 1'b1;
|
|
ack_err <= 1'b0;
|
|
tx_shift <= {addr, rw};
|
|
latched_rw <= rw;
|
|
state <= START_A;
|
|
end
|
|
end
|
|
|
|
START_A: begin
|
|
busy <= 1'b1;
|
|
if (scl_in && sda_in) begin
|
|
sda_drive_low <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
state <= START_B;
|
|
end else begin
|
|
ack_err <= 1'b1;
|
|
state <= ERROR;
|
|
end
|
|
end
|
|
|
|
START_B: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b1;
|
|
bit_count <= 3'd7;
|
|
sda_drive_low <= ~tx_shift[7];
|
|
state <= ADDR_DRIVE;
|
|
end
|
|
|
|
ADDR_DRIVE: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= ~tx_shift[7];
|
|
state <= ADDR_SAMPLE;
|
|
end
|
|
|
|
ADDR_SAMPLE: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
if (tx_shift[7] && !sda_in) begin
|
|
ack_err <= 1'b1;
|
|
state <= ARBITRATION_LOST;
|
|
end else if (bit_count == 3'd0) begin
|
|
sda_drive_low <= 1'b0;
|
|
state <= ACK1;
|
|
end else begin
|
|
tx_shift <= {tx_shift[6:0], 1'b0};
|
|
bit_count <= bit_count - 1'b1;
|
|
scl_drive_low <= 1'b1;
|
|
sda_drive_low <= ~tx_shift[6];
|
|
state <= ADDR_DRIVE;
|
|
end
|
|
end
|
|
|
|
ACK1: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
bit_count <= 3'd7;
|
|
if (!sda_in) begin
|
|
if (latched_rw) begin
|
|
rx_shift <= 8'd0;
|
|
state <= READ;
|
|
end else begin
|
|
tx_shift <= data_in;
|
|
sda_drive_low <= ~data_in[7];
|
|
state <= WRITE_DRIVE;
|
|
end
|
|
end else begin
|
|
ack_err <= 1'b1;
|
|
state <= ERROR;
|
|
end
|
|
end
|
|
|
|
WRITE_DRIVE: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= ~tx_shift[7];
|
|
state <= WRITE_SAMPLE;
|
|
end
|
|
|
|
WRITE_SAMPLE: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
if (tx_shift[7] && !sda_in) begin
|
|
ack_err <= 1'b1;
|
|
state <= ARBITRATION_LOST;
|
|
end else if (bit_count == 3'd0) begin
|
|
sda_drive_low <= 1'b0;
|
|
state <= ACK2;
|
|
end else begin
|
|
tx_shift <= {tx_shift[6:0], 1'b0};
|
|
bit_count <= bit_count - 1'b1;
|
|
scl_drive_low <= 1'b1;
|
|
sda_drive_low <= ~tx_shift[6];
|
|
state <= WRITE_DRIVE;
|
|
end
|
|
end
|
|
|
|
READ: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
rx_shift <= {rx_shift[6:0], sda_in};
|
|
if (bit_count == 3'd0) begin
|
|
data_out <= {rx_shift[6:0], sda_in};
|
|
state <= ACK2;
|
|
end else begin
|
|
bit_count <= bit_count - 1'b1;
|
|
end
|
|
end
|
|
|
|
ACK2: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
if (!latched_rw && sda_in) begin
|
|
ack_err <= 1'b1;
|
|
end
|
|
state <= STOP_A;
|
|
end
|
|
|
|
STOP_A: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b1;
|
|
if (!scl_in) begin
|
|
state <= CLOCK_STRETCH;
|
|
end else begin
|
|
state <= STOP_B;
|
|
end
|
|
end
|
|
|
|
CLOCK_STRETCH: begin
|
|
busy <= 1'b1;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b1;
|
|
if (scl_in) begin
|
|
state <= STOP_B;
|
|
end
|
|
end
|
|
|
|
STOP_B: begin
|
|
busy <= 1'b0;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
state <= IDLE;
|
|
end
|
|
|
|
ARBITRATION_LOST: begin
|
|
busy <= 1'b0;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
ack_err <= 1'b1;
|
|
state <= IDLE;
|
|
end
|
|
|
|
default: begin
|
|
busy <= 1'b0;
|
|
scl_drive_low <= 1'b0;
|
|
sda_drive_low <= 1'b0;
|
|
ack_err <= 1'b1;
|
|
state <= IDLE;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
endmodule
|