基于一个github项目自己修改编写的,对于一些信号进行了整理,方便读者的理解。已通过FPGA上板测试,写操作无误。
module i2c_slave (scl,sda,rst_n,reg_data_rd,reg_data_wr,reg_addr,wen);
input scl;//rx from FPGA's peripheral
inout sda;
input rst_n;
input [7:0] reg_data_rd;//rx from regs
output [7:0] reg_data_wr;//tx to regs
output [1:0] reg_addr;//tx to regs
output wen;//tx to regs
parameter I2C_ADDR = 7'h27;//device's static addr
inout setting
wire sda_in;
reg output_ctrl;
assign sda_in = sda ;
assign sda = output_ctrl ? 1'bz : 1'b0;
start detect
reg start_detect;//when SCL keep high,SDA pull low
reg start_resetter;
wire start_rst;
assign start_rst = ~rst_n | start_resetter;
always@(posedge start_rst or negedge sda)begin//trigger condition
if(start_rst)
start_detect <= 1'b0;
else
start_detect <= scl;
end
always@(posedge scl or negedge rst_n)begin
if(!rst_n)
start_resetter <= 1'b0;
else
start_resetter <= start_detect;
end
stop detect
reg stop_detect;//when SCL keep high,SDA pull high
reg stop_resetter;//
wire stop_rst;
assign stop_rst = ~rst_n | stop_resetter;
always@(posedge stop_rst or posedge sda)begin
if(stop_rst)
stop_detect <= 1'b0;
else
stop_detect <= scl;
end
always@(posedge scl or negedge rst_n)begin
if(!rst_n)
stop_resetter <= 1'b0;
else
stop_resetter <= stop_detect;
end
counting bit number
reg [3:0] bit_cnt;
wire lsb_bit = (bit_cnt==4'h7) && !start_detect;//the LSB bit is the 8th bit sent
wire ack_bit = (bit_cnt==4'h8) && !start_detect;//the ACK bit is the 9th bit sent
always@(negedge scl)begin
if(ack_bit || start_detect)
bit_cnt <= 4'h0;
else
bit_cnt <= bit_cnt + 4'h1;
end
address detect
reg [7:0] rx_shift;
always@(posedge scl)begin
if(!ack_bit)
rx_shift <= {rx_shift[6:0],sda};
else
rx_shift <= rx_shift;
end
wire addr_detect;
wire rd_or_wr;//(static addr + 'rd_or_wr' + ack/nack )
assign addr_detect = (rx_shift[7:1]==I2C_ADDR) ? 1'b1 : 1'b0 ;
assign rd_or_wr = rx_shift[0];
master's ack/nack detect
reg sda_from_mst;
always@(posedge scl)begin
if(ack_bit)
sda_from_mst <= sda_in;
end
FSM for RX from SDA and TX to reg_core
localparam IDLE = 3'b000;//0
localparam DEV_ADDR = 3'b001;//1
localparam READ = 3'b011;//3
localparam IDX_PTR = 3'b101;//5
localparam WRITE = 3'b111;//7
reg [2:0] cstate;
reg [2:0] nstate;
always@(negedge scl or negedge rst_n)begin
if(!rst_n )
cstate <= IDLE;
else if(start_detect)//for restart function
cstate <= DEV_ADDR;
else if(stop_detect)
cstate <= IDLE;
else if(ack_bit)//9bit done, state change
cstate <= nstate;
end
always@(*)begin
case(cstate)
IDLE:begin
if(start_detect)
nstate <= DEV_ADDR;
else
nstate <= IDLE;
end
DEV_ADDR:begin
if(!addr_detect)
nstate <= IDLE;
else if(rd_or_wr)//write --> bit = 0 ;read --> bit = 1
nstate <= READ;
else
nstate <= IDX_PTR;
end
READ:begin
if(sda_from_mst)// for NACK
nstate <= IDLE;
else
nstate <= READ;
end
IDX_PTR:begin
nstate <= WRITE;
end
WRITE:begin
nstate <= WRITE;
end
default:begin
nstate <= IDLE;
end
endcase
end
//regs' pointer
reg [1:0] pointer;
always@(negedge scl or negedge rst_n)begin
if(!rst_n)
pointer <= 2'd0;
else if(stop_detect)
pointer <= 2'd0;
else begin
case(cstate)
IDLE:begin
pointer <= 2'd0;
end
DEV_ADDR:begin
if(ack_bit)
pointer <= pointer + 1'b1;//when 8 bit finished, will restore the tx_shift,so must positioning the pointer early
end
READ:begin
if(ack_bit)
pointer <= pointer + 1'b1;//姣忎紶瀹?8bit鏁版嵁鍚庯紝鍦板潃+1锛屼緵鍐呴儴绉讳綅瀵勫瓨鍣ㄦ彁鍓嶅瓨鍌ㄦ暟鎹??
end
IDX_PTR:begin
if(ack_bit)
pointer <= rx_shift[1:0];//as a tester,shall set the first DATA = 8'b0;
end
WRITE:begin
if(ack_bit)
pointer <= pointer + 1'b1;
end
default:begin
pointer <= 2'd0;
end
endcase
end
end
//slave tx data to bus && ack/nack
reg [7:0] tx_shift;
always@(negedge scl)begin
if(lsb_bit)
tx_shift <= reg_data_rd;//when 8 bit finished, restore the tx_shift
else
tx_shift <= {tx_shift[6:0],1'b0};//when 8 bit unfinished, shift bit for tx
end
always@(negedge scl or negedge rst_n)begin
if(!rst_n)
output_ctrl <= 1'b1;
else begin
case(cstate)
IDLE:begin
output_ctrl <= 1'b1;
end
DEV_ADDR:begin
if(lsb_bit)begin//after negedge sample lsb_bit, slave active on sda bus
if(addr_detect)
output_ctrl <= 1'b0;//indicate ack
else
output_ctrl <= 1'b1;
end
else if (ack_bit)begin
if(addr_detect && rd_or_wr)
output_ctrl <= tx_shift[7]; Deliver the first bit of DATA in DEV_ADDR. !!important!!
else
output_ctrl <= 1'b1;
end
else begin
output_ctrl <= 1'b1;
end
end
READ:begin
if(ack_bit )begin//consider 9bit's master ACK/NACK
if(!sda_from_mst)
output_ctrl <= tx_shift[7];//if master ACK
else
output_ctrl <= 1'b1;//if master NACK
end
else begin//other bit
output_ctrl <= tx_shift[7];//MSB fisrt
end
end
IDX_PTR:begin
if(lsb_bit)
output_ctrl <= 1'b0;//indicate ack
else
output_ctrl <= 1'b1;
end
WRITE:begin
if(lsb_bit)
output_ctrl <= 1'b0;//indicate ack
else
output_ctrl <= 1'b1;
end
default:begin
output_ctrl <= 1'b1;
end
endcase
end
end
output assignment
assign reg_addr = pointer;
assign wen = (cstate == WRITE) && ack_bit;
assign reg_data_wr = rx_shift;
endmodule