文章目录
- 前言
- 1. 结构
- 2.代码
- 2.1 iic_slave.v
- 2.2 sync.v
- 2.3 wr_fsm.v
- 2.3.1 状态机状态解释
- 2.4 ram.v
- 3. 波形展示
- 4. 建议
- 5. 资料总结
前言
首先就不啰嗦iic协议了,网上有不少资料都是叙述此协议的。
下面将是我本次设计的一些局部设计汇总,如果对读者有借鉴意义那最好,如果没有的话也无所谓,互相交流而已。(这是我早期的版本,注释比较少,代码编写比较混乱,读者自便)
希望读者发现问题可在下方留言,我会及时回答或者修改。
1. 结构
顶层结构图
master结构图
slave结构图
2.代码
2.1 iic_slave.v
`timescale 1ns/1psmodule iic_slave (input rstn,input clk,input scl,inout sda,input [7:0] q, // RAM data to slaveoutput wen,output [7:0] d, // Slave data to RAMoutput [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
`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) beginif (!rstn) beginsync_scl_1 <= 1'b0;sync_sda_1 <= 1'b0;sync_scl_0 <= 1'b0;sync_sda_0 <= 1'b0;end else beginsync_scl_0 <= scl;sync_sda_0 <= sda;sync_scl_1 <= sync_scl_0;sync_sda_1 <= sync_sda_0;endendassign 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
`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 regreg [7:0] scl_cnt; // clk delay counterreg [3:0] bit_cnt; // valid transfer bytereg [7:0] a; // a = save word addr, shift data to ramreg sda_out; // data out regreg sda_oen; // three state gate flag bitreg [7:0] save_ctrl; // store ctrl wordreg [7:0] save_q_data; // store data of qwire [7:0] q;parameter slave_addr = 7'b1101101; // parameter slave addrparameter scl_cnt_max = 60-1;reg [3:0] state; // state transformparameter 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)beginif (!rstn) beginstate <= idle;sda_oen <= 1'b0;sda_out <= 1'b1;scl_cnt <= 8'b0;bit_cnt <= 4'b0;sda_out <= 1'b0;end else begincase (state)idle: begin// Initialize state and signalsstate <= w_start;sda_oen <= 1'b0;sda_out <= 1'b1;scl_cnt <= 8'b0;bit_cnt <= 4'b0;sda_out <= 1'b0;endw_start: begin// Wait for start conditionif (sync_scl_1 && sda_negedge)beginstate <= w_ctrl;bit_cnt <= 4'd8;endelsestate <= w_start;endw_ctrl: begin// Control word transferif (scl_negedge)beginsave_ctrl <= {save_ctrl[6:0], sync_sda_1};bit_cnt <= bit_cnt - 1;if (bit_cnt == 4'd0)beginstate <= ack1;bit_cnt <= 4'd8;endelsestate <= w_ctrl;endelsestate <= w_ctrl;endack1: begin// Acknowledge control wordif (save_ctrl[7:1] == slave_addr)beginscl_cnt <= scl_cnt + 8'b1;if (scl_cnt == scl_cnt_max >> 2)beginsda_out <= 0;sda_oen <= 1;state <= ack1;endelse if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)beginstate <= w_addr;sda_oen <= 0;scl_cnt <= 8'b0;bit_cnt <= 4'd7;endelsestate <= ack1;endelsestate <= stop;endw_addr: begin// Write addressif (scl_negedge)beginbit_cnt <= bit_cnt - 4'b1;wen <= save_ctrl[0]; // write operationa <= {a[6:0], sync_sda_1};if (bit_cnt == 4'd0)beginbit_cnt <= 4'd7;state <= ack2;endelsestate <= w_addr;endelsestate <= w_addr;endack2: begin// Acknowledge addressscl_cnt <= scl_cnt + 8'b1;if (scl_cnt == scl_cnt_max >> 2)beginsda_out <= 1'b0;sda_oen <= 1'b1;state <= ack2;endelse if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)beginsda_oen <= 1'b0;scl_cnt <= 8'b0;if (wen == 0) // decide write or readstate <= w_data;elsestate <= r_start;endelsestate <= ack2;endw_data: begin// Write dataif (scl_negedge)begind <= {d[6:0], sync_sda_1};bit_cnt <= bit_cnt - 4'b1;if (bit_cnt == 4'd0)beginbit_cnt <= 4'd7;state <= ack3;endelsestate <= w_data;endelsestate <= w_data;endack3: begin// Acknowledge datascl_cnt <= scl_cnt + 8'b1;if (scl_cnt == scl_cnt_max >> 2)beginsda_out <= 0;sda_oen <= 1'b1;state <= ack3;endelse if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)beginsda_oen <= 1'b0;scl_cnt <= 8'b0;state <= stop;endelsestate <= ack3;endr_start: begin// Read start conditionif (sync_scl_1 && sda_negedge)beginsda_oen <= 1'b0;bit_cnt <= 4'd8;state <= r_ctrl;endelsestate <= r_start;endr_ctrl: begin// Read control wordif (scl_negedge)beginbit_cnt <= bit_cnt - 4'b1;save_ctrl <= {save_ctrl[6:0], sync_sda_1};if (bit_cnt == 4'd0)beginwen <= save_ctrl[0];bit_cnt <= 4'd7;state <= ack4;endelsestate <= r_ctrl;endelsestate <= r_ctrl;endack4: begin// Acknowledge control wordif (save_ctrl[7:1] == slave_addr)beginscl_cnt <= scl_cnt + 8'b1;if (scl_cnt == scl_cnt_max >> 2)beginsda_out <= 0;sda_oen <= 1;state <= ack4;endelse if (scl_cnt == (scl_cnt_max >> 2) + scl_cnt_max)beginsda_oen <= 1'b0;scl_cnt <= 8'b0;if (wen)beginstate <= r_data;sda_oen <= 1'b1;sda_out <= sync_sda_1;endelsestate <= w_data;endelsestate <= ack4;endelsestate <= stop;endr_data: begin// Read dataif (scl_negedge)beginsave_q_data <= q[7:0];bit_cnt <= bit_cnt - 4'b1;sda_out <= save_q_data[7];if (bit_cnt == 4'd0)beginstate <= ack5;bit_cnt <= 4'd7;sda_oen <= 0;endelsebeginstate <= r_data;sda_oen <= 1;save_q_data <= {save_q_data[6:0], 1'b0};endendelsestate <= r_data;endack5: begin// Acknowledge dataif (scl_posedge)beginif (sync_sda_1 == 1)state <= stop;elsestate <= idle;endelsestate <= ack5;endstop: begin// Stop conditionif (sync_scl_1 && sda_posedge)beginstate <= idle;sda_oen <= 1'b0;sda_out <= 1'b1;endelsestate <= stop;enddefault: state <= idle;endcaseendend
endmodule
2.3.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;
状态作用解释
- 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
`timescale 1ns/1ps
module ram (clk,rstn,d,a,q,wen
);// Input portsinput clk, rstn;input [7:0] a; // Address inputinput [7:0] d; // Data inputinput wen; // Write enable// Output portsoutput [7:0] q; // Data output// Internal registersreg [7:0] ram [255:0]; // RAM arrayinteger i; // Loop counterreg [7:0] q; // Output data register// Always block for RAM operationsalways @(posedge clk or negedge rstn)beginif (!rstn) begin// Initialize RAM on resetfor (i = 0; i <= 255; i = i + 1)ram[i] <= 8'b0;end else beginif (!wen) begin// Write operation: wen = 0ram[a] <= d;end else begin// Read operation: wen = 1q <= ram[a];endendendendmodule
3. 波形展示
4. 建议
必看
此设计还存在一些问题,后续有时间我会完善的。
在同步的时候我建议还是使用两个寄存器缓冲,而不是使用一个,使用多个更加的稳妥一些,我这个就是使用了较少的寄存器缓冲,所以波形中有问题。(我把这段字打个红色背景)。(是因为在边沿检测的时候无法确认信号是否同步还是异步所以在设计的时候还是使用双寄存器进行消除亚稳态)。
5. 资料总结
练习时的一些思路。
https://blog.csdn.net/weixin_46163885/article/details/107170689