tinyriscv/rtl/perips/i2c/i2c_slave.sv

271 lines
9.6 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_slave (
input logic clk_i,
input logic rst_ni,
input logic enable_i,
input logic [7:0] slave_addr_i,
input logic [7:0] data_i,
output logic [7:0] addr_o,
output logic read_o,
output logic valid_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 scl_raise_edge, scl_fall_edge;
logic sda_raise_edge, sda_fall_edge;
logic sda, scl;
logic [5:0] state_d, state_q;
logic sda_oe_d, sda_oe_q;
logic scl_oe_d, scl_oe_q;
logic sda_d, sda_q;
logic scl_d, scl_q;
logic [7:0] data_d, data_q;
logic [7:0] addr_d, addr_q;
logic read_d, read_q;
logic send_ack_d, send_ack_q;
logic [3:0] scl_raise_edge_cnt_d, scl_raise_edge_cnt_q;
logic valid_d, valid_q;
logic op_read_d, op_read_q;
always_comb begin
state_d = state_q;
sda_oe_d = sda_oe_q;
sda_d = sda_q;
scl_oe_d = scl_oe_q;
scl_d = scl_q;
data_d = data_q;
addr_d = addr_q;
read_d = read_q;
send_ack_d = send_ack_q;
scl_raise_edge_cnt_d = scl_raise_edge_cnt_q;
valid_d = valid_q;
op_read_d = op_read_q;
if (!enable_i) begin
state_d = S_IDLE;
sda_oe_d = 1'b0;
sda_d = 1'b0;
scl_oe_d = 1'b0;
scl_d = 1'b0;
end else begin
case (state_q)
S_IDLE: begin
sda_oe_d = 1'b0;
sda_d = 1'b0;
valid_d = 1'b0;
read_d = 1'b0;
// START信号
if (scl_i && sda_fall_edge) begin
state_d = S_ADDR;
send_ack_d = 1'b0;
scl_raise_edge_cnt_d = '0;
op_read_d = 1'b0;
end
end
S_ADDR: begin
// SCL上升沿采数据
if (scl_raise_edge) begin
scl_raise_edge_cnt_d = scl_raise_edge_cnt_q + 1'b1;
if (scl_raise_edge_cnt_q < 4'd8) begin
data_d = {data_q[6:0], sda};
end
end
// 下降沿发ACK信号
if ((scl_raise_edge_cnt_q == 4'd8) && scl_fall_edge) begin
// 地址对得上则回ACK信号
if (slave_addr_i[7:1] == data_q[7:1]) begin
sda_oe_d = 1'b1;
sda_d = 1'b0;
if (data_q[0]) begin
read_d = 1'b1;
end
// 否则回到S_IDLE状态
end else begin
state_d = S_IDLE;
end
end
// 释放SDA
if ((scl_raise_edge_cnt_q == 4'd9) && scl_fall_edge) begin
sda_oe_d = 1'b0;
scl_raise_edge_cnt_d = '0;
// 读
if (data_q[0]) begin
state_d = S_DATA;
op_read_d = 1'b1;
sda_oe_d = 1'b1;
data_d = {data_i[6:0], 1'b1};
sda_d = data_i[7];
// 写
end else begin
state_d = S_REG;
op_read_d = 1'b0;
end
end
// 收到STOP信号
if (scl_i && sda_raise_edge) begin
state_d = S_IDLE;
end
end
S_REG: begin
// SCL上升沿采数据
if (scl_raise_edge) begin
scl_raise_edge_cnt_d = scl_raise_edge_cnt_q + 1'b1;
if (scl_raise_edge_cnt_q < 4'd8) begin
data_d = {data_q[6:0], sda};
end
end
// 下降沿发ACK信号
if ((scl_raise_edge_cnt_q == 4'd8) && scl_fall_edge) begin
sda_oe_d = 1'b1;
sda_d = 1'b0;
addr_d = data_q;
end
// 释放SDA
if ((scl_raise_edge_cnt_q == 4'd9) && scl_fall_edge) begin
sda_oe_d = 1'b0;
scl_raise_edge_cnt_d = '0;
state_d = S_DATA;
op_read_d = 1'b0;
end
// 收到STOP信号
if (scl_i && sda_raise_edge) begin
state_d = S_IDLE;
end
end
S_DATA: begin
if (scl_raise_edge) begin
scl_raise_edge_cnt_d = scl_raise_edge_cnt_q + 1'b1;
if ((!op_read_q) && (scl_raise_edge_cnt_q < 4'd8)) begin
data_d = {data_q[6:0], sda};
end
end else if (scl_fall_edge) begin
if (op_read_q && (scl_raise_edge_cnt_q < 4'd8)) begin
sda_oe_d = 1'b1;
data_d = {data_q[6:0], 1'b1};
sda_d = data_q[7];
end
end
// 下降沿发ACK信号
if ((scl_raise_edge_cnt_q == 4'd8) && scl_fall_edge) begin
sda_oe_d = 1'b1;
// 回NACK
if (op_read_q) begin
sda_d = 1'b1;
// 回ACK
end else begin
sda_d = 1'b0;
end
valid_d = 1'b1;
end
// 释放SDA
if ((scl_raise_edge_cnt_q == 4'd9) && scl_fall_edge) begin
sda_oe_d = 1'b0;
op_read_d = 1'b0;
end
// 收到STOP信号
if (scl_i && sda_raise_edge) begin
state_d = S_IDLE;
end
end
default: ;
endcase
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
state_q <= S_IDLE;
sda_oe_q <= 1'b0;
sda_q <= 1'b0;
scl_oe_q <= 1'b0;
scl_q <= 1'b0;
data_q <= '0;
addr_q <= '0;
read_q <= 1'b0;
send_ack_q <= 1'b0;
scl_raise_edge_cnt_q <= '0;
valid_q <= 1'b0;
op_read_q <= 1'b0;
end else begin
state_q <= state_d;
sda_oe_q <= sda_oe_d;
sda_q <= sda_d;
scl_oe_q <= scl_oe_d;
scl_q <= scl_d;
data_q <= data_d;
addr_q <= addr_d;
read_q <= read_d;
send_ack_q <= send_ack_d;
scl_raise_edge_cnt_q <= scl_raise_edge_cnt_d;
valid_q <= valid_d;
op_read_q <= op_read_d;
end
end
assign scl_oe_o = scl_oe_q;
assign scl_o = scl_q;
assign sda_oe_o = sda_oe_q;
assign sda_o = sda_q;
assign data_o = data_q;
assign addr_o = addr_q;
assign read_o = read_q;
assign valid_o = valid_q;
// SCL信号沿检测
edge_detect scl_edge_detect (
.clk_i (clk_i),
.rst_ni (rst_ni),
.sig_i (scl_i),
.sig_o (scl),
.re_o (scl_raise_edge),
.fe_o (scl_fall_edge)
);
// SDA信号沿检测
edge_detect sda_edge_detect (
.clk_i (clk_i),
.rst_ni (rst_ni),
.sig_i (sda_i),
.sig_o (sda),
.re_o (sda_raise_edge),
.fe_o (sda_fall_edge)
);
endmodule