FPGA中FIFO的应用(二)——异步FIFO设计


⭐️作者简介:小瑞同学,主要学习FPGA、信号处理、通信等。
🍎个人主页:小瑞同学的博客主页
🌻个人信条:越努力,越幸运!
⏰日期:2023.12.3
📖文章内容概述:介绍了异步FIFO的基本工作原理和深度计算,通过仿真观察了其读写过程。


连载系列:FPGA中FIFO的应用
完整工程已上传至CSDN下载链接


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_ptrwr_ptr所经过的路径相同,反应在变量数值上就是rd_ptr=wr_ptr
  • 对于满标志,wr_ptr需要比rd_ptr多走一圈,反应在变量数值上就是rd_ptr和wr_ptr的最高位不同,而其它位相同

在这里插入图片描述

2.2 空满标志的时钟域同步

☀️由于读写过程由不同时钟控制,所以在比较读写指针前需要先对其进行时钟域同步。但是这里存在一个问题,如果直接使用二进制数据进行同步的话,会出现多位数据同时跳变,这样就增大了出错的概率。格雷码可以很好的解决这个问题,相邻的两个格雷码之间只有1位不同,所以在同步之前需要先将二进制数据转化为格雷码。

2.3 二进制数和格雷码之间的转换

四位二进制数和格雷码的对应关系如下图所示。

在这里插入图片描述

具体的转化公式如下:

格雷码转二进制:
在这里插入图片描述

二进制转格雷码:
在这里插入图片描述
☀️我们将二进制地址指针转化为对应的格雷码wr_grayrd_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/810=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 250Byte125Byte=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 仿真结果

可以看到,读写过程都按照各自的时钟频率进行,空满标志可以正确产生。

在这里插入图片描述


❤️如果觉得文章对你有所帮助的话,别忘了点个收藏哦~
❤️更多优质内容可浏览本人主页👇,期待再次与你相遇!
🎉🎉🎉🎉🎉🎉小瑞同学的博客主页🎉🎉🎉🎉🎉🎉

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hi小瑞同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值