tinyriscv/rtl/perips/i2c/i2c_master.sv

289 lines
11 KiB
Systemverilog
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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