本文首发于个人博客
设计一个参数可配置的异步FIFO,要求:
名称 | 默认值 | 说明 |
---|---|---|
DEPTH_LOG | 4 | FIFO容量为 |
DATA_WIDTH | 8 | 数据位宽 |
名称 | 类型 | 位宽 | 说明 |
---|---|---|---|
read_clk | input | 1 | 读时钟域时钟 |
write_clk | input | 1 | 写时钟域时钟 |
rst_n | input | 1 | 系统复位端口,低有效 |
名称 | 类型 | 位宽 | 说明 |
---|---|---|---|
read_req | input | 1 | 读完成信号 |
read_valid | output | 1 | 读数据有效信号 |
read_data | output | DEPTH_LOG | 读数据 |
fifo_empty | output | 1 | FIFO空信号 |
名称 | 类型 | 位宽 | 说明 |
---|---|---|---|
write_req | input | 1 | 写请求信号 |
write_data | input | DEPTH_LOG | 写数据 |
fifo_full | output | 1 | FIFO满信号 |
read_port.png
read_req
信号拉高表示请求读数据,若此时FIFO非空(fifo_empty
为低),FIFO将会将数据置于read_data
上,同时拉高read_valid
信号。即当read_valid
有效时,对应的read_data
上的数据有效。fifo_empty
拉高表示FIFO已空,当前数据输出端口上的数据无意义, 再拉高read_req
将不会改变read_data
上的数据。
write_port.png
写端口如上所示,当且仅当write_req
信号高且fifo_full
信号低时将write_data
端口上的数据写入FIFO。
structure.png
系统整体结构如上所示,分为两个时钟域——读时钟域和写时钟域。每个时钟域结构相互镜像:
其他还有跨时钟域的组件,分别为:
假设二进制码为每位为
,对应的格雷码每位为
,共N位,转换算法为:
例如二进制码011
,共3位,则格雷码第2位为0
,其他几位为10
,对应格雷码为010
,在具体实现时,可以参考下图的实现方法:
bin2gray.png
对读指针和写指针有以下含义:
二进制下,对于地址位宽为N的SRAM,可以使用位宽为N+1的地址——低N位为地址,MSB为标志位,用于标记满和空:
转换到格雷码域,做相同判断,判空条件为两个指针相等,相等的二进制码对应格雷码相等,条件不变。对于判满,需要两个二进制仅有最高位不同,参考二进制转格雷码条件,判满条件如下:
由于同步器的同步需要消耗时钟周期,因此:
“假满”和“假空”状态均不影响异步FIFO的正常工作,仅为略微降低FIFO的工作效率
同步器是一种跨时钟域数据传输的方法,二级同步器结构如下所示:
synchronizer.png
从源时钟域下的源信号开始,依次通过多个时钟为目标时钟域时钟下的寄存器,即构成了多级同步器,寄存器的数量就是同步器的级数。一般的信号仅需要二级同步器,高速信号一般使用三级同步器。
写FIFO部分包括以下几个组件:
写FIFO部分的需求如下:
名称 | 类型 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 写部分时钟 |
rst_n | input | 1 | 系统复位 |
write_req | input | 1 | 写FIFO请求 |
fifo_full | output | 1 | FIFO满信号 |
write_addr | output | DEPTH_LOG | 写存储器地址 |
read_point_gray | input | DEPTH_LOG+1 | 读指针格雷码,未同步 |
write_point_gray | output | DEPTH_LOG+1 | 写指针格雷码 |
write_control.png
上图为fifo_full
生成部分的结构图,为了保证保证fifo_full
拉高的及时性,设置next_write_point_gray
寄存器,指示下一个格雷码的写地址。当一次is_write
拉高时,每个部件的功能如下所示:
write_point
:自增1write_point_gray
:从next_write_point_gray
获取与write_point
同步的格雷码next_write_point_gray
:取现在write_point加2后对应的格雷码,获得与write_point+1
同步的格雷码写产生FIFO满的波形如下所示:
full_gen.png
当一次写请求使FIFO满时,由于写请求发生,因此使用next_write_point_gray
进行判满操作,此时该信号与read_point_gray
相等,因此在下一个时钟周期fifo_full
拉高(图中a,b->c),同时,write_point
和write_point_gray
均完成更新。下一个时钟周期,无写请求发生,因此使用write_point_gray
进行判满操作,此时write_point_gray
更新后与read_point_gray
相等,保持fifo满状态(图中e,d->f)。
对于数据部分,如下图所示:
write_data.png
该部分不包括在写控制模块中,系统输入的write_data
端口直接连接到SRAM的写数据端口,写请求端口为write_req
和fifo_full
的组合逻辑与信号(write_req && !fifo_full
)
读FIFO部分包括以下几个组件:
读FIFO部分需要满足以下几个需求:
名称 | 类型 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 读时钟信号 |
rst_n | input | 1 | 系统复位信号,低有效 |
read_req | input | 1 | 读请求信号 |
fifo_empty | output | 1 | FIFO空信号 |
memory_read_addr | output | DEPTH_LOG | SRAM的读地址信号 |
read_point_gray | output | DEPTH_LOG+1 | 读指针格雷码 |
write_point_gray | input | DEPTH_LOG+1 | 写指针格雷码,未同步 |
read_valid | output | 1 | 读数据有效信号 |
实现的方式与写部分类似,fifo_empty
信号和读指针生成如下所示:
read_control.png
主要部件的功能如下所示:
read_data.png
读数据部分如上图所示,read_valid信号在read_req && !fifo_empty
为真时拉高,表示当前read_data
上的数据有效。read_data
为输出数据端口直接连接到SRAM的输出端口(SRAM输出端口自带寄存器)。