异步FIFO
基于FIFO基础的知识,进一步学习什么是异步FIFO,除了基础知识外,还会增加verilog代码和波形图仿真。
计划有UVM验证,做不做得出来再说
基础知识
参考视频:https://www.youtube.com/watch?v=mGREY8u9ELs&t
异步FIFO是一种用于解决数据在不同时钟域之间安全传输的电路模块。你可以想象它像一个临时的数据仓库,一端负责接收数据,另一端负责发送数据,但这两端的工作节奏完全不同步——比如一端可能跑得飞快,而另一端慢悠悠的,就像两个人用不同的速度传递物品。这时候异步FIFO就充当了缓冲区的角色,确保快的一方不会把数据硬塞给慢的一方导致丢失,慢的一方也不会因为来不及取数据而卡住整个系统。
它的核心难点在于协调两个不同步的时钟。比如当写入端以高频时钟快速存入数据时,读取端可能用低频时钟慢慢读取,这时候需要巧妙的设计来避免读写指针冲突。常用的方法是用格雷码来表示指针,这种编码每次变化只改动一个比特位,能大幅降低跨时钟域同步时出现中间态错误的风险。你可能会在处理器与外设通信,或者芯片内部模块交互的场景中见到它,就像两个说不同方言的人通过翻译器顺畅对话一样。
操作步骤
1.写入数据
当外部电路需要写入数据时,写使能信号(这破翻译每次看到都想骂人,我念书的时候就一直搞不懂什么是使能。搞半天原来就是请求信号/启动信号Enable)就会被拉高。接着数据就会被放入FIFO的存储单元,同时写指针会向向前移动一步。
注:这里的写指针的移动速度由写时钟决定
2.同步读指针到写时钟域
读指针原本属于读时钟域,但是需要告诉写端的朋友们“嘿,我读到第X个数据啦!”,否则写入端就不知道前面是否还有位置可以写入。就像你有个停车场管理员朋友,你要听他打电话缺人那里是否有停车位,你才能开车去找车位。
在数字电路中,两级触发器(或格雷码转换)就是起到电话的作用,电话将读指针同步到写时钟域。这时候写域的朋友就可以同步读域的情况了。
3.判断FIFO是否已经满了
这里在基础部分已经详细讲过了 “传送门“
4.读取数据
当外部电路需要读取数据时,读使能信号(就是读的请求信号)会被拉高。数据从当前读指针指向的位置取出,同时读指针向前移动一步。读指针的移动速度由读时钟决定,可能与写时钟完全不同步。
5.同步写指针到读时钟域
写指针需要被同步到读时钟域,让读端知道“写端已经写到哪里了”,否则读端可能读到无效数据。
这时候就需要我们的空信号闪亮登场:读端比较同步后的写指针和当前读指针。如果两者完全相等,说明FIFO已空,停止读取。
形象表达
想象一个环形传送带:
写端工人(快时钟) 不断往传送带上放包裹(数据),但需要时不时看一眼对面发来的“读端进度表”(同步后的读指针),决定是否还能继续放。
读端工人(慢时钟) 按自己的节奏取包裹,同时定期向写端汇报“我取到第几个了”(同步后的写指针)。
如果写端太快,传送带放满时会暂停;如果读端太快,发现传送带空了也会暂停。两者通过“进度表”间接协调,即使速度不同也不会出错。
verilog代码
来了来了,开始写代码仿真了!!!
食用说明
稍后会被放入仓库中,点击”传送门“可查看,只需要克隆至你的本地即可。
文档中会有6个文件:顶层模块(async_fifo.v)、存储器模块(fifo_memory.v)、写控制模块(write_controller.v)、读控制模块(read_controller.v)、跨时钟同步模块(sync_ptr.v)、TB模块(testbench.v)
代码内容
仓库地址:https://github.com/HauUhang/Asynchronous-FIFO
- async_fifo.v
- fifo_memory.v
- write_controller.v
- read_controller.v
- sync_ptr.v
- testbench.v
写代码过程中的疑问和学习
(1)write_ctrl
写代码的逻辑是什么?
- 首先要写信号:
- 📥 Inputs(来自哪里?)
- 写时钟信号【提示:FIFO 是异步的,写和读时钟分开,写控制模块用哪个时钟?】
- 写使能信号(是否写入)【提示:这个信号由外部模块控制,用来决定是否尝试写数据。】
- FIFO已满标志?【提示:如果FIFO满了,还能写吗?这个信息从哪里来?你需要接收它还是计算它?】
- 读指针(同步过来的)【提示:你是否需要判断“满”?那你要和谁比较?异步域的信号可以直接比较吗?】
- 📤 Outputs(你要输出给谁?)
- 写地址(写指针)【提示:你要输出写地址给 RAM,同时也可能输出一个格雷码版本用于跨时钟同步。】
- 写指针(格雷码)【提示:异步FIFO通常会输出一个“同步友好”的写指针给读域。】
- 满信号(FIFO full)【提示:你是否要计算 FIFO 是否满?满的时候外部就不该写了。】
- 接着是逻辑
异步FIFO的逻辑是这样的:
1 | +--------------------+ |
- 写指针(二进制)逻辑
- 写地址输出
- 写指针的格雷码形式
为什么在write_ctrl.v中我使用的是wire,而不是reg?
因为根据FIFO基础中描述,我提到full= {~wptr[3],wptr[2:0]}=={rptr[3:0]},这是一种组合逻辑,就要用wire
波形图
1 | `timescale 1ns/1ps |
(2)read_ctrl
这个模块的代码逻辑是什么?
和写代码的很像,就几乎把w
变成r
就行了,不多赘述
(3)dual_port_ram
我不太懂[7:0]和[2:0]究竟是什么,我想要一个8-deep的FIFO
在 Verilog 中,[a:b] 是位宽定义,表示这个信号有多少位(bit)
[7:0] 表示一共有 8 位(bit) 可以表示 0 到 255 的数(1 byte)
[2:0] 表示一共有 3 位(bit) 可以表示 0 到 7(8个地址)
为什么这个里面不用wrst和rrst
因为这个模块只是一个 纯存储器(memory)模块,RAM 本身是:同步写、同步读;没有“状态”需要复位;读写操作全靠 wen/ren + clk 控制;数据存在 RAM 的数组里(如 mem[0:7]),不受 reset 控制清空
mem[waddr] <= wdata 的意思是wdata赋值给men的waddr中吗?
在硬件中你可以把 mem[…] 看成一个可以存很多小值的“储物柜”,waddr 就是柜子号,wdata 是你放进去的东西。
testbench
1 | `timescale 1ns/1ps |
波形图
(4)sync_gray
testbench
1 | `timescale 1ns/1ps |