目录
异步FIFO常用于实现多bit数据的跨时钟域传输。
同时本篇blog的行文也是基于从无到有的设计思路,更加注重逻辑性。
4.4 Verilog FIFO 设计
异步FIFO设计(非常详细,图文并茂,值得一看!)
异步FIFO设计
<FPGA>异步FIFO的Verilg实现方法
1. 功能
可实现快到慢、慢到快的跨时钟域多bit数据传输,具体功能介绍如下:、
● 可对FIFO深度、宽度进行参数化自定义
● 能够实现数据的异步读写功能,且读出的数据是先入先出的顺序
● 能够指示FIFO空、满状态。同时FIFO内部数据量达到配置的参数数目时,拉高一个可配置的满状态信号
● 写数据宽度和读数据宽度可以不一致,但要实现位数拼接和分割
例如写是8bit读是32bit,那么每1次读的数据应当是每4次写的数据。
同理写是32bit读是8bit,那么每1次读的数据应当是每1次写的数据中8bit的那部分。
2. 模块分解与参数描述
首先是异步FIFO的架构,基于自顶向下的设计思路,将功能划分为多个模块,再相互连接
2.1. 顶层模块 async_fifo
由以下几个模块构成:wr_logc、rd_logic和simple dual-port RAM,其中
● Simle dual-port RAM完成数据的存储和读写功能
● wr_logic完成写逻辑的实现,同时产生waddr与双口RAM通信。
rd_logic完成读逻辑的实现,同时产生raddr与双口RAM通信。
注意写逻辑还需要输出full信号,读逻辑需要输出empty信号,这两个信号均与读写指针有关,所以需要wptr和rptr的跨时钟域通信。
参数描述
先是端口信号描述
Signal | Direction | Width(bits) | Description |
---|---|---|---|
rstn | input | 1 | FIFO异步复位 |
wclk | input | 1 | 写时钟 |
wr_en | input | 1 | 写使能 |
wdata | input | WDATA_WIDTH | 写数据 |
full | output | 1 | 写满标志 |
pfull | output | 1 | 可编程满标志。当FIFO内数据数目达到`PROG_DEPTH时,该信号拉高 |
rclk | input | 1 | 读时钟 |
rd_en | input | 1 | 读使能 |
rdata | output | RDATA_WIDTH | 读数据 |
empty | output | 1 | 读空标志 |
之后是对FIFO配置参数描述
Parameter | Units | Description |
---|---|---|
WDATA_WIDTH | bit | FIFO写数据宽度,即伪双口RAM的写数据宽度 |
RDATA_WIDTH | bit | FIFO读数据宽度,即伪双口RAM的读数据宽度 |
ASYNC_FIFO_DEPTH | bit | FIFO深度 |
PROG_DEPTH | bit | 可编程数据深度。当FIFO内数据数目达到该值时,pfull拉高 |
ADDR_WIDTH | bit | 用于设定SDRAM地址位宽 |
2.2. 伪双口RAM sdram
异步FIFO由读写两个逻辑组成,需要对写数据输入输出,所以使用伪双口RAM就行了。
但是注意我们要实现读写数据宽度不同的FIFO,所以此处的RAM读写宽度也不同,相应的读写地址宽度也不同。
注意这几个RAM的区别
单口RAM(Single-port RAM):只有一端一个时钟,可读、写
伪双口RAM(Simple Dual-port RAM):一端只读,另一端只写,且读写时钟不同
真双口RAM(True Dual-port RAM):两个端口都可读、写,且两端口的时钟不同
单端口和双端口RAM的区别
参数描述
先是端口信号描述
Signal | Direction | Width(bits) | Description |
---|---|---|---|
rstn | input | 1 | RAM异步复位 |
wclk | input | 1 | 写时钟 |
wr_en | input | 1 | 写使能 |
wdata | input | WDATA_WIDTH | 写数据 |
waddr | input | ADDR_WIDTH | 写地址 |
rclk | input | 1 | 读时钟 |
rd_en | input | 1 | 读使能 |
rdata | output | RDATA_WIDTH | 读数据 |
raddr | input | ADDR_WIDTH | 读地址 |
之后是对伪双口RAM配置参数描述
Parameter | Units | Description |
---|---|---|
WDATA_WIDTH | bit | 伪双口RAM写数据宽度 |
RDATA_WIDTH | bit | 伪双口RAM读数据宽度 |
ADDR_WIDTH | bit | 伪双口RAM地址宽度,需要根据FIFO深度和写数据宽度计算得出。注意此处令读写地址位宽相同 |
2.3. 写逻辑 wr_logic
写逻辑这边主要实现对伪双口RAM的写控制,同时基于读逻辑模块的rptr信号产生FIFO的full和pfull信号
参数描述
端口信号描述
Signal | Direction | Width(bits) | Description |
---|---|---|---|
rstn | input | 1 | 异步复位 |
wclk | input | 1 | 写时钟 |
wr_en | input | 1 | 写使能 |
wdata | input | WDATA_WIDTH | 写数据 |
rptr_g | input | ADDR_WIDTH+1 | 带扩展位的Grey码读指针,注意是rclk域下的信号 |
Signal | Direction | Width(bits) | Description |
---|---|---|---|
wr_en_ram | output | 1 | 双口RAM的写使能 |
waddr_ram | output | ADDR_WIDTH | 双口RAM的写地址 |
wdata_ram | output | WDATA_WIDTH | 双口RAM的写数据 |
full | output | 1 | FIFO写满标志 |
pfull | output | 1 | FIFO可编程写满标志 |
wptr_g | output | ADDR_WIDTH+1 | 带扩展位的Grey码写指针 |
之后是需要配置的参数描述
Parameter | Units | Description |
---|---|---|
WDATA_WIDTH | bit | 伪双口RAM写数据宽度 |
ADDR_WIDTH | bit | 伪双口RAM地址宽度,需要根据FIFO深度和写数据宽度计算得出 |
2.4. 读逻辑 rd_logic
读逻辑这边主要实现对伪双口RAM的读控制,同时基于写逻辑模块的wptr信号产生FIFO的empty信号
参数描述
端口信号描述
Signal | Direction | Width(bits) | Description |
---|---|---|---|
rstn | input | 1 | 异步复位 |
rclk | input | 1 | 读时钟 |
rd_en | input | 1 | 读使能 |
rdata_ram | input | RDATA_WIDTH | 读数据,来自于RAM |
wptr_g | input | WADDR_WIDTH+1 | 带扩展位的Grey码写指针,注意是wclk域下的信号 |
Signal | Direction | Width(bits) | Description |
---|---|---|---|
rd_en_ram | output | 1 | 送入RAM的读使能 |
raddr_ram | output | ADDR_WIDTH | 双口RAM的读地址 |
rdata | output | RDATA_WIDTH | FIFO读数据 |
empty | output | 1 | FIFO读空标志 |
rptr_g | output | ADDR_WIDTH+1 | 带扩展位的Grey码读指针 |
之后是需要配置的参数描述
Parameter | Units | Description |
---|---|---|
RDATA_WIDTH | bit | 伪双口RAM读数据宽度 |
RADDR_WIDTH | bit | 伪双口RAM读地址宽度,需要根据FIFO深度和读数据宽度计算得出 |
3. 逻辑设计
时序以及代码比较重要,见这个blog
4. 测试
4.1. 写数据位宽 > 读数据位宽 测试
这种测试中,参数定义如下
`define ASYNC_FIFO_DEPTH 4096
`define WDATA_WIDTH 16
`define RDATA_WIDTH 4
`define PROG_DEPTH 2048
`define ADDR_WIDTH 10
根据async_fifo_tb.sv的内容,先写再读
使用questasim编译仿真,执行run 13us
观察波形
首先,先写入数据
查看读出的数据
可以看出写是每拍写16bit、读是每拍读4bit,读的数据可按照先高位后低位拼接成写的数据,并且满足先入先出的顺序。
4.2. 写数据位宽 < 读数据位宽 测试
参数定义如下
`define ASYNC_FIFO_DEPTH 4096
`define WDATA_WIDTH 8
`define RDATA_WIDTH 16
`define PROG_DEPTH 2048
`define ADDR_WIDTH 9
写数据波形
读数据波形
可以看出写是每拍写8bit、读是每拍读16bit,写的数据可按照先高位后低位拼接成读的数据,并且满足先入先出的顺序。
4.3. full、pfull和empty 测试
参数设定如下
`define ASYNC_FIFO_DEPTH 4096
`define WDATA_WIDTH 16
`define RDATA_WIDTH 8
`define PROG_DEPTH 2048
`define ADDR_WIDTH 9
波形如下图所示,可以看到full、pfull和empty如期变化
4.4. 写数据位宽 = 读数据位宽 测试
参数设定如下
`define ASYNC_FIFO_DEPTH 64
`define WDATA_WIDTH 8
`define RDATA_WIDTH 8
在Linux环境下,使用VCS进行编译仿真,使用Verdi查看波形。
图中
wptr_rd_logic
表示wr_logic
模块向rd_logic
模块发送的写指针
rptr_d2
表示wr_logic
模块收到的来自rd_logic
模块的读指针、打拍之后的结果图中
rptr_wr_logic
表示rd_logic
模块向wr_logic
模块发送的读指针
wptr_d2
表示rd_logic
模块收到的来自wr_logic
模块的写指针、打拍之后的结果