271 lines
9.6 KiB
Systemverilog
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
|