verilog练习:i2c slave 模块设计

news/2025/2/19 14:37:48/

文章目录

  • 前言
  • 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;

状态作用解释

  1. idle (4’d0):
  • 作用: 初始状态,等待复位信号或起始条件。
  • 描述: 在这个状态下,所有信号被初始化,状态机等待复位信号 rstn 或起始条件(sync_scl_1 和 sda_negedge)。
  1. w_start (4’d1):
  • 作用: 等待起始条件。
  • 描述: 在这个状态下,状态机检测起始条件(sync_scl_1 和 sda_negedge)。如果检测到起始条件,状态机进入 w_ctrl 状态。
  1. w_ctrl (4’d2):
  • 作用: 接收控制字。
  • 描述: 在这个状态下,状态机接收控制字(save_ctrl),并将其存储在寄存器中。控制字的接收通过 scl_negedge 信号完成。当接收到完整的控制字后,状态机进入 ack1 状态。
  1. ack1 (4’d3):
  • 作用: 发送 ACK 信号。
  • 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。如果接收到的控制字匹配从设备地址(slave_addr),状态机进入 w_addr 状态。否则,状态机进入 stop 状态。
  1. w_addr (4’d4):
  • 作用: 接收地址。
  • 描述: 在这个状态下,状态机接收地址数据(a),并将其存储在寄存器中。地址的接收通过 scl_negedge 信号完成。当接收到完整的地址后,状态机进入 ack2 状态。
  1. ack2 (4’d5):
  • 作用: 发送 ACK 信号。
  • 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。根据控制字中的写入标志(wen),状态机决定进入 w_data 状态(写入数据)或 r_start 状态(读取数据)。
  1. w_data (4’d6):
  • 作用: 写入数据。
  • 描述: 在这个状态下,状态机接收数据(d),并将其存储在寄存器中。数据的接收通过 scl_negedge 信号完成。当接收到完整的数据后,状态机进入 ack3 状态。
  1. ack3 (4’d7):
  • 作用: 发送 ACK 信号。
  • 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。然后状态机进入 stop 状态。
  1. r_start (4’d8):
  • 作用: 等待读取起始条件。
  • 描述: 在这个状态下,状态机检测读取起始条件(sync_scl_1 和 sda_negedge)。如果检测到起始条件,状态机进入 r_ctrl 状态。
  1. r_ctrl (4’d9):
  • 作用: 接收控制字。
  • 描述: 在这个状态下,状态机接收控制字(save_ctrl),并将其存储在寄存器中。控制字的接收通过 scl_negedge 信号完成。当接收到完整的控制字后,状态机进入 ack4 状态。
  1. ack4 (4’d10):
  • 作用: 发送 ACK 信号。
  • 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。如果接收到的控制字匹配从设备地址(slave_addr),状态机进入 r_data 状态。否则,状态机进入 stop 状态。
  1. r_data (4’d11):
  • 作用: 读取数据。
  • 描述: 在这个状态下,状态机读取数据(q),并将其存储在寄存器中。数据的读取通过 scl_negedge 信号完成。当读取完数据后,状态机进入 ack5 状态。
    13。 ack5 (4’d12):
  • 作用: 发送 ACK 信号。
  • 描述: 在这个状态下,状态机发送 ACK 信号(sda_out 和 sda_oen)。然后状态机进入 idle 状态。
  1. 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


http://www.ppmy.cn/news/1572405.html

相关文章

【云安全】云原生-K8S(三) 安装 Dashboard 面板

在Kubernetes中安装Dashboard需要几个步骤&#xff0c;包括部署Dashboard组件、配置访问权限以及暴露Dashboard服务等。以下是详细的步骤&#xff1a; 1. 部署 K8S Dashboard 可以通过以下命令用Kubernetes官方的YAML文件来快速部署&#xff0c;由于是国外网站&#xff0c;需…

01什么是DevOps

在日常开发中&#xff0c;运维人员主要负责跟生产环境打交道&#xff0c;开发和测试&#xff0c;不去操作生产环境的内容&#xff0c;生产环境由运维人员操作&#xff0c;这里面包含了环境的搭建、系统监控、故障的转移&#xff0c;还有软件的维护等内容。 当一个项目开发完毕&…

web前端第三次作业:登录窗口拖动效果

<!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>登录窗口拖动效果</title> <style>…

阿里4面+腾讯4面春招面试题解析,附Java 岗 988 道题分享

金三银四即将到来&#xff0c;想要跳槽的小伙伴&#xff0c;和想要在金三银四中大放异彩的朋友们如何才能做得更好呢&#xff1f;不要想着现在还在春节还处在懒惰的状态&#xff0c;不要想着临时抱佛脚想着面试前的狂补&#xff0c; 这样想的话你就大错特错了。小编认为只有做好…

使用Redis生成全局唯一ID/分布式唯一ID

全局唯一ID在开发中的应用很多&#xff0c;特别是在生成一些诸如订单编号&#xff0c;对ID的唯一性&#xff0c;安全性&#xff0c;高可用性&#xff0c;生成的高效性有严格的要求。 通常我们习惯使用数据库的自增字段来作为ID&#xff0c;但是&#xff0c;这种方式生成的ID规律…

C++学习 mac上VScode运行C++

一 运行hello world 1 安装C/CRunner包 2 写代码 这个命令用于将 C 源代码文件 main.cpp 编译成一个可执行文件&#xff0c;文件名为 main。具体说明如下&#xff1a; g&#xff1a;这是 GNU C 编译器。 main.cpp&#xff1a;这是包含 C 代码的源文件。 -o main&#xff1a…

Oracle 随机数0-100000

Oracle 随机数0-100000 在Oracle数据库中生成一个随机数&#xff08;例如&#xff0c;0到100000之间的随机数&#xff09;&#xff0c;你可以使用DBMS_RANDOM包中的VALUE函数。DBMS_RANDOM.VALUE函数允许你指定随机数的范围。 方法1&#xff1a;使用DBMS_RANDOM.VALUE SELEC…

wps配置deepseek

wps 配置deepseek https://e6jy62bip4.feishu.cn/docx/R09IdpU5HoADyDxcgfQcjpR8nnb