289 lines
11 KiB
Systemverilog
289 lines
11 KiB
Systemverilog
/*
|
||
Copyright 2021 Blue Liang, liangkangnan@163.com
|
||
|
||
Licensed under the Apache License, Version 2.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
You may obtain a copy of the License at
|
||
|
||
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
||
Unless required by applicable law or agreed to in writing, software
|
||
distributed under the License is distributed on an "AS IS" BASIS,
|
||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
*/
|
||
|
||
module i2c_master (
|
||
input logic clk_i,
|
||
input logic rst_ni,
|
||
|
||
input logic enable_i,
|
||
input logic [15:0] div_ratio_i,
|
||
input logic read_i,
|
||
input logic [7:0] slave_addr_i,
|
||
input logic [7:0] slave_reg_i,
|
||
input logic [7:0] slave_data_i,
|
||
input logic start_i,
|
||
output logic ready_o,
|
||
output logic error_o,
|
||
output logic [7:0] data_o,
|
||
|
||
input logic scl_i,
|
||
output logic scl_o,
|
||
output logic scl_oe_o,
|
||
input logic sda_i,
|
||
output logic sda_o,
|
||
output logic sda_oe_o
|
||
);
|
||
|
||
localparam S_IDLE = 6'b000001;
|
||
localparam S_START = 6'b000010;
|
||
localparam S_ADDR = 6'b000100;
|
||
localparam S_REG = 6'b001000;
|
||
localparam S_DATA = 6'b010000;
|
||
localparam S_STOP = 6'b100000;
|
||
|
||
logic tick;
|
||
|
||
logic error_d, error_q;
|
||
logic [7:0] data_d, data_q;
|
||
logic [4:0] edge_cnt_d, edge_cnt_q;
|
||
logic [7:0] shift_reg_d, shift_reg_q;
|
||
logic [5:0] state_d, state_q;
|
||
logic sda_d, sda_q;
|
||
logic scl_d, scl_q;
|
||
logic sda_oe_d, sda_oe_q;
|
||
logic scl_oe_d, scl_oe_q;
|
||
logic op_read_d, op_read_q;
|
||
|
||
|
||
always_comb begin
|
||
state_d = state_q;
|
||
shift_reg_d = shift_reg_q;
|
||
scl_d = scl_q;
|
||
sda_oe_d = sda_oe_q;
|
||
scl_oe_d = scl_oe_q;
|
||
sda_d = sda_q;
|
||
edge_cnt_d = edge_cnt_q;
|
||
data_d = data_q;
|
||
error_d = error_q;
|
||
op_read_d = op_read_q;
|
||
|
||
if (!enable_i) begin
|
||
sda_d = 1'b0;
|
||
sda_oe_d = 1'b0;
|
||
scl_d = 1'b0;
|
||
scl_oe_d = 1'b0;
|
||
state_d = S_IDLE;
|
||
end else begin
|
||
case (state_q)
|
||
S_IDLE: begin
|
||
sda_d = 1'b1;
|
||
sda_oe_d = 1'b1;
|
||
scl_d = 1'b1;
|
||
scl_oe_d = 1'b1;
|
||
if (start_i) begin
|
||
state_d = S_START;
|
||
error_d = 1'b0;
|
||
data_d = '0;
|
||
op_read_d = 1'b0;
|
||
end
|
||
end
|
||
|
||
S_START: begin
|
||
if (tick) begin
|
||
sda_d = 1'b0;
|
||
edge_cnt_d = '0;
|
||
state_d = S_ADDR;
|
||
shift_reg_d = {slave_addr_i[7:1], op_read_q};
|
||
end
|
||
end
|
||
|
||
S_ADDR: begin
|
||
if (tick) begin
|
||
scl_d = ~scl_q;
|
||
edge_cnt_d = edge_cnt_q + 1'b1;
|
||
// 下降沿释放SDA,准备接收ACK
|
||
if (edge_cnt_q == 5'd16) begin
|
||
sda_oe_d = 1'b0;
|
||
// 上升沿接收ACK
|
||
end else if (edge_cnt_q == 5'd17) begin
|
||
// NACK
|
||
if (sda_i) begin
|
||
error_d = 1'b1;
|
||
// ACK
|
||
end else begin
|
||
if (op_read_q) begin
|
||
state_d = S_DATA;
|
||
end else begin
|
||
state_d = S_REG;
|
||
shift_reg_d = slave_reg_i;
|
||
end
|
||
edge_cnt_d = '0;
|
||
end
|
||
// 最后一个下降沿
|
||
end else if (edge_cnt_q == 5'd18) begin
|
||
sda_d = 1'b0;
|
||
sda_oe_d = 1'b1;
|
||
// 最后一个上升沿
|
||
end else if (edge_cnt_q == 5'd19) begin
|
||
state_d = S_STOP;
|
||
end else begin
|
||
// 发数据
|
||
if (scl_q) begin
|
||
// 左移一位(MSB first)
|
||
shift_reg_d = {shift_reg_q[6:0], 1'b1};
|
||
sda_d = shift_reg_q[7];
|
||
sda_oe_d = 1'b1;
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
S_REG: begin
|
||
if (tick) begin
|
||
scl_d = ~scl_q;
|
||
edge_cnt_d = edge_cnt_q + 1'b1;
|
||
// 下降沿释放SDA,准备接收ACK
|
||
if (edge_cnt_q == 5'd16) begin
|
||
sda_oe_d = 1'b0;
|
||
// 上升沿接收ACK
|
||
end else if (edge_cnt_q == 5'd17) begin
|
||
// NACK
|
||
if (sda_i) begin
|
||
error_d = 1'b1;
|
||
// ACK
|
||
end else begin
|
||
// 写操作,转去S_DATA状态
|
||
if (!read_i) begin
|
||
state_d = S_DATA;
|
||
shift_reg_d = slave_data_i;
|
||
edge_cnt_d = '0;
|
||
// 读操作,发送STOP信号
|
||
end else begin
|
||
op_read_d = 1'b1;
|
||
end
|
||
end
|
||
// 最后一个下降沿
|
||
end else if (edge_cnt_q == 5'd18) begin
|
||
sda_d = 1'b0;
|
||
sda_oe_d = 1'b1;
|
||
// 最后一个上升沿
|
||
end else if (edge_cnt_q == 5'd19) begin
|
||
state_d = S_STOP;
|
||
end else begin
|
||
// 发数据
|
||
if (scl_q) begin
|
||
// 左移一位(MSB first)
|
||
shift_reg_d = {shift_reg_q[6:0], 1'b1};
|
||
sda_d = shift_reg_q[7];
|
||
sda_oe_d = 1'b1;
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
S_DATA: begin
|
||
if (tick) begin
|
||
scl_d = ~scl_q;
|
||
edge_cnt_d = edge_cnt_q + 1'b1;
|
||
// 下降沿释放SDA,准备接收ACK
|
||
if (edge_cnt_q == 5'd16) begin
|
||
sda_oe_d = 1'b0;
|
||
// 上升沿接收ACK
|
||
end else if (edge_cnt_q == 5'd17) begin
|
||
// NACK
|
||
if (sda_i ^ op_read_q) begin
|
||
error_d = 1'b1;
|
||
// ACK
|
||
end else begin
|
||
error_d = 1'b0;
|
||
end
|
||
op_read_d = 1'b0;
|
||
// 最后一个下降沿
|
||
end else if (edge_cnt_q == 5'd18) begin
|
||
sda_d = 1'b0;
|
||
sda_oe_d = 1'b1;
|
||
// 最后一个上升沿
|
||
end else if (edge_cnt_q == 5'd19) begin
|
||
state_d = S_STOP;
|
||
end else begin
|
||
// 读数据
|
||
if (op_read_q && (~scl_q)) begin
|
||
data_d = {data_q[6:0], sda_i};
|
||
// 发数据
|
||
end else if ((~op_read_q) && scl_q) begin
|
||
// 左移一位(MSB first)
|
||
shift_reg_d = {shift_reg_q[6:0], 1'b1};
|
||
sda_d = shift_reg_q[7];
|
||
sda_oe_d = 1'b1;
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
S_STOP: begin
|
||
if (tick) begin
|
||
sda_d = 1'b1;
|
||
if (op_read_q) begin
|
||
state_d = S_START;
|
||
end else begin
|
||
state_d = S_IDLE;
|
||
end
|
||
end
|
||
end
|
||
|
||
default: ;
|
||
endcase
|
||
end
|
||
end
|
||
|
||
assign scl_o = scl_q;
|
||
assign scl_oe_o = scl_oe_q;
|
||
assign sda_o = sda_q;
|
||
assign sda_oe_o = sda_oe_q;
|
||
assign data_o = data_q;
|
||
assign ready_o = (state_q == S_IDLE);
|
||
assign error_o = error_q;
|
||
|
||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||
if (!rst_ni) begin
|
||
state_q <= S_IDLE;
|
||
shift_reg_q <= '0;
|
||
scl_q <= 1'b0;
|
||
sda_q <= 1'b0;
|
||
sda_oe_q <= 1'b0;
|
||
scl_oe_q <= 1'b0;
|
||
edge_cnt_q <= '0;
|
||
data_q <= '0;
|
||
error_q <= 1'b0;
|
||
op_read_q <= 1'b0;
|
||
end else begin
|
||
state_q <= state_d;
|
||
shift_reg_q <= shift_reg_d;
|
||
scl_q <= scl_d;
|
||
sda_q <= sda_d;
|
||
sda_oe_q <= sda_oe_d;
|
||
scl_oe_q <= scl_oe_d;
|
||
edge_cnt_q <= edge_cnt_d;
|
||
data_q <= data_d;
|
||
error_q <= error_d;
|
||
op_read_q <= op_read_d;
|
||
end
|
||
end
|
||
|
||
logic [15:0] ratio = {1'b0, div_ratio_i[15:1]};
|
||
|
||
clk_div #(
|
||
.RATIO_WIDTH(16)
|
||
) u_clk_div (
|
||
.clk_i(clk_i),
|
||
.rst_ni(rst_ni || (~((state_q == S_IDLE) && start_i))),
|
||
.en_i(state_q != S_IDLE),
|
||
.ratio_i(ratio),
|
||
.clk_o(tick)
|
||
);
|
||
|
||
endmodule
|