一、自定 FSM 说明
1、状态描述
State0:睡觉,如果闹钟响则起床吃早餐,否则继续睡觉
State1:吃早餐,吃完去上课
State2:上课,上完课后如果要开会就去开会,否则去自习
State3:自习,自习会后吃午餐
State4:开会,开会完后吃午餐
State5:吃午餐,吃完午餐去睡午觉
State6:睡午觉,睡晚午觉后如果要运动则去运动,否则打游戏
State7:运动,运动完后洗澡
State8:打游戏,打完游戏后洗澡
State9:洗澡,洗完澡后吃晚餐
State10:吃晚餐,吃完晚餐后如果要上晚自习则去上自习,否则看 电影
State11:晚自习,晚自习完后去睡觉
State12:看电影,看完电影后睡觉
2、设计代码说明
首先对模块名进行定义,其中包括输入和输出接口:
接着对定义的 12状态进行编码:
然后是状态直接根据输入的控制信号来进行状态转移的代码:
3、仿真波形说明(截图+文字标注)
测试文件代码:
如图对于第一天来说,有一个判断语句是 zot==4’b1011 才会执行下 面的语句,即 state11 时。而一开始所有控制信号都为 0,则第一天 的状态过程会是 state0 ->state1 ->state3 ->state5 ->state6 ->state8 ->state9 ->state10 ->state11 ->state0。
在状态到了 state11 的 200ms 后,meet 会为 1,此时会进入 state4。 而在进入 state6 时 sport 控制信号被设置为 1,故会进入 state7, 最后进入 state12 回到 state0。所以第二次的状态过程是 state0 ->state1 ->state2 ->state4 ->state5 ->state6 ->state7 ->state9 ->state10 ->state12 ->state0。
通过仿真查看验证:
可以看到红色框框出的即为第一天的状态转移,绿色框出的是第二天 的状态转移,和上面分析的一致,正确。
二、EEPROM 读写代码设计及仿真
1、代码说明
1. 端口
2.状态
每个状态用 8 位 16 进制数表示,对照上面图中 SDA 上数据传输情况, 每一位数据传输都有一个对应的状态,多出了一些等待状态。
3. SCL 同步
时钟的频率很高,读写数据不能以时钟周期为周期进行,设备进行响 应和读写需要的时间远大于时钟周期,因此使用 SCL 同步方式来同 步时序。
SCL 周期是通过时钟周期实现的。 一个 scl 周期是 30 个时钟周期。使用 div_cnt 来记录时钟周期数, 30 个一次循环
4.状态更新
5.读写命令的判断
读写命令是通过端口输入 write_op 和 read_op 确定的,这两个信号 是低电平有效,用了 wr_op 和 rd_op 两个寄存器把输入的 write_op 和 read_op 取反,这样如果有读或写命令,wr_op 或 rd_op 为 1,复 位和操作结束(wr_opover)时都要清零,接下来就用 wr_op 和 rd_op 判断是否读 写了,这里仅仅是把原来低电平有效的信号替换为两个 高电平有效信号。
6.下一状态判断
Byte Write : START + DEVICE +ACK + ADDR + ACK + DATA + ACK + STOP
Random Read : START + DEVICE + ACK +ADDR + ACK+START + DEVICE + DATA + NO ACK + STO
下一状态的更新是在 scl_tick(每 30 个 clk),上面下划线划出的 部分是相同的,无论读写,一开始的状态更新都是按照上面下划线的 顺 序依次更新状态,这时候的器件地址都是 10100000(write), 到了 ADDR 的 ACK,即 W_AACK 时会根据 wr_op 和 rd_op 决定接下来进 入怎样的 状态,wr_op 会开始到状态 W_DATA 读数据;rd_op 会到 WAIT_WTICK3,然后继续 START。操作结束后需要等待一个信号 d5ms_over 才能回到 空闲,控制器件工作的频率不要太高。
7.SCL 同步实现
空闲,等待,操作结束,start 开始等状态下 SCL 都是高电平,因此 不需要 clr_scl 对 SCL 清零。另外 clr_scl 只在 scl_ls(scl 的低电 平开始)处 才置 1,把 scl 清 0,在 15 个 clk 周期的 scl_hs 处,再 把 scl 拉高,就实现了 SCL 周期
8.SDA 实现
代码后半都是 SDA 实现的部分,因为太长所以一段一段分析。
下面的一段是实现 SDA 的控制信号声明,这些信号在对应的状态且 scl 在低电平的中心时置 1,告诉 SDA 该怎么做,i2c_reg 用来暂存 scl 上 的数据,i2c_rlf 是在读写数据时用的,使用的部分在下一段 代码
接下来就是根据控制信号进行操作,把对应操作的数据放到 i2c_reg 里,准备向 SDA 线上传输,i2c_rlf 为 1 时 i2creg 会左移一位,因 为 sda 是单位宽的,每次把 i2creg 的最高位送到 sda 上,因此左移 就是一位一位把数据送到 sda 上
接下来是 sda 输出的控制,sda 使能在主机写数据地址,数据时都 是 1,使能为 1 时器件靠 sda 输出 i2creg 的最高位。NOACK 以外的其 他 响应信号,以及读的数据,都是从机发到 sda 线上的,这时主机 的 sda 使能为 0,不可以发出数据
以下是读数据的操作,用 sda_wr 控制,在 scl 高电平的中心(数据 稳定时)往 read_data 里读数据,左移补 sda,就一位一位的用 sda 发的 数据更新了 read_data,这时 sda 是从机在发数据
9.操作结束,等待
最后就是操作结束后的等待时间,d5ms_cnt 用来记时钟周期,记满 了就会把 d5ms_over 置 1,在上面状态转换的部分,最后进入 STOP 状 态等待的就是这个信号,等到了这个信号,I2C 就回到空闲状态 了。
最后,这个模块里没有 ACK 处理的部分,只是在应该收到 ACK 的时候 从 sda 读信号出来,没有进行判断,或许是交给主机来做的
2、TestBench 代码说明
TestBench 主要代码如下:
根据上面的代码发现,我们可以知道读写命令是通过端口输入 write_op 和 read_op 确定的。所以该程序先写再读,波形应该也是 这样。
3、仿真波形说明(截图+文字标注)
SCL 为高时,SDA 为下降沿的时候,设备开始工作。先由主机写入器 件地址,然后写入数据地址,每次写入后从机都会回复一个 ACK 信号 表示收到。其中器件地址是从机 EEPROM 的地址 1010000X,X 为 0 表 示写,1 表示读,一开始写入器件地址时,X 都是 0。进行写操作, 接下来只需要由主机发出数据就可以了,从机发出 ACK 响应回复收 到。
进行读操作,重新写一遍器件地址,这次 X 就是 1 了,表示接下来要 读数据了,从机读出数据,主机收到数据以后回复 NOACK 给从机表示 收到数据。
三、实验总结
通过这次实验,我学会了很多知识点,比如如何自定义状态机, Verilog 的基本语法,如何编写 testbench 测试文件,如何使用 modelsim 软件进行仿真测试。同时对于 I2C 协议有了更深层次的理 解,对于 EEPROM 代码能够详细阐述其含义。这次实验对我的编码能 力也有了极大提升,也能够通过分析状态图来解释状态转移。 这次实验同时也遇到了很多困难,比如对 Verilog 语法不熟悉,以及 对 modelsim 软件的安装存在疑问等,不过通过向老师和同学求助可 以得到解决