⭐️作者简介:
小瑞同学
,一个努力精进的FPGA和通信学习者。
🍎个人主页:小瑞同学的博客主页
🌻个人信条:越努力,越幸运!
⏰日期:2023.12.3
🔍来源:自学经历
📖文章内容概述:介绍了异步FIFO的基本工作原理和深度计算,通过仿真观察了其读写过程。
连载系列:FPGA中FIFO的应用
完整工程已上传至CSDN:下载链接
- 同步FIFO设计
- 异步FIFO设计
- Vivado FIFO IP核的调用
1.异步FIFO简介
1.1 概述
☀️异步FIFO是指读写受两个不同的时钟控制,一般用于数据的跨时钟域传递以及不同数据宽度的数据接口。对于单bit信号跨时钟域可以使用简单的打拍进行同步,而对于多bit信号的跨时钟域传递,就需要使用异步FIFO了。
1.2 主要参数
☀️异步FIFO的主要参数和同步FIFO基本相同,主要的区别的是异步FIFO有读时钟rd_clk和写时钟wr_clk两个时钟。
参数 | 意义 |
---|---|
读时钟 | —— |
写时钟 | —— |
FIFO的宽度 | FIFO一次读写的数据位宽 |
FIFO的深度 | FIFO中存储的数据个数 |
空标志 | FIFO已空时发出空信号,以阻止读操作继续读取无效数据 |
满标志 | FIFO已满时发出满信号,以阻止写操作继续向FIFO中写数据而导致FIFO溢出 |
2.空满判断
2.1 高位扩展法
☀️异步FIFO的设计难点在于空满标志的判断,由于其读写时钟不同,无法用上篇文章提到的的计数器法进行判断。
☀️这里我们采用高位扩展法,高位扩展法就是将读写地址变量扩展出一个最高位作为判断位,判断位和地址组成读指针变量rd_ptr和写指针变量wr_ptr,对于二进制地址数据,判断方法如下:
- 对于空标志,rd_ptr和wr_ptr所经过的路径相同,反应在变量数值上就是 rd_ptr=wr_ptr ;
- 对于满标志,wr_ptr需要比rd_ptr多走一圈,反应在变量数值上就是rd_ptr和wr_ptr的最高位不同,而其它位相同 。
2.2 空满标志的时钟域同步
☀️由于读写过程由不同时钟控制,所以在比较读写指针前需要先对其进行时钟域同步。但是这里存在一个问题,如果直接使用二进制数据进行同步的话,会出现多位数据同时跳变,这样就增大了出错的概率。格雷码可以很好的解决这个问题,相邻的两个格雷码之间只有1位不同,所以在同步之前我们先将二进制数据转化为格雷码。
2.3 二进制数和格雷码之间的转换
四位二进制数和格雷码的对应关系如下图所示。
具体的转化公式如下:
格雷码转二进制:
二进制转格雷码:
我们将二进制地址指针转化为对应的格雷码wr_gray和rd_gray,然后进行时钟同步:
- 在写时钟上升沿,将rd_gray打两拍延时得到rd_gray_d2。
- 在读时钟上升沿,将wr_gray打两拍延时得到wr_gray_d2。
在转化为格雷码后,空满标志的判断标准将不同于二进制:
- 空标志:rd_gray==wr_gray_d2。
- 满标志:wr_gray和rd_gray_d2的最高位和次高位相反,而其它各位相等。
例如:对于深度为8的FIFO,其地址宽度为3,当rd_ptr的二进制数为0001时,如果是满状态的话wr_ptr应该为1001,参考上表,对应的格雷码分别为0001和1101,即最高位和次高位相反,其它各位相等。
3.异步FIFO的深度计算
☀️在跨时钟域数据传递用到异步FIFO时,往往需要计算FIFO的理论最小深度,而实际FIFO深度通常要大于计算值。
☀️现考虑这样的场景,假设模块A以一定的时钟频率不间断地向FIFO中写数据,而模块B以慢于A的时钟频率不间断地从FIFO中读取数据,如果系统一直工作,那么FIFO中的数据会越累积越多,需要FIFO的深度就是无穷大的,这并不现实。所以只有在突发传输过程中讨论FIFO的深度才有意义。要确定FIFO的深度,就要计算出在突发读写这段时间内有多少数据没有被读走。
突发传输:短时间内进行相对高带宽的数据传输
这里举一个简单的题:
问:一个8bit位宽的FIFO,输入时钟是100MHz,输出时钟是50MHz,设计一个读写包文的缓存是2Kbit,两个包文间的发送时间间隔足够大,求异步FIFO的最小读写深度。
答:
- 发送包文的突发长度是 250 B y t e 250Byte 250Byte,每个地址存取1个 B y t e Byte Byte
- 输入时钟周期为 10 n s 10ns 10ns,输出时钟周期为 20 n s 20ns 20ns
- 前一模块发送包文,写入FIFO所用的时间为 2000 / 8 ∗ 10 = 2500 n s 2000/8*10=2500 ns 2000/8∗10=2500ns
- 在这段时间内,后一模块接收包文,从FIFO中读出读取的数据量为 2500 / 20 = 125 B y t e 2500/20=125Byte 2500/20=125Byte
- 异步FIFO理论最小深度为 250 B y t e − 125 B y t e = 125 B y t e 250Byte-125Byte=125Byte 250Byte−125Byte=125Byte
有关FIFO深度计算更细致的讲解,可参考该文章:FIFO深度计算
4.verilog代码
module FIFO_asyn
#(parameter FIFO_WIDTH=8,
parameter FIFO_DEPTH=16)
(
input wclk ,
input rclk ,
input wr_rstn,
input rd_rstn,
input wr_en ,
input rd_en ,
input [FIFO_WIDTH-1:0] din ,
output empty ,
output full ,
output reg [FIFO_WIDTH-1:0] dout
);
localparam ADDR_LEN=$clog2(FIFO_DEPTH);
localparam PTR_LEN=ADDR_LEN+1;
//读写地址
wire [ADDR_LEN-1:0] wr_addr;
wire [ADDR_LEN-1:0] rd_addr;
//读写指针
reg [PTR_LEN-1:0] wr_ptr;
reg [PTR_LEN-1:0] rd_ptr;
//读写指针对应的格雷码
wire [PTR_LEN-1:0] wr_gray;
wire [PTR_LEN-1:0] rd_gray;
//打拍寄存器变量
reg [PTR_LEN-1:0] wr_gray_d1;
reg [PTR_LEN-1:0] wr_gray_d2;
reg [PTR_LEN-1:0] rd_gray_d1;
reg [PTR_LEN-1:0] rd_gray_d2;
//FIFO
reg [FIFO_WIDTH-1:0] fifo [0:FIFO_DEPTH-1];
//读写指针为1bit判断位和读写地址的拼接
assign wr_addr=wr_ptr[ADDR_LEN-1:0];
assign rd_addr=rd_ptr[ADDR_LEN-1:0];
//二进制转格雷码
assign wr_gray=(wr_ptr>>1)^wr_ptr;
assign rd_gray=(rd_ptr>>1)^rd_ptr;
//空满判断
assign empty=(wr_gray_d2==rd_gray) ? 1'b1:1'b0;
assign full=(wr_gray=={~rd_gray_d2[PTR_LEN-1:PTR_LEN-2],rd_gray_d2[PTR_LEN-3:0]})? 1'b1: 1'b0;
//写FIFO
always@(posedge wclk or negedge wr_rstn)begin
if(!wr_rstn)begin
wr_ptr<='b0;
end
else if(wr_en&&!full)begin
fifo[wr_addr]<=din;
wr_ptr<=wr_ptr+1'b1;
end
else begin
wr_ptr<=wr_ptr;
end
end
//读FIFO
always@(posedge rclk or negedge rd_rstn)begin
if(!rd_rstn)begin
rd_ptr<='b0;
dout<='b0;
end
else if(rd_en&&!empty)begin
dout<=fifo[rd_addr];
rd_ptr<=rd_ptr+1'b1;
end
else begin
rd_ptr<=rd_ptr;
dout<=dout;
end
end
//将写指针同步到读时钟域
always@(posedge rclk or negedge rd_rstn)begin
if(!rd_rstn)begin
wr_gray_d1<='b0;
wr_gray_d2<='b0;
end
else begin
wr_gray_d1<=wr_gray;
wr_gray_d2<=wr_gray_d1;
end
end
//将读指针同步到写时钟域
always@(posedge wclk or negedge wr_rstn)begin
if(!wr_rstn)begin
rd_gray_d1<='b0;
rd_gray_d2<='b0;
end
else begin
rd_gray_d1<=rd_gray;
rd_gray_d2<=rd_gray_d1;
end
end
endmodule
5.仿真分析
5.1 参考testbench文件
`timescale 1ns / 1ps
module tb();
parameter FIFO_WIDTH=8;
reg wclk;
reg rclk;
reg rstn;
reg wr_en;
reg rd_en;
reg [FIFO_WIDTH-1:0] din;
wire empty;
wire full;
wire [FIFO_WIDTH-1:0] dout;
initial begin
wclk=1'b0;
rclk=1'b0;
rstn=1'b0;
wr_en=1'b0;
rd_en=1'b0;
din='b0;
#15;
rstn=1'b1;
repeat(16)wr_only;
repeat(16)rd_only;
repeat(5)wr_rd;
#30;
wr_en=1'b0;
rd_en=1'b0;
$stop;
end
always #10 wclk=~wclk;
always #20 rclk=~rclk;
//只写的任务
task wr_only;
begin
@(negedge wclk)begin
wr_en=1'b1;
rd_en=1'b0;
din={$random}%(2^FIFO_WIDTH);//生成0~2^FIFO_WIDTH-1的随机数
end
end
endtask
//只读的任务
task rd_only;
begin
@(negedge rclk)begin
rd_en=1'b1;
wr_en=1'b0;
end
end
endtask
//读写的任务
task wr_rd;
begin
@(negedge rclk)begin
rd_en=1'b1;
end
@(negedge wclk)begin
wr_en=1'b1;
din={$random}%(2^FIFO_WIDTH);
end
end
endtask
FIFO_asyn FIFO_asyn_u
(
. wclk (wclk),
. rclk (rclk),
. wr_rstn (rstn),
. rd_rstn (rstn),
. wr_en (wr_en),
. rd_en (rd_en),
. din (din),
. empty (empty),
. full (full),
. dout (dout)
);
endmodule
5.2 仿真结果
可以看到,读写过程都按照各自的时钟频率进行,空满标志可以正确产生。
文章来源:https://www.toymoban.com/news/detail-772327.html
❤️如果觉得文章对你有所帮助的话,别忘了点个收藏和赞哦~
❤️更多优质内容可浏览本人主页👇,期待再次与你相遇!
🎉🎉🎉🎉🎉🎉小瑞同学的博客主页🎉🎉🎉🎉🎉🎉文章来源地址https://www.toymoban.com/news/detail-772327.html
到了这里,关于FPGA中FIFO的应用(二)——异步FIFO设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!