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
|