文章目录
- 前言
- [1. 结构](#1. 结构)
- 2.代码
-
- [2.1 iic_slave.v](#2.1 iic_slave.v)
- [2.2 sync.v](#2.2 sync.v)
- [2.3 wr_fsm.v](#2.3 wr_fsm.v)
-
- [2.3.1 状态机状态解释](#2.3.1 状态机状态解释)
- [2.4 ram.v](#2.4 ram.v)
- [3. 波形展示](#3. 波形展示)
- [4. 建议](#4. 建议)
- [5. 资料总结](#5. 资料总结)
前言
首先就不啰嗦iic协议了,网上有不少资料都是叙述此协议的。
下面将是我本次设计的一些局部设计汇总,如果对读者有借鉴意义那最好,如果没有的话也无所谓,互相交流而已。(这是我早期的版本,注释比较少,代码编写比较混乱,读者自便)
希望读者发现问题可在下方留言,我会及时回答或者修改。
1. 结构
顶层结构图
master结构图
slave结构图
2.代码
2.1 iic_slave.v
c
`timescale 1ns/1ps
module iic_slave (
input rstn,
input clk,
input scl,
inout sda,
input [7:0] q, // RAM data to slave
output wen,
output [7:0] d, // Slave data to RAM
output [7:0] a // Slave address to RAM
);
// Internal signals
wire sync_scl_1;
wire sync_sda_1;
wire sda_posedge;
wire sda_negedge;
wire scl_posedge;
wire scl_negedge;
wire sda_out;
wire sda_oen;
// Three-state gate for SDA
assign sda = (sda_oen) ? sda_out : 1'bz;
// Instantiate sync module
sync sync (
.clk(clk),
.rstn(rstn),
.scl(scl),
.sda(sda),
.sync_scl_1(sync_scl_1),
.sync_sda_1(sync_sda_1),
.sda_posedge(sda_posedge),
.sda_negedge(sda_negedge),
.scl_posedge(scl_posedge),
.scl_negedge(scl_negedge)
);
// Instantiate RAM module
ram ram (
.clk(clk),
.rstn(rstn),
.d(d),
.a(a),
.q(q),
.wen(wen)
);
// Instantiate write FSM module
wr_fsm wr_fsm (
.clk(clk),
.rstn(rstn),
.sync_scl_1(sync_scl_1),
.sync_sda_1(sync_sda_1),
.scl_posedge(scl_posedge),
.scl_negedge(scl_negedge),
.sda_posedge(sda_posedge),
.sda_negedge(sda_negedge),
.d(d),
.a(a),
.q(q),
.wen(wen),
.sda_out(sda_out),
.sda_oen(sda_oen)
);
endmodule
2.2 sync.v
c
`timescale 1ns/1ps
module sync (
rstn,
clk,
scl,
sda,
sync_scl_1,
sync_sda_1,
sda_posedge,
sda_negedge,
scl_posedge,
scl_negedge
);
input rstn;
input clk;
input scl;
input sda;
output sync_scl_1;
output sync_sda_1;
output sda_posedge;
output sda_negedge;
output scl_posedge;
output scl_negedge;
reg sync_scl_1;
reg sync_sda_1;
reg sync_scl_0;
reg sync_sda_0;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
sync_scl_1 <= 1'b0;
sync_sda_1 <= 1'b0;
sync_scl_0 <= 1'b0;
sync_sda_0 <= 1'b0;
end else begin
sync_scl_0 <= scl;
sync_sda_0 <= sda;
sync_scl_1 <= sync_scl_0;
sync_sda_1 <= sync_sda_0;
end
end
assign sda_posedge = (sync_sda_0) & (~sync_sda_1);
assign sda_negedge = (~sync_sda_0) & (sync_sda_1);
assign scl_posedge = (sync_scl_0) & (~sync_scl_1);
assign scl_negedge = (~sync_scl_0) & (sync_scl_1);
endmodule
2.3 wr_fsm.v
c
`timescale 1ns/1ps
module wr_fsm (
rstn,
clk,
sync_scl_1,
sync_sda_1,
scl_posedge,
scl_negedge,
sda_posedge,
sda_negedge,
q,
d,
a,
wen,
sda_out,
sda_oen
);
input rstn, clk;
input sync_scl_1;
input sync_sda_1;
input scl_posedge;
input scl_negedge;
input sda_posedge;
input sda_negedge;
input [7:0] q;
output [7:0] d;
output [7:0] a;
output wen;
output sda_out;
output sda_oen;
reg wen; // write and read flags reg
reg [7:0] scl_cnt; // clk delay counter
reg [3:0] bit_cnt; // valid transfer byte
reg [7:0] a; // a = save word addr, shift data to ram
reg sda_out; // data out reg
reg sda_oen; // three state gate flag bit
reg [7:0] save_ctrl; // store ctrl word
reg [7:0] save_q_data; // store data of q
wire [7:0] q;
parameter slave_addr = 7'b1101101; // parameter slave addr
parameter scl_cnt_max = 60-1;
reg [3:0] state; // state transform
parameter idle = 4'd0,
w_start = 4'd1,
w_ctrl = 4'd2,
ack1 = 4'd3,
w_addr = 4'd4,
ack2 = 4'd5,
w_data = 4'd6,
ack3 = 4'd7,
r_start = 4'd8,
r_ctrl = 4'd9,
ack4 = 4'd10,
r_data = 4'd11,
ack5 = 4'd12,
stop = 4'd13;
always @(posedge clk or negedge rstn)
begin
if (!rstn) begin
state <= idle;
sda_oen <= 1'b0;
sda_out <= 1'b1;
scl_cnt <= 8'b0;
bit_cnt <= 4'b0;
sda_out <= 1'b0;
end else begin
case (state)
idle: begin
// Initialize state and signals
state <= w_start;
sda_oen <= 1'b0;
sda_out <= 1'b1;
scl_cnt <= 8'b0;
bit_cnt <= 4'b0;
sda_out <= 1'b0;
end
w_start: begin
// Wait for start condition
if (sync_scl_1 && sda_negedge)
begin
state <= w_ctrl;
bit_cnt <= 4'd8;
end
else
state <= w_start;
end
w_ctrl: begin
// Control word transfer
if (scl_negedge)
begin
save_ctrl <= {save_ctrl[6:0], sync_sda_1};
bit_cnt <= bit_cnt - 1;
if (bit_cnt == 4'd0)
begin
state <= ack1;
bit_cnt <= 4'd8;
end
else
state <= w_ctrl;
end
else
state <= w_ctrl;
end
ack1: begin
// Acknowledge control word
if (save_ctrl[7:1] == slave_addr)
begin
scl_cnt <= scl_cnt + 8'b1;
if (scl_cnt == scl_cnt_max >> 2)
begin
sda_out <= 0;
sda_oen <= 1;
state <= ack1;
end
else if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)
begin
state <= w_addr;
sda_oen <= 0;
scl_cnt <= 8'b0;
bit_cnt <= 4'd7;
end
else
state <= ack1;
end
else
state <= stop;
end
w_addr: begin
// Write address
if (scl_negedge)
begin
bit_cnt <= bit_cnt - 4'b1;
wen <= save_ctrl[0]; // write operation
a <= {a[6:0], sync_sda_1};
if (bit_cnt == 4'd0)
begin
bit_cnt <= 4'd7;
state <= ack2;
end
else
state <= w_addr;
end
else
state <= w_addr;
end
ack2: begin
// Acknowledge address
scl_cnt <= scl_cnt + 8'b1;
if (scl_cnt == scl_cnt_max >> 2)
begin
sda_out <= 1'b0;
sda_oen <= 1'b1;
state <= ack2;
end
else if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)
begin
sda_oen <= 1'b0;
scl_cnt <= 8'b0;
if (wen == 0) // decide write or read
state <= w_data;
else
state <= r_start;
end
else
state <= ack2;
end
w_data: begin
// Write data
if (scl_negedge)
begin
d <= {d[6:0], sync_sda_1};
bit_cnt <= bit_cnt - 4'b1;
if (bit_cnt == 4'd0)
begin
bit_cnt <= 4'd7;
state <= ack3;
end
else
state <= w_data;
end
else
state <= w_data;
end
ack3: begin
// Acknowledge data
scl_cnt <= scl_cnt + 8'b1;
if (scl_cnt == scl_cnt_max >> 2)
begin
sda_out <= 0;
sda_oen <= 1'b1;
state <= ack3;
end
else if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)
begin
sda_oen <= 1'b0;
scl_cnt <= 8'b0;
state <= stop;
end
else
state <= ack3;
end
r_start: begin
// Read start condition
if (sync_scl_1 && sda_negedge)
begin
sda_oen <= 1'b0;
bit_cnt <= 4'd8;
state <= r_ctrl;
end
else
state <= r_start;
end
r_ctrl: begin
// Read control word
if (scl_negedge)
begin
bit_cnt <= bit_cnt - 4'b1;
save_ctrl <= {save_ctrl[6:0], sync_sda_1};
if (bit_cnt == 4'd0)
begin
wen <= save_ctrl[0];
bit_cnt <= 4'd7;
state <= ack4;
end
else
state <= r_ctrl;
end
else
state <= r_ctrl;
end
ack4: begin
// Acknowledge control word
if (save_ctrl[7:1] == slave_addr)
begin
scl_cnt <= scl_cnt + 8'b1;
if (scl_cnt == scl_cnt_max >> 2)
begin
sda_out <= 0;
sda_oen <= 1;
state <= ack4;
end
else if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)
begin
sda_oen <= 1'b0;
scl_cnt <= 8'b0;
if (wen)
begin
state <= r_data;
sda_oen <= 1'b1;
sda_out <= sync_sda_1;
end
else
state <= w_data;
end
else
state <= ack4;
end
else
state <= stop;
end
r_data: begin
// Read data
if (scl_negedge)
begin
save_q_data <= q[7:0];
bit_cnt <= bit_cnt - 4'b1;
sda_out <= save_q_data[7];
if (bit_cnt == 4'd0)
begin
state <= ack5;
bit_cnt <= 4'd7;
sda_oen <= 0;
end
else
begin
state <= r_data;
sda_oen <= 1;
save_q_data <= {save_q_data[6:0], 1'b0};
end
end
else
state <= r_data;
end
ack5: begin
// Acknowledge data
if (scl_posedge)
begin
if (sync_sda_1 == 1)
state <= stop;
else
state <= idle;
end
else
state <= ack5;
end
stop: begin
// Stop condition
if (sync_scl_1 && sda_posedge)
begin
state <= idle;
sda_oen <= 1'b0;
sda_out <= 1'b1;
end
else
state <= stop;
end
default: state <= idle;
endcase
end
end
endmodule
2.3.1 状态机状态解释
当然可以!以下是优化后的代码中每个状态的作用解释:
c
reg [3:0] state; // state transform
parameter idle = 4'd0,
w_start = 4'd1,
w_ctrl = 4'd2,
ack1 = 4'd3,
w_addr = 4'd4,
ack2 = 4'd5,
w_data = 4'd6,
ack3 = 4'd7,
r_start = 4'd8,
r_ctrl = 4'd9,
ack4 = 4'd10,
r_data = 4'd11,
ack5 = 4'd12,
stop = 4'd13;
状态作用解释
- idle (4'd0):
- 作用: 初始状态,等待复位信号或起始条件。
- 描述: 在这个状态下,所有信号被初始化,状态机等待复位信号 rstn 或起始条件(sync_scl_1 和 sda_negedge)。
- w_start (4'd1):
- 作用: 等待起始条件。
- 描述: 在这个状态下,状态机检测起始条件(sync_scl_1 和 sda_negedge)。如果检测到起始条件,状态机进入 w_ctrl 状态。
- w_ctrl (4'd2):
- 作用: 接收控制字。
- 描述: 在这个状态下,状态机接收控制字(save_ctrl),并将其存储在寄存器中。控制字的接收通过 scl_negedge 信号完成。当接收到完整的控制字后,状态机进入 ack1 状态。
- ack1 (4'd3):
- 作用: 发送 ACK 信号。
- 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。如果接收到的控制字匹配从设备地址(slave_addr),状态机进入 w_addr 状态。否则,状态机进入 stop 状态。
- w_addr (4'd4):
- 作用: 接收地址。
- 描述: 在这个状态下,状态机接收地址数据(a),并将其存储在寄存器中。地址的接收通过 scl_negedge 信号完成。当接收到完整的地址后,状态机进入 ack2 状态。
- ack2 (4'd5):
- 作用: 发送 ACK 信号。
- 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。根据控制字中的写入标志(wen),状态机决定进入 w_data 状态(写入数据)或 r_start 状态(读取数据)。
- w_data (4'd6):
- 作用: 写入数据。
- 描述: 在这个状态下,状态机接收数据(d),并将其存储在寄存器中。数据的接收通过 scl_negedge 信号完成。当接收到完整的数据后,状态机进入 ack3 状态。
- ack3 (4'd7):
- 作用: 发送 ACK 信号。
- 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。然后状态机进入 stop 状态。
- r_start (4'd8):
- 作用: 等待读取起始条件。
- 描述: 在这个状态下,状态机检测读取起始条件(sync_scl_1 和 sda_negedge)。如果检测到起始条件,状态机进入 r_ctrl 状态。
- r_ctrl (4'd9):
- 作用: 接收控制字。
- 描述: 在这个状态下,状态机接收控制字(save_ctrl),并将其存储在寄存器中。控制字的接收通过 scl_negedge 信号完成。当接收到完整的控制字后,状态机进入 ack4 状态。
- ack4 (4'd10):
- 作用: 发送 ACK 信号。
- 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。如果接收到的控制字匹配从设备地址(slave_addr),状态机进入 r_data 状态。否则,状态机进入 stop 状态。
- r_data (4'd11):
- 作用: 读取数据。
- 描述: 在这个状态下,状态机读取数据(q),并将其存储在寄存器中。数据的读取通过 scl_negedge 信号完成。当读取完数据后,状态机进入 ack5 状态。
13。 ack5 (4'd12): - 作用: 发送 ACK 信号。
- 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。然后状态机进入 idle 状态。
- stop (4'd13):
- 作用: 停止状态。
- 描述: 在这个状态下,状态机等待停止条件(sync_scl_1 和 sda_posedge)。如果检测到停止条件,状态机进入 idle 状态。
每个状态的作用如下:
- idle: 初始状态,等待复位或起始条件。
- w_start: 等待起始条件。
- w_ctrl: 接收控制字。
- ack1: 发送 ACK 信号,确认控制字接收。
- w_addr: 接收地址。
- ack2: 发送 ACK 信号,确认地址接收。
- w_data: 写入数据。
- ack3: 发送 ACK 信号,确认数据写入。
- r_start: 等待读取起始条件。
- r_ctrl: 接收控制字。
- ack4: 发送 ACK 信号,确认控制字接收。
- r_data: 读取数据。
- ack5: 发送 ACK 信号,确认数据读取。
- stop: 停止状态,等待停止条件。
这些状态共同构成了一个完整的 I2C 从设备写入和读取的有限状态机,确保数据的正确传输和接收。
2.4 ram.v
c
`timescale 1ns/1ps
module ram (
clk,
rstn,
d,
a,
q,
wen
);
// Input ports
input clk, rstn;
input [7:0] a; // Address input
input [7:0] d; // Data input
input wen; // Write enable
// Output ports
output [7:0] q; // Data output
// Internal registers
reg [7:0] ram [255:0]; // RAM array
integer i; // Loop counter
reg [7:0] q; // Output data register
// Always block for RAM operations
always @(posedge clk or negedge rstn)
begin
if (!rstn) begin
// Initialize RAM on reset
for (i = 0; i <= 255; i = i + 1)
ram[i] <= 8'b0;
end else begin
if (!wen) begin
// Write operation: wen = 0
ram[a] <= d;
end else begin
// Read operation: wen = 1
q <= ram[a];
end
end
end
endmodule
3. 波形展示
![](https://i-blog.csdnimg.cn/direct/d6ad88ee063b400f83e5ec91743928ed.png)
4. 建议
必看
此设计还存在一些问题,后续有时间我会完善的。
在同步的时候我建议还是使用两个寄存器缓冲,而不是使用一个,使用多个更加的稳妥一些,我这个就是使用了较少的寄存器缓冲,所以波形中有问题。(我把这段字打个红色背景)。(是因为在边沿检测的时候无法确认信号是否同步还是异步所以在设计的时候还是使用双寄存器进行消除亚稳态)。
5. 资料总结
练习时的一些思路。
https://blog.csdn.net/weixin_46163885/article/details/107170689