学习笔记一:I2C协议学习和Verilog实现
//////////////////////////////////////////////////
//clk = 20 MHz ,一个周期50ns
//sck = 100 kHz (scl) ,一个周期 1000ns
//I2C在sck下降沿更新数据,上升沿读取(采样)数据
///////////////////////////////////////////////////
module demo_I2C #(parameter F100K = 'd200)(clk,rstn,start_sig,word_addr,wr_data,rd_data,done_sig,scl,sda,sq_i); input clk ;
input rstn ; input [:] start_sig ; //
input [:] word_addr ; //word address
input [:] wr_data ; //Data
output [:] rd_data ; //Data from EEPROM
output done_sig ; output scl ; //sda和scl其实是用来作为仿真信号添加在这里的,寄存器信号都用rscl和rsda表示了,最后用assign将rscl和rsda赋值给sda和scl,连到模块外部仿真用
inout sda ; //sda表示当前sda的in或out的值 output [:] sq_i ;
/************************************
在这里,iic_func_module.v 的步骤i已经被引出来了。读者要知道步骤i在无论是在设计上还是仿真上都有许多的好处。
步骤i在仿真中可以充当“调试跟踪”的作用,因为只要模块的那个部分出问题,步骤i就会指向它。
此外,步骤i在驱动IO口的时候,我们还可以知道仿真对象的内部到底发生什么事情了。
*************************************/ reg [:] i ;
reg [:] cnt ;
reg [:] go ;
reg isout ;
reg isack ; //临时存放ack信号用于判断
reg [:] rdata ; //存放任意8位数据的寄存器。在读的最后一步,还会将读到的8位sda存起来赋值给rd_data
reg rsda ; //用来寄存任意一位sda
reg rscl ;
reg rdone_sig ; always@(posedge clk or negedge rstn)
begin
if(!rstn)
begin
// start_sig <= 2'b00 ; /*输入信号不是寄存器类型,不需要Reset*/
// word_addr <= 8'd0 ; /*在处理输入输出信号时,输入信号因为不是reg而是wire,不需要Reset*/
// wr_data <= 8'd0 ; /*输出信号一般也不直接Reset,而是定义一个他们对应的reg,在Reset或者其他操作时对这些reg进行操作,最后用assign将输出信号和各自的reg相连*/
rdata <= 'd0 ;
rdone_sig <= 'b0 ; rscl <= 'b1 ;
rsda <= 'b1 ;
i <= 'd0 ;
isout <= 'b1 ;
isack <= 'b0 ;
rdata <= 'd0 ;
go <= 'd0 ;
end else if(start_sig[]) //write option
case(i)
: //start
begin
if(cnt == 'd0)
begin
rscl <= 'b1 ;
rsda <= 'b1 ;
end
else cnt <= cnt + 'b1; if(cnt == 'd100)
begin
rsda <= 'b0 ; end
else cnt <= cnt + 'b1; if(cnt == F100K - 'b1)
begin
i <= i + 'd1 ;
cnt <= ;
end
else cnt <= cnt + 'b1;
end : //write device address
begin
isout = 'b1;
rdata <= {'b1010,3'b000,'b0}; //1010是EEPROM型号,000是这颗EEPROM地址(三个引脚全部接地),0表示/W(写)
i <= 'd7;
go <= i + 'b1;
end : //write word address
begin
isout = 'b1;
i <= 'd7;
rdata <= word_addr;
go <= i + 'b1;
end : //write data
begin
isout = 'b1;
i <= 'd7;
rdata <= wr_data;
go <= i + 'b1;
end : //stop
begin
if(cnt == 'd0)
rscl <= 'b0 ;
else if(cnt == 'd50)
rscl <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == 'd0)
rsda <= 'b0 ;
else if(cnt == 'd150)
rsda <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == 'd50 + F100K - 1'b1)
begin
i <= i + 'b1 ;
cnt <= 'd0 ;
end
else
cnt <= cnt + 'b1 ;
end : //return done_sig
begin
rdone_sig <= 'b1;
i <= i + 'b1;
end
: //return IDLE
begin
rdone_sig <= 'b0;
i <= 'd0;
end
,,,,,,,:
begin
isout = 'b1;
rsda <= rdata[ - i];
if(cnt == 'd0)
rscl <= 'b0 ;
else if(cnt == 'd100)
rscl <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == F100K - )
begin
i <= i + 'b1 ;
cnt <= 'b0 ;
end
else
cnt <= cnt + 'b1 ; end
: //waiting for acknowledge
begin
isout = 'b0; //等待应答时是Read,因此是输入模式,=表示即时响应
if(cnt == 'b0)
rscl <= 'b0;
else if(cnt == 'b100)
rscl <= 'b1;
else
cnt <= cnt + 'b1; if(cnt == F100K - )
begin
i <= i + 'b1;
cnt <= ;
end
else
cnt <= cnt + 'b1; if(cnt == 'd150)
isack <= sda; //保险起见,在150个clk后才进行ack读取
else
cnt <= cnt + 'b1;
end
: //判断是否应答,返回go
begin
if(!isack)
i <= go;
else
i <= ;
end default: i <= ;
endcase
/***************************************************************************************************************************************/
else if(start_sig[]) //read option
case(i) //读写操作不冲突,i 不冲突
: //start
begin
if(cnt == )
begin
rscl <= 'b1 ;
rsda <= 'b1 ;
end
else cnt <= cnt + 'b1; if(cnt == )
begin
rsda <= 'b0 ; end
else cnt <= cnt + 'b1; if(cnt == F100K - )
begin
i <= i + 'd1 ;
cnt <= ;
end
else cnt <= cnt + 'b1;
end
: //write device address (read前先要write获得从机应答)
begin
isout = 'b1;
rdata <= {'b1010,3'b000,'b0}; //1010是EEPROM型号,000是这颗EEPROM地址(三个引脚全部接地),0表示/W(写)
i <= 'd10;
go <= i + 'b1;
end
: //write word address
begin
isout = 'b1;
rdata <= word_addr;
i <= 'd10;
go <= i + 'b1;
end
: //start again ,需要再控制sda和scl共同作用产生start信号
begin
isout = 'b1;
if(cnt == )
begin
rscl <= 'b0 ;
rsda <= 'b0 ;
end
else cnt <= cnt + 'b1; if(cnt == )
begin
rsda <= 'b1 ;
rscl <= 'b1 ;
end
else cnt <= cnt + 'b1; if(cnt == )
begin
rsda <= 'b0 ; end
else cnt <= cnt + 'b1;
if(cnt == )
begin
rscl <= 'b0 ; //这时EEPROM已经start了 end
else cnt <= cnt + 'b1;
if(cnt == - ) //保险起见,等到start稳定再进入下一状态
begin
i <= i + 'd1 ;
cnt <= ;
end
else cnt <= cnt + 'b1;
end : // 再写一次device address,告诉从设备变成read了
begin
isout = 'b1; //切换到输入(读取)模式
rdata <= {'b1010,3'b000,'b1}; //1010是EEPROM型号,000是这颗EEPROM地址(三个引脚全部接地),1表示R(读)
i <= 'd10;
go <= i + 'b1;
end
: //read data
begin
isout = 'b0;
rdata <= 'd0; ///* 注意这里,在读取8位sda寄存在rdata之前,要先将rdata清零*///
i <= 'd20;
go <= i + 'b1;
end : //stop
begin
isout = 'b1; if(cnt == )
rscl <= 'b0 ;
else if(cnt == )
rscl <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == )
rsda <= 'b0 ;
else if(cnt == )
rsda <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == + F100K - )
begin
i <= i + 'b1 ;
cnt <= ;
end
else
cnt <= cnt + 'b1 ;
end : //return isdone
begin
rdone_sig <= 'b1;
i <= i + 'b1;
end
: //return IDLE
begin
rdone_sig <= 'b0;
i <= ;
end
,,,,,,,:
begin
isout = 'b1;
rsda <= rdata[ - i];
if(cnt == )
rscl <= 'b0 ;
else if(cnt == )
rscl <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == F100K - )
begin
i <= i + 'b1;
cnt <= ;
end
else
cnt <= cnt + 'b1 ; end
: //waiting for acknowledge
begin
isout = 'b0; //等待应答时是Read,因此是输入模式,=表示即时响应
if(cnt == )
rscl <= 'b0;
else if(cnt == )
rscl <= 'b1;
else
cnt <= cnt + 'b1; if(cnt == F100K - )
begin
i <= i + 'b1;
cnt <= ;
end
else
cnt <= cnt + 'b1; if(cnt == )
isack <= sda; //保险起见,在150个clk后才进行sda读取
else
cnt <= cnt + 'b1;
end
: //判断是否应答,返回go
begin
if(!isack)
i <= go;
else
i <= ;
end ,,,,,,,:
begin
isout = 'b0; if(cnt == )
rscl <= 'b0 ;
else if(cnt == )
rscl <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == F100K - )
begin
i <= i + 'b1 ;
cnt <= ;
end
else
cnt <= cnt + 'b1 ; if(cnt == ) //保险起见,在150个clk后才进行sda读取
rdata[- i] <= sda; //读写都是先对MSB操作
else
cnt <= cnt + 'b1; end : //no ack(由于是单Data读,不是连续Data读,所以不需要应答),但是scl还是要继续跳转的
begin
isout = 'b1; if(cnt == )
rscl <= 'b0 ;
else if(cnt == )
rscl <= 'b1 ;
else
cnt <= cnt + 'b1 ; if(cnt == F100K - )
begin
i <= go ;
cnt <= ;
end
else
cnt <= cnt + 'b1 ;
end default: i <= ;
endcase
else
begin
// start_sig <= 2'b00 ;
// addr_sig <= 8'd0 ;
// wr_data <= 8'd0 ;
rdata <= 'd0 ;
rdone_sig <= 'b0 ; rscl <= 'b1 ;
rsda <= 'b1 ;
i <= 'd0 ;
isout <= 'b1 ;
isack <= 'b0 ;
rdata <= 'd0 ;
go <= 'd0 ;
end
end assign sda = isout?rsda:'bz ; //sda是一个带三态门的inout端口,三态门在out线上,所以在输出情况下,要将isout打开,rsda传到sda上。在输入情况下,isout关闭,输出方
assign scl = rscl ; //向线是高阻态,但输入方向没有三态门,是导通状态,可以read或者ackownledge数据。
assign done_sig = rdone_sig ;
assign rd_data = rdata ; //rdata在读的最后一步,将8位sda都存了下来,所以要将rdata给rd_data assign sq_i = i ;
endmodule
--------------------------------------------------------------------------------------------------------------------------分割线----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`timescale ns/ ns
module demo_I2C_vlg_tst(); /*与其说这是一个仿真文件,倒不如说这是一个top文件,因为里面不仅给出了激励,还模拟出了EEPROM的应答信号,
与接口模块通过sq_i,done_sig,start_sig,inout口sda实现互相控制*/
/*这也就说明了为什么设计和验证不分家的原因,当设计模块很庞大的时候,只能通过FPGA上板进行原型验证,大到一定规模之后,
FPGA也无法满足,只能通过搭建一个验证平台模拟使用环境(可能是硬件类似这种EEPROMinout接口)来验证功能,testbench并不简单,更像top*/ reg clk;
reg rstn; reg [:] start_sig;
reg [:] word_addr;
reg [:] wr_data; // wires
wire done_sig;
wire [:] rd_data;
wire scl; //IO inout端口在写testbench时,输入reg和输出wire都要写
reg treg_sda; //输入
wire sda; //输出
assign sda = treg_sda; //由于是inout端口,要将输入输出连起来 wire [:] sq_i; demo_I2C i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.done_sig(done_sig),
.rd_data(rd_data),
.rstn(rstn),
.scl(scl),
.sda(sda),
.sq_i(sq_i),
.start_sig(start_sig),
.word_addr(word_addr),
.wr_data(wr_data)
);
initial
begin
//学习这里的Reset和clk写法
rstn = ;
# rstn = ;
clk = ;
forever # clk = ~clk; $display("Running testbench");
end reg [:] i; always@(posedge clk or negedge rstn) /*这里是对输入进行激励*/
if(!rstn)
begin
i <= 'd0;
start_sig <= 'd0;
word_addr <= 'd0;
wr_data <= 'd0;
end
else
case(i)
:
begin
if(done_sig) //第二步:写完成后,done_sig有一拍变1的动作,在这一拍跳到第三步
begin
start_sig <= 'd0;
i <= i + 'b1;
end
else //第一步:done_sig=0,先写
begin
start_sig <= 'b01;
word_addr <= 'b10101010;
wr_data <= 'b11110000;
end
end
:
begin
if(done_sig) //第四步:读操作完成后,done_sig会有一拍短暂的变1动作,在这一拍跳到第五步
begin
start_sig <= 'd0;
i <= i + 'b1;
end
else //第三步:在上一拍结束后,done_sig立刻变0,进行这一步读操作
begin
start_sig <= 'b10;
word_addr <= 'b10101010;
end
end
: //停止动作 //第五步:在第四步短暂的变1后立马回到0,并停留在这一步,表示这个写+读操作结束
i <= i;
default: i <= i;
endcase ///////////////////////////////////////////////
/*这一部分是对IO口的激励,模拟EEPROM的acknowledge操作,因为接口代码并不是上板和EEPROM通信,为了仿真接口代码的正确性
这里写出了在接口代码进行一段操作后EEPROM的应答信号,还有读取操作时EEPROM输入给接口的rd_data*/ always@(posedge clk or negedge rstn)
if(!rstn)
treg_sda = 'b1; //reset并不是输入状态(在根本上,如果仿真对象不是将IO设置为输入状态,无论怎样驱动和刺激都没有用)
else if(start_sig[])
case(sq_i)
:
treg_sda = 'b0; //在15状态时,isout拉低输出关断,输入导通,sda输入为0,即ack default: treg_sda = 'b1; //其他状态下,并不是输入状态(在根本上,如果仿真对象不是将IO设置为输入状态,无论怎样驱动和刺激都没有用)
endcase
else if(start_sig[])
case(sq_i)
:
treg_sda = 'b0; ,,,,,,,:
treg_sda = wr_data[ - sq_i]; //这些状态是read,isout拉低,是输入状态,进行8位数据传入 default: treg_sda = 'b1; //其他状态下,并不是输入状态(在根本上,如果仿真对象不是将IO设置为输入状态,无论怎样驱动和刺激都没有用)
endcase
else
treg_sda = 'b1; endmodule
学习笔记一:I2C协议学习和Verilog实现的更多相关文章
- [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- 【神经网络与深度学习】学习笔记:AlexNet&Imagenet学习笔记
学习笔记:AlexNet&Imagenet学习笔记 ImageNet(http://www.image-net.org)是李菲菲组的图像库,和WordNet 可以结合使用 (毕业于Caltec ...
- Vue学习笔记-Vue.js-2.X 学习(六)===>脚手架Vue-CLI(项目说明-Babel)
五 Vue学习-vue-cli脚手架学习(创建只选一个选项:Babel) 1. 项目目录说明 node_modules : 包管理文件夹 public : 静态资源 src : 源代码 gitign ...
- Vue学习笔记-Vue.js-2.X 学习(五)===>脚手架Vue-CLI(PyCharm)
Vue项目在pycharm中配置 退出运行: ctrl+c Vue学习笔记-Vue.js-2.X 学习(六)===>脚手架Vue-CLI(项目说明)
- Vue学习笔记-Vue.js-2.X 学习(四)===>脚手架Vue-CLI(基本工作和创建)
(五) 脚手架Vue-CLI 一 Vue-CLI前提(nodejs和webpack) 二 Vue学习-nodejs按装配置,Node.js 就是运行在服务端的 JavaScript. 1. 去nod ...
- Vue学习笔记-Vue.js-2.X 学习(三)===>组件化高级
(四) 组件化高级 1.插槽(slot)的基本使用 A:基本使用: <slot></slot> B:默认置:<slot><h1>中间可以放默认值< ...
- Vue学习笔记-Vue.js-2.X 学习(二)===>组件化开发
===重点重点开始 ========================== (三) 组件化开发 1.创建组件构造器: Vue.extends() 2.注册组件: Vue.component() 3.使用 ...
- Vue学习笔记-Vue.js-2.X 学习(一)===>基本知识学习
一 使用环境: windows 7 64位操作系统 二 IDE:VSCode/PyCharm 三 Vue.js官网: https://cn.vuejs.org/ 四 下载安装引用 方式1:直接 ...
- I2C协议学习笔记
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/wzt_007/article/detai ...
随机推荐
- java 对象
对象可以看成是静态属性和动态属性的封装体.静态属性——成员变量:动态属性——方法. 1.汇编语言是对机器语言的抽象. 2.面向过程的语言是对汇编语言的抽象.属性和方法分离,不是封装在一起的,复用性 ...
- Fatal error: Can't use function return value in write context
这个的出错原因很简单,先贴出错代码: <?php $contact = array("id"=>1, "姓名"=>"老高" ...
- 如何使用 Jenkins、GitHub 和 Docker 在 Azure 中的 Linux VM 上创建开发基础结构
若要将应用程序开发的生成和测试阶段自动化,可以使用持续集成和部署 (CI/CD) 管道. 本教程介绍如何在 Azure VM 上创建 CI/CD 管道,包括如何: 创建 Jenkins VM 安装并配 ...
- 关于easyUI一些标签的使用
①table: 1.class="easyui-datagrid":指定为easyUI的表格布局 2.pagination="true":带分页的表格 3.ro ...
- 转:C#中的多态
封装.继承.多态,面向对象的三大特性,前两项理解相对容易,但要理解多态,特别是深入的了解,对于初学者而言可能就会有一定困难了.我一直认为学习OO的最好方法就是结合实践,封装.继承在实际工作中的应用随处 ...
- 编译并导入OpenSSL
编译并导入OpenSSL 1. 首先,需要运行脚本生成OpenSSL库,参考 https://github.com/x2on/OpenSSL-for-iPhone 示例 2. 运行脚本生成静态库 下一 ...
- 使用开源库 MagicalRecord 操作 CoreData
MagicalRecord https://github.com/magicalpanda/MagicalRecord 注意: MagicalRecord 在 ARC 下运作,Core Data ...
- 使用AHKActionSheet
使用AHKActionSheet https://github.com/fastred/AHKActionSheet 基本配置代码: AHKActionSheet *actionSheet = [[A ...
- jquery ajax跨域解决
双十一开发了一个抽奖API,最近上线了,各个事业部的大神们需要前台页面,异步调用我的抽奖API,要我提供js. js 提供之后发现不对,跨域了.之前也碰到过跨域的问题,研究过这个问题,三种方法解决. ...
- 对于高并发短连接造成Cannot assign requested address解决方法
https://www.cnblogs.com/dadonggg/p/8778318.html 感谢这篇文章给予的启发 在tcp四次挥手断开连接时,主动释放连接的一方最后会进入TIME_WAIT状态, ...