一、FIFO的相关概念、作用

1.概念

        FIFO(first-in-first-out),可作为以先入先出顺序存取数据的数据缓存器。读写时钟或存取速率不同时,需要一个数据缓存器作为桥梁进行存—取两端的数据传输,常用异步FIFO完成该任务。与BRAM的区别在于,FIFO无地址线,可以简单地实现缓冲数据的先入先出顺序传输,无需按地址存取数据。FIFO内存取的数据不由地址指向,而是由自动变化的读指针和写指针指向要读或写的下一个数据。

2.原理

        写指针等于读指针时,写满或读空。读指针追上写指针:读空;写指针追上读指针:写满

(从别处拿来的一张图,个人习惯,从右往左做井状图看其变化)

3.常见应用

        数据缓存、协议处理、串并转换、跨时钟域数据处理

4.FIFO分类

(1)同步FIFO(Independent Clock FIFO)

        读写时钟相同。

        简单判断满空:可以简单用一个额外的计数器实现判断满空:WP移动向下一写入位置时,计数器+1;RP移动向下一读出数据时,计数器-1;计数=FIFO深度时,写满,计数=0时,读空。

        

信号 描述
clk 系统时钟
rstn 系统复位信号
wr_en 写使能端
wr_data FIFO写数据
fifo_full FIFO的满标志位
rd_en 读使能端
rd_data FIFO读数据
fifo_empty FIFO的空标志位
(2)异步FIFO(Common Clock FIFO)

        读写时钟不同。

        跨时钟域处理判断满空:需要将写地址指针放入读时钟域(以读端clk为标准对写端地址指针采样),将其与读地址指针比较判断是否读空;将读地址指针放入写时钟域(以写端clk为标准对读端地址指针采样),将其与写地址比较判断是否写满。

跨时钟域处理可能产生亚稳态,一般有两种方法减少亚稳态:打两拍和使用格雷码表示地址指针,这里不进行深入讨论。

二、FIFO IP核(FIFO Generator)

        这里只对比较主要的可定制选项进行说明。

Interface Type

        调用FIFO IP核时可在Basic中选择下面3种Interface Type,本文目前暂时只讨论Native类型。各Interface Type可定制选项差别较大,但均有同步、异步FIFO的选项。

Ⅰ.Native Type

        1.各ports 说明

        在介绍各Interface Type下,IP核的不同选项前,最好先了解其端口。下面进行端口介绍,免去对部分不重要的IP选项的繁琐介绍。

基本信号
rst 输入 异步复位信号,能初始化所有的内部指针和输出寄存器。    
wr_clk 输入 写时钟
rd_clk 输入 读时钟
 din[a:0] 输入 单次clk写入的的a位宽数据
wr_en 输入   写入使能
rd_en 输入 读出使能
 
读失能时,dout保持输出寄存器上存的上一输出值
dout [b:0] 输出 单次clk输出的b位宽数据
写相关握手信号
注 :     受写时钟控制
full 输出 满标志:生效时说明FIFO满了,此时写操作可以直接忽视 已经写不下了
almost_full 输出 将满信号:生效时说明FIFO只能最多再允许一个写操作 下一周期写不下了
wr_ack 输出 写确认:生效时说明上个周期数据写入成功 上一周期写进去了

overflow

输出 溢出信号:表示上个周期的写请求被拒绝,因为FIFO已经满了 上一周期没写进去,溢出了
读相关握手信号
注        受读时钟控制
empty 输出 空标志:生效时说明FIFP空了,此时读操作直接忽视 已经读不出了
almost_empty 输出 将空信号:生效时说明FIFO只剩下最后一个数据可以读 下一周期读不出了
valid 输出 有效信号:生效时表示输出总线上的数据有效,上个周期数据读取成功 上一周期读出来了   
underflow 输出 下溢出:表示上一个周期的读操作被拒绝,因为FIFO是空的 上一周期没读出来,下溢了(此时dout保持输出寄存器存的上一输出值
rst相关信号
wr_rst_busy 输出 生效时表示整个写时钟域被rst了,处于复位状态  写时钟域目前复位状态,不要看此时的时序  
rd_rst_busy 输出 生效时表示整个读时钟域被rst了,处于复位状态 读时钟域目前复位状态,不要看此时的时序
可编程满空阈值以及对应握手信号
prog_full 输出

可编程满信号:在生成FIFO IP核前通过选项设置一个阈值来判断FIFO还有多少空间,该信号在FIFO剩余空间大于等于某阈值时=1。

满握手信号,

可提前设定阈值,也可动态设定阈值

prog_full_thresh

输入 可编程满阈值:输入可编程满信号的阈值,可动态调控 使/失能满握手信号的动态阈值

prog_full_thresh_assert

输入 可编程阈值生效信号:输入可编程满信号的上阈值,可动态设置,与prog_full_thresh_negate一同用于替代仅有一个阈值的prog_full_thresh 使能满握手信号的动态上阈值

prog_full_thresh_negate

输入 可编程阈值失效信号:输入可编程满信号的下阈值,可动态设置,与prog_full_thresh_assert一同用于替代仅有一个阈值的prog_full_thresh 失能满握手信号的动态下阈值
prog_empty 输出 可编程空:在生成FIFO IP核前通过选项设置,该信号在FIFO中已写入的数据数小于等于某阈值时=1。

空握手信号,

可提前设定阈值,也可动态设定阈值

prog_empty_thresh

输入 可编程空阈值:输入可编程满信号的阈值,可动态调控,用于替代不可动态调控的prog_empty 使/失能空握手信号的动态阈值

prog_empty_thresh_assert

输入 可编程空阈值生效:输入可编程空信号的下阈值,可动态设置,与prog_empty_thresh_negate一同用于替代仅有一个阈值的prog_empty_thresh 失能空握手信号的动态上阈值

prog_empty_thresh_negate

输入

可编程空阈值失效:输入可编程空信号的上阈值,可动态设置,与prog_empty_thresh_negate一同用于替代仅有一个阈值的prog_empty_thresh

使能空握手信号的动态下阈值
计数器信号
wr_data_count[d:0] 输出

写数据计数:显示FIFO中目前写进了多少个字。

例外情况是,当一个写操作发生在wr_clk/ clk的上升缘时,该写操作将只反映在下一个上升时钟边缘的wr_data_count上。如果D小于log2(FIFO depth)-1,总线通过删除最不重要的位而被截断。

        这条总线在UltraScale系类,通过一个common clock Block RAM-based 实现的(还要用上了Asymmetric Port Width端口)FIFO里也有效。

1.该字的位宽和din字的位宽一样,与dout字位宽不一定相同。不考虑位宽差,代表的数据量其实和读数据计数相同

2.受写时钟域控制。

3.写入字增加其数值,读出字也会减少其数值。

4.读出的字位宽小于该字时,依然视作减少一个该字。

5.在某时钟沿时开始写入一个字,则读写数据计数在未完成字的完全写入前都不变。比如:在上升沿开始最后一个字写入时,写满,full立即拉高,但读写数据计数在该字所有bit位都写入后才+1为满计数。

rd_data_count [c:0]

输出 读数据计数:显示FIFO中目前可以读多少个字。

        这条总线在UltraScale系类,通过一个common clock Block RAM-based 实现的(还要用上了Asymmetric Port Width端口)FIFO里也有效。

1.该字的位宽和dout字的位宽一样,与din字位宽不一定相同。不考虑位宽差,代表的数据量其实和写数据计数相同。

2.受读时钟域控制。

3.写入字也会增加其数值,读出字减少其数值。

4.写入的字位宽小于该字时,不视作增加一个该字。

5.在某时钟沿时开始读出一个字,则读写数据计数在未完成字的完全读出前都不变。比如:在上升沿开始最后一个字读出时,读空,empty立即拉高,但写数据计数在该字所有bit位都读出后才+1为空计数。

ECC相关信号

injectsbiterr

输入 使用ECC的时候插入一位错误

injectdbiterr

输入 使用ECC的时候插入两位错误

sbiterr

输出 单位错误:表示ECC解码器检测到并修复了块RAM或内置的FIFO宏上的单位错误。

dbiterr

输出 双位错误:表示ECC解码器在块RAM或内置的FIFO宏上检测到双位错误,并且FIFO核心中的数据已损坏。

        2.FIFO Implementation

        可选择使用的FIFO类型,基本只会用到BRAM或DRAM模式,前者是使用FPGA内部固定缓存资源,后者是使用LUT等逻辑资源搭建的FIFO功能。当BRAM资源紧张的时候,合理利用后者可以减轻BRAM负担。各类型FIFO支持的选项如下表。

        (1)Non-symmetric aspect ratios指读写数据位宽不同,成一定比例,即非对称读写位宽;(之后在说明Port的选项时将介绍)

        (2)First-word Fall-Through指前显模式读操作,提前将第一个要读取的数据放在输出寄存器dout上;(一般不用,用标准读操作即可)

        (3)Uses Built-in FIFO Primitives指使用内置的FIFO单元,从而可以通过在宽度和深度上级联内置 FIFO 来创建大型 FIFO;

本文将以异步BRAM FIFO(Independent Clocks Block RAM FIFO)为例进行说明。

        ★注:当选择异步FIFO时,会出现Synchronization stages —— 同步级数的选项,此选项的意思是,当FIFO中写入数据后,empty信号并不会立刻拉低,因为写入数据是基于写时钟的,而empty信号是基于读时钟的,要多拍寄存器才能去掉跨时钟亚稳态。

        如果Synchronization stages设定为2,则意味着empty会在FIFO中写入数据成功后的2个读时钟周期后拉低

        3.Ports

        (1)读操作Read Mode:Standard FIFO和First-word Fall-Through,一般使用标准读操作模式Standard FIFO

        (2)读写的数据参数Data Port Parameters:可选择非对称读写位宽Asymmetric Port Width,实现串并转换、位宽转换等效果。Vivado可以自动根据读写位宽判断FIFO深度。下面以为读写位宽4:1、1:4的情况和时序分别介绍读位宽>写位宽和写位宽>读位宽情况:

        a.读写位宽4:1

        b.读写位宽1:4

        4.Status Flags

        这里可选各个握手信号,详细含义可参考1.ports说明

        对Programma Flags的额外说明:

        Full与Empty均可选5个Programmable Type

        ①No Programmable Threshold Constant:不设置可编程满/空信号;

        ②Single Programmable Threshold Constant:在选项中设置一个固定的满/空阈值常数

        ③Multiple Programmable Threshold Constant:在选项中设置两个固定的满/空阈值常数,一个上阈值,一个下阈值;

        ④Single Programmable Threshold Input Port:允许一个输入信号作为一个可动态调控的满/空阈值;

        ⑤Multiple Programmable Threshold Constant:允许两个输入信号分别作为可动态调控的满/空阈值的上、下阈值;

        5.Data Counts

        增加读or写计数器并设计其计数位宽,输出的write_data_count直接显示目前FIFO中写入的字,read_data_count直接显示目前FIFO中可读出的字。参考1.ports说明。

三、FIFO IP核的调用和仿真

这里只调用异步FIFO

Ⅰ.Native Type

调用FIFO的summary:

        顶层文件:

`timescale 1ns / 1ps


module fifo_test(
    input rst,           
    input wr_clk,        
    input rd_clk,        
    input [0:0] din,     
    input wr_en,         
    input rd_en, 
    input [3 : 0] prog_empty_thresh_assert,
    input [3 : 0] prog_empty_thresh_negate,
    input [5 : 0] prog_full_thresh_assert,
    input [5 : 0] prog_full_thresh_negate,
    output [3: 0] dout,   
    output full,         
    output almost_full,  
    output wr_ack,       
    output overflow,     
    output empty,        
    output almost_empty, 
    output valid,        
    output underflow,  
    output [3: 0] rd_data_count,
    output [5: 0] wr_data_count,
    output prog_full,
    output prog_empty,  
    output wr_rst_busy,  
    output rd_rst_busy   
);
fifo_generator_1 fifo_inst1 (
  .rst(rst),                                            // input wire rst
  .wr_clk(wr_clk),                                      // input wire wr_clk
  .rd_clk(rd_clk),                                      // input wire rd_clk
  .din(din),                                            // input wire [0 : 0] din
  .wr_en(wr_en),                                        // input wire wr_en
  .rd_en(rd_en),                                        // input wire rd_en
  .prog_empty_thresh_assert(prog_empty_thresh_assert),  // input wire [3 : 0] prog_empty_thresh_assert
  .prog_empty_thresh_negate(prog_empty_thresh_negate),  // input wire [3 : 0] prog_empty_thresh_negate
  .prog_full_thresh_assert(prog_full_thresh_assert),    // input wire [5 : 0] prog_full_thresh_assert
  .prog_full_thresh_negate(prog_full_thresh_negate),    // input wire [5 : 0] prog_full_thresh_negate
  .dout(dout),                                          // output wire [3 : 0] dout
  .full(full),                                          // output wire full
  .almost_full(almost_full),                            // output wire almost_full
  .wr_ack(wr_ack),                                      // output wire wr_ack
  .overflow(overflow),                                  // output wire overflow
  .empty(empty),                                        // output wire empty
  .almost_empty(almost_empty),                          // output wire almost_empty
  .valid(valid),                                        // output wire valid
  .underflow(underflow),                                // output wire underflow
  .rd_data_count(rd_data_count),                        // output wire [3 : 0] rd_data_count
  .wr_data_count(wr_data_count),                        // output wire [5 : 0] wr_data_count
  .prog_full(prog_full),                                // output wire prog_full
  .prog_empty(prog_empty),                              // output wire prog_empty
  .wr_rst_busy(wr_rst_busy),                            // output wire wr_rst_busy
  .rd_rst_busy(rd_rst_busy)                            // output wire rd_rst_busy
);
    
endmodule

        testbench:

`timescale 1ns / 1ps


module fifo_test_tb();
reg rst;
reg wr_clk;
reg rd_clk;                  
reg [0:0] din;               
reg wr_en;                   
reg rd_en;                   
reg [3 : 0] prog_empty_thresh_assert;
reg [3 : 0] prog_empty_thresh_negate;
reg [5 : 0] prog_full_thresh_assert; 
reg [5 : 0] prog_full_thresh_negate; 
wire [3:0] dout;             
wire full;                   
wire almost_full;            
wire wr_ack;                 
wire overflow;               
wire empty;                  
wire almost_empty;           
wire valid;                  
wire underflow;              
wire [3 : 0] rd_data_count;          
wire [5 : 0] wr_data_count;          
wire prog_full;              
wire prog_empty;             
wire wr_rst_busy;            
wire rd_rst_busy;             

fifo_test fifo_test_1(
  .rst(rst),                                           
  .wr_clk(wr_clk),                                     
  .rd_clk(rd_clk),                                     
  .din(din),                                           
  .wr_en(wr_en),                                       
  .rd_en(rd_en),                                       
  .prog_empty_thresh_assert(prog_empty_thresh_assert), 
  .prog_empty_thresh_negate(prog_empty_thresh_negate), 
  .prog_full_thresh_assert(prog_full_thresh_assert),   
  .prog_full_thresh_negate(prog_full_thresh_negate),   
  .dout(dout),                                         
  .full(full),                                         
  .almost_full(almost_full),                           
  .wr_ack(wr_ack),                                     
  .overflow(overflow),                                 
  .empty(empty),                                       
  .almost_empty(almost_empty),                         
  .valid(valid),                                       
  .underflow(underflow),                               
  .rd_data_count(rd_data_count),                       
  .wr_data_count(wr_data_count),                       
  .prog_full(prog_full),                               
  .prog_empty(prog_empty),                             
  .wr_rst_busy(wr_rst_busy),                           
  .rd_rst_busy(rd_rst_busy)                            
);

always begin             //读写时钟翻转
    #5 wr_clk<=~wr_clk;
    #7 rd_clk<=~rd_clk;
end

initial begin            //设定控制信号初始值,规定结束时间
    din <= 0;
    wr_clk <= 0;
    rd_clk <= 0;
    rst <= 1;
    wr_en <= 0;
    rd_en <= 0;
    prog_empty_thresh_assert <= 61;
    prog_empty_thresh_negate <= 60;
    prog_full_thresh_assert  <= 2; 
    prog_full_thresh_negate  <= 3; 
    #5000 $finish;
end

initial begin           //实现读写使能信号和重置信号的变化、din的变化
    #5;
    repeat(150) #5 begin din <= 1;wr_en <= 1;rd_en <= 0; end   //深度为63,75次1位输入1'b1,溢出FIFO
    repeat(10)  #5 begin din <= 1;wr_en <= 0;rd_en <= 0; end
    repeat(200) #5 begin din <= 1;wr_en <= 0;rd_en <= 1; end   //约71次4位输出4'b1111,下溢FIFO
    repeat(20) #5  begin din <= 1;wr_en <= 1;rd_en <= 1; end   //同时允许读写信号
    #5 rst <= 1'b1;                                            //rst清空fifo
    #5 rst <= 1'b0;
    repeat(40) #5 begin  din <= 1;wr_en <= 1;rd_en <= 0;end     //20次1位输入1'b1
    repeat(10) #5 begin  din <= 1;wr_en <= 0;rd_en <= 0;end     //读写失能时din尝试写入1'b1
    repeat(12) #5 begin  din <= 1;wr_en <= 0;rd_en <= 1;end     //4次4位输出4'b1111,FIFO中仍有数据

    repeat(40) #5 begin  din <= 0;wr_en <= 1;rd_en <= 0;end    //20次1位输入1'b0  
    repeat(10) #5 begin  din <= 0;wr_en <= 0;rd_en <= 0;end
    repeat(10) #5 begin  din <= 1;wr_en <= 0;rd_en <= 1;end    //写失能,din尝试写入1'b1,读使能,3次4位输出4'b1111,FIFO中仍有数据
    repeat(20) #5 begin  din <= 1;wr_en <= 1;rd_en <= 1;end    //10次1位输入1'b0,同时7次4位输出4'b1111 ,FIFO中仍有数据 
    #5 rst <= 1'b1;                                             //rst清空fifo   
    #5 rst <= 1'b0;
    
    repeat(40) #5 begin  din <= 0;wr_en <= 1;rd_en <= 0;end     //20次1位输入1'b0
    repeat(10) #5 begin  din <= 0;wr_en <= 0;rd_en <= 0;end     //读写失能时din尝试写入1'b0
    repeat(12) #5 begin  din <= 0;wr_en <= 0;rd_en <= 1;end     //4次4位输出4'b0000,FIFO中仍有数据

    repeat(40) #5 begin  din <= 1;wr_en <= 1;rd_en <= 0;end    //20次1位输入1'b1  
    repeat(10) #5 begin  din <= 1;wr_en <= 0;rd_en <= 0;end
    repeat(10) #5 begin  din <= 0;wr_en <= 0;rd_en <= 1;end    //写失能,din尝试写入1'b0,读使能,3次4位输出4'b????,FIFO中仍有数据
    repeat(20) #5 begin  din <= 0;wr_en <= 1;rd_en <= 1;end    //10次1位输入1'b0,同时7次4位输出4'b??? ,FIFO中仍有数据
     
    repeat(800)#5 begin  din <= 1;wr_en <= 1;rd_en <= 0;end    //造成溢出
end


initial begin           //阈值信号的设定、重置信号的发送
    #10   rst = 0;
         prog_empty_thresh_assert <= 4;
         prog_empty_thresh_negate <= 5;
         prog_full_thresh_assert  <= 55;   
         prog_full_thresh_negate  <= 54;   
    #200 prog_empty_thresh_assert <= 10;
         prog_empty_thresh_negate <= 15;
         prog_full_thresh_assert  <= 63; 
         prog_full_thresh_negate  <= 60;
    #200 prog_empty_thresh_assert <= 10;
         prog_empty_thresh_negate <= 11;
         prog_full_thresh_assert  <= 40; 
         prog_full_thresh_negate  <= 30;      
    #200 prog_empty_thresh_assert <= 2;
         prog_empty_thresh_negate <= 3;
         prog_full_thresh_assert  <= 61; 
         prog_full_thresh_negate  <= 60; 
end


endmodule

        仿真结果:

        · 全图

        · 0~1000.00ns

        · 1700.00ns~2600.00ns

                放大后的2500.00ns~2600.00ns

        · 2600.00ns~3500.00ns

                放大后的2900.00ns~3300.00ns

        · 3500.00ns~4400.00ns

        · 4400.00ns~5000.00ns

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐