//////////////////////////////////////////////////
//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实现的更多相关文章

  1. [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  2. [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  3. 【神经网络与深度学习】学习笔记:AlexNet&Imagenet学习笔记

    学习笔记:AlexNet&Imagenet学习笔记 ImageNet(http://www.image-net.org)是李菲菲组的图像库,和WordNet 可以结合使用 (毕业于Caltec ...

  4. Vue学习笔记-Vue.js-2.X 学习(六)===>脚手架Vue-CLI(项目说明-Babel)

    五  Vue学习-vue-cli脚手架学习(创建只选一个选项:Babel) 1. 项目目录说明 node_modules : 包管理文件夹 public : 静态资源 src : 源代码 gitign ...

  5. Vue学习笔记-Vue.js-2.X 学习(五)===>脚手架Vue-CLI(PyCharm)

    Vue项目在pycharm中配置 退出运行: ctrl+c Vue学习笔记-Vue.js-2.X 学习(六)===>脚手架Vue-CLI(项目说明)

  6. Vue学习笔记-Vue.js-2.X 学习(四)===>脚手架Vue-CLI(基本工作和创建)

    (五) 脚手架Vue-CLI 一 Vue-CLI前提(nodejs和webpack) 二  Vue学习-nodejs按装配置,Node.js 就是运行在服务端的 JavaScript. 1. 去nod ...

  7. Vue学习笔记-Vue.js-2.X 学习(三)===>组件化高级

    (四) 组件化高级 1.插槽(slot)的基本使用 A:基本使用: <slot></slot> B:默认置:<slot><h1>中间可以放默认值< ...

  8. Vue学习笔记-Vue.js-2.X 学习(二)===>组件化开发

    ===重点重点开始 ========================== (三) 组件化开发 1.创建组件构造器: Vue.extends() 2.注册组件: Vue.component() 3.使用 ...

  9. Vue学习笔记-Vue.js-2.X 学习(一)===>基本知识学习

    一  使用环境: windows 7 64位操作系统 二  IDE:VSCode/PyCharm 三  Vue.js官网: https://cn.vuejs.org/ 四  下载安装引用 方式1:直接 ...

  10. I2C协议学习笔记

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/wzt_007/article/detai ...

随机推荐

  1. oracle截取字符串去掉字段末尾指定长度的字符

    lengthb(string)计算string所占的字节长度:返回字符串的长度,单位是字节 length(string)计算string所占的字符长度:返回字符串的长度,单位是字符 eg: //去掉该 ...

  2. RHEL7系统管理之内核管理

    1. Kdump工具 Kdump的工作机制是在内核崩溃时, 通过kexec 工具由BIOS启动一个备用内核, 由备用内核执行一系列任务,保存内存中崩溃内核的状态, 供后续故障分析用. 本文默认AMD或 ...

  3. SQL server数据库压缩空间

    SQL server数据库,在手动删除或者自动删除数据后,查看数据库物理文件发现占用空间并没有释放,果断采用万能的重启.(反正是自己用的一个服务器,随便玩.如果不是只有自己用的服务器,还是建议让运维或 ...

  4. 非定制UIImagePickerController的使用

    非定制UIImagePickerController的使用 效果: 源码: // // ViewController.m // ImagePic // // Created by XianMingYo ...

  5. django 板块动态切换

    需求:在同一页面的不同板块上可以实现动态切换,使用一个view实现,具体如下图所示,点击phy显示物理机列表,点击vm显示虚机列表,phy.vm对应的url均是动态生成:               ...

  6. Google, Facebook, Amazon and Microsoft Salaries

    https://blog.step.com/2016/04/08/an-open-source-project-for-tech-salaries/ Step.com Crowdsource your ...

  7. [2018HN省队集训D8T3] 水果拼盘

    [2018HN省队集训D8T3] 水果拼盘 题意 给定 \(n\) 个集合, 每个集合包含 \([1,m]\) 中的一些整数, 在这些集合中随机选取 \(k\) 个集合, 求这 \(k\) 个集合的并 ...

  8. 激活office软件

    1. 打开校园软件正版化激活网址 http://nic.seu.edu.cn/2015/0113/c12333a115290/page.htm 2. 下载KMS激活脚本 3. 登陆easyconnec ...

  9. 【CodeChef】Prime Distance On Tree

    vjudge 给定一棵边长都是\(1\)的树,求有多少条路径长度为质数 树上路径自然是点分治去搞,但是发现要求是长度为质数,总不能对每一个质数都判断一遍吧 自然是不行的,这个东西显然是一个卷积,我们合 ...

  10. HBase学习之路 (四)HBase的API操作

    Eclipse环境搭建 具体的jar的引入方式可以参考http://www.cnblogs.com/qingyunzong/p/8623309.html HBase API操作表和数据 import ...