异步FIFO原理与Verilog简单实现

异步FIFO

本文代码参考Simulation and Synthesis Techniques for Asynchronous FIFO Design Clifford E. Cummings, Sunburst Design, Inc FIFO style #1
异步FIFO是读写时钟频率不同的先入先出数据缓存器。一个系统往往有多个时钟,跨时钟域数据传输由异步FIFO实现。异步FIFO实现时必须考虑跨时钟域问题。跨时钟域问题,是指输入触发器的信号没有满足触发器的建立和保持时间,导致触发器输出t时间的中间电平,最终稳定到未知电平(高电平或低电平)的现象。其中t时间的中间电平可以沿着触发器传播。

读和写

FIFO通过读指针写指针,来确定RAM内部数据位置。读端需要知道写端指针位置 来发出读空信号。写端需要知道读端指针位置 来发出写满信号。FIFO通过这两个信号限制写入和读出,防止指针乱套。
假设有一个深度为 8 的RAM,地址 3 bit,将地址在最高位扩展一位,用来表示写指针超过RAM最大地址(该 4bit 地址记为wrptrwrptr的低3bit接到RAM记作wraddr,读也一样)。需要记住 写入数据后再将地址加1,该过程由一个时钟上升沿完成,读出也是一样。

FIFO工作流程如下:

  1. rdptrwrptr指向RAM地址 0。由于wrptr==rdptr,置高 read empty
  2. 写使能 clk上升沿开始写数据,同时wrptr变为 1
  3. 继续写直到wrptr==7(0111)
  4. 再写一个后wrptr==8(1000),此时rdptr==0(0000) 由于低3bit相同 高位不同,置高 write full

读端也是一样,这里不讨论,增加1bit就可以区分 wraddr==rdaddr时 是满还是空。如果不考虑亚稳态,使用上面的方法就能构建正确的FIFO了。但是由于时钟频率不同,读端rd_clk 去采 写端rdptr可能采到错误数据。造成empty 信号不准确,但是empty和full不准确都是可以接受的

使用格雷码传输指针

转换表
在使用二进制码时,空满如下产生。

assign wr_full  = (wrptr == {~rdptr[3],rdptr[2:0]});
assign rd_empty = (rdptr == wrptr);

在使用格雷码时,空满信号产生方式不一样。格雷码次高位 0 ~ 7和8 ~ 15之间是对称的。
例如:wrptr=8,rdptr=0,在二进制码里 wrptr高位与rdptr高位相反说明写超过一轮,低3bit相同说明指向同一RAM地址,此时说明FIFO写满。在格雷码里需要将次高位取反再与低0 ~ 7内的rdptr做比较。
然后读取8个数据,rdptr变为8,wrptr==rdptr转成格雷码也相等,此时FIFO读空。
然后写入8个数据,wrptr变为0,rdptr=8,wrptr高位与rdptr高位相反说明写超过一轮。

assign wr_full  = (wrptr_gray == {~rdptr_gray_r1[3:2],rdptr_gray_r1[1:0]});
assign rd_empty = (rdptr_gray == wrptr_gray_r1);

结构

Clifford描述的异步FIFO结构图:
在这里插入图片描述
其中的sync_r2w、sync_w2r用于缓解亚稳态的传播,并将指针同步到各自时钟域以进行比较,其中传输的是格雷码。
讨论极限情况:

写时钟100MHz,读时钟1MHz。以下说明以上图中的信号举例(格雷码),并且FIFO深度8

1. 写7个数据,下一上升沿写入8,wptr变为1100,并输出wq2_rptr,假设采集的wq2_rptr是错误值(这是因为rptr从0000变为0001,wclk恰好采到了过度态)。由于写时钟很快,wq2_rptr之前的值是0000,新的值(0001)只改变了一位,这一位又采错了变为0000,那么wfull会提早给出,指示满了但还有一个可写。
2. 上升沿读第4个,rptr变为0101,并输出rq2_wptr,上一次rq2_wptr=0110,由于读时钟很慢,这次采样的rq2_wptr=1001,中间跳过了很多格雷码,4比特发生改变(0110=>1001),假设rq2_wptr采样的4bit全错,变成了0101,居然和rptr和一样,那么rempty会意外置高。还有一种情况是 rd端发现读空,置高rempty,但写速度很快已经写入多个数据,这时就是虚空。wfull和rempty是保守估计,不影响使用。

转换表
写慢读快的情况类似。
附代码:

/************************************************************************************
*   Name         :async_fifo.v
*   Description  :
*   Origin       :210430
*   Author       :helrori2011@gmail.com
*   Reference    :
************************************************************************************/
module async_fifo //(First_Word_Fall_Through FIFO,数据提前输出,可能不利于时序收敛)
#(
    parameter integer DATA_WIDTH = 16 ,
    parameter integer ADDR_WIDTH = 8   //深度只能是2**ADDR_WIDTH 
)
(
    input   wire                 wr_rst  ,    
    input   wire                 wr_clk  ,    
    input   wire                 wr_ena  ,    
    input   wire[DATA_WIDTH-1:0] wr_dat  , 
    output  wire                 wr_full ,

    input   wire                 rd_rst  ,  
    input   wire                 rd_clk  ,    
    input   wire                 rd_ena  ,    
    output  wire[DATA_WIDTH-1:0] rd_dat  , 
    output  wire                 rd_empty 
);
reg  [DATA_WIDTH-1:0]mem[0:2**ADDR_WIDTH-1];
reg  [ADDR_WIDTH  :0]wrptr;
reg  [ADDR_WIDTH  :0]rdptr;
wire [ADDR_WIDTH-1:0]wraddr = wrptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH-1:0]rdaddr = rdptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH  :0]wrptr_gray = (wrptr>>1)^wrptr;
wire [ADDR_WIDTH  :0]rdptr_gray = (rdptr>>1)^rdptr;

reg  [ADDR_WIDTH  :0]wrptr_gray_r0;
reg  [ADDR_WIDTH  :0]wrptr_gray_r1;
reg  [ADDR_WIDTH  :0]rdptr_gray_r0;
reg  [ADDR_WIDTH  :0]rdptr_gray_r1;
assign wr_full  = (wrptr_gray == {~rdptr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rdptr_gray_r1[ADDR_WIDTH-2:0]});
assign rd_empty = (rdptr_gray == wrptr_gray_r1);//在有些设计中,full和empty提早一个时钟出现,并加了一级寄存,这有助于收敛
always @(posedge wr_clk or posedge wr_rst) begin
    if (wr_rst) begin
        rdptr_gray_r0 <= 'd0;
        rdptr_gray_r1 <= 'd0;
    end else begin
        rdptr_gray_r0 <= rdptr_gray;
        rdptr_gray_r1 <= rdptr_gray_r0;
    end
end
always @(posedge rd_clk or posedge rd_rst) begin
    if (rd_rst) begin
        wrptr_gray_r0 <= 'd0;
        wrptr_gray_r1 <= 'd0;
    end else begin
        wrptr_gray_r0 <= wrptr_gray;
        wrptr_gray_r1 <= wrptr_gray_r0;
    end
end
always @(posedge wr_clk or posedge wr_rst) begin
    if (wr_rst) begin
        mem[0]      <= 'd0;
        wrptr       <= 'd0;
    end else if (wr_ena&&(!wr_full)) begin
        mem[wraddr] <= wr_dat;
        wrptr       <= wrptr + 1'd1;
    end else begin
        mem[wraddr] <= mem[wraddr];
        wrptr       <= wrptr;
    end
end
always @(posedge rd_clk or posedge rd_rst) begin
    if (rd_rst) begin
        rdptr       <= 'd0;
    end else if (rd_ena&&(!rd_empty)) begin
        rdptr       <= rdptr + 1'd1;
    end else begin
        rdptr       <= rdptr;
    end
end
assign rd_dat = mem[rdaddr];
endmodule

testbench

//~ `New testbench
`timescale  1ns / 1ps

module tb_async_fifo;
// async_fifo Parameters
parameter PERIOD_WR   = 1000 ;
parameter PERIOD_RD   = 20 ;
parameter DATA_WIDTH  = 16 ;
parameter ADDR_WIDTH  = 8  ;
parameter DATA_DEPTH  = 2**ADDR_WIDTH;
// async_fifo Inputs
reg   wr_clk                               = 0 ;
reg   wr_ena                               = 0 ;
reg   [DATA_WIDTH-1:0] wr_dat              = 0 ;
reg   rd_clk                               = 0 ;
reg   rd_ena                               = 0 ;
// async_fifo Outputs
wire  wr_full                              ;
wire  [DATA_WIDTH-1:0] rd_dat              ;
wire  rd_empty                             ;
reg   rst=1;
always #(PERIOD_WR/2)  wr_clk=~wr_clk;
always #(PERIOD_RD/2)  rd_clk=~rd_clk;
async_fifo #(
    .DATA_WIDTH ( DATA_WIDTH ),
    .ADDR_WIDTH ( ADDR_WIDTH ))
u_async_fifo (
    .wr_rst  (rst     ),    
    .wr_clk  (wr_clk  ),    
    .wr_ena  (wr_ena  ),    
    .wr_dat  (wr_dat  ), 
    .wr_full (wr_full ),

    .rd_rst  (rst     ),  
    .rd_clk  (rd_clk  ),    
    .rd_ena  (rd_ena  ),    
    .rd_dat  (rd_dat  ), 
    .rd_empty(rd_empty) 
);

task automatic wr;
    input integer cnt;
    begin:w
        integer i;
        @(posedge wr_clk)
        #0 wr_ena=1;
        wr_dat=0;
        for ( i=0 ;i< cnt;i=i+1 ) begin
            @(posedge wr_clk)
            if (i==cnt-1) begin
                #0 wr_ena=0;
            end else begin
                #0 wr_dat=wr_dat+1;
            end
        end
    end
endtask 
task automatic rd;
    input integer cnt;
    begin:r
        integer j;
        @(posedge rd_clk)
        #0 rd_ena=1;
        for ( j=0 ;j< cnt;j=j+1 ) begin
            @(posedge rd_clk)
            if(rd_dat!=j)begin
                $display("error");
                $stop;
            end 
            if (j==cnt-1) begin
                #0 rd_ena=0;
            end
        end
    end
endtask 
task automatic massive_test;
    input real times;
    begin:massive_test
        real k;
        integer rands,wrn;
        real pct;
        for(k=0;k<times;k=k+1)begin
            pct= k/times;
            $display("%.0f%%",pct*100);
            rands = {$random}%(DATA_DEPTH+DATA_DEPTH/2);
            rands = rands+1;
            wrn = rands>DATA_DEPTH?DATA_DEPTH:rands;
            wr(rands);//writing side do not care about wr_full
            wait(!rd_empty) @(posedge rd_clk);// reading side will check rd_empty before read
            rd(wrn);
        end
    end
endtask 

initial
begin
    //$dumpfile("wave.vcd");
    //$dumpvars(0,tb_async_fifo);
    #(PERIOD_WR*20) rst  =  0;
    #(PERIOD_WR*20)
    massive_test(1000);
    $display("Test success");
    #(PERIOD_WR*20)
    $finish;
end
endmodule


full和empty提前输出并设置一级寄存的版本:
除了时序好一点,其他一样

module async_fifo //(First_Word_Fall_Through FIFO,数据提前输出)
#(
    parameter integer DATA_WIDTH = 16 ,
    parameter integer ADDR_WIDTH = 8   
)
(
    input   wire                 wr_rst  ,    
    input   wire                 wr_clk  ,    
    input   wire                 wr_ena  ,    
    input   wire[DATA_WIDTH-1:0] wr_dat  , 
    output  reg                  wr_full ,

    input   wire                 rd_rst  ,  
    input   wire                 rd_clk  ,    
    input   wire                 rd_ena  ,    
    output  wire[DATA_WIDTH-1:0] rd_dat  , 
    output  reg                  rd_empty 
);
reg  [DATA_WIDTH-1:0]mem[0:2**ADDR_WIDTH-1];
reg  [ADDR_WIDTH  :0]wrptr;
reg  [ADDR_WIDTH  :0]rdptr;
wire [ADDR_WIDTH  :0]wrptr_nxt = wrptr + (wr_ena&(~wr_full ));
wire [ADDR_WIDTH  :0]rdptr_nxt = rdptr + (rd_ena&(~rd_empty));
wire [ADDR_WIDTH-1:0]wraddr = wrptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH-1:0]rdaddr = rdptr[ADDR_WIDTH-1:0];
wire [ADDR_WIDTH  :0]wrptr_nxt_gray = (wrptr_nxt>>1)^wrptr_nxt;
wire [ADDR_WIDTH  :0]rdptr_nxt_gray = (rdptr_nxt>>1)^rdptr_nxt;
reg  [ADDR_WIDTH  :0]wrptr_gray;
reg  [ADDR_WIDTH  :0]rdptr_gray;

reg  [ADDR_WIDTH  :0]wrptr_gray_r0;
reg  [ADDR_WIDTH  :0]wrptr_gray_r1;
reg  [ADDR_WIDTH  :0]rdptr_gray_r0;
reg  [ADDR_WIDTH  :0]rdptr_gray_r1;
assign wr_full_nxt  = (wrptr_nxt_gray == {~rdptr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rdptr_gray_r1[ADDR_WIDTH-2:0]});
assign rd_empty_nxt = (rdptr_nxt_gray == wrptr_gray_r1);
always @(posedge rd_clk or posedge rd_rst) begin
    if (rd_rst) begin
        rdptr_gray <= 'd0;
    end else begin
        rdptr_gray <= rdptr_nxt_gray;
    end
end
always @(posedge wr_clk or posedge wr_rst) begin
    if (wr_rst) begin
        rdptr_gray_r0 <= 'd0;
        rdptr_gray_r1 <= 'd0;
    end else begin
        rdptr_gray_r0 <= rdptr_gray;
        rdptr_gray_r1 <= rdptr_gray_r0;
    end
end
always @(posedge wr_clk or posedge wr_rst) begin
    if (wr_rst) begin
        wrptr_gray  <= 'd0;
    end else  begin
        wrptr_gray  <= wrptr_nxt_gray;
    end
end
always @(posedge rd_clk or posedge rd_rst) begin
    if (rd_rst) begin
        wrptr_gray_r0 <= 'd0;
        wrptr_gray_r1 <= 'd0;
    end else begin
        wrptr_gray_r0 <= wrptr_gray;
        wrptr_gray_r1 <= wrptr_gray_r0;
    end
end


always @(posedge wr_clk or posedge wr_rst) begin
    if (wr_rst) begin
        mem[0]      <= 'd0;
        wrptr       <= 'd0;
    end else if (wr_ena&&(!wr_full)) begin
        mem[wraddr] <= wr_dat;
        wrptr       <= wrptr_nxt;
    end else begin
        mem[wraddr] <= mem[wraddr];
        wrptr       <= wrptr;
    end
    if (wr_rst) begin
        wr_full     <= 'd0;
    end begin
        wr_full     <= wr_full_nxt;
    end 
end
always @(posedge rd_clk or posedge rd_rst) begin
    if (rd_rst) begin
        rdptr      <= 'd0;
        rd_empty   <= 'd0;
    end else begin
        rdptr      <= rdptr_nxt;
        rd_empty   <= rd_empty_nxt;
    end
end

assign rd_dat = mem[rdaddr];
endmodule

部分波形

  1. 波形中的未定值,是由于mem未初始化和RAM提前输出造成的,这只在rd_empty时出现,不影响使用。
  2. 行为仿真不能说明所有问题

以上。
还是不太理解 使用格雷码可以避免下面情况出现
wrptr==rdptr时empty没有置高
wrptr、rdptr低3bit相同高位不同时full没有置高

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FIFO是一种先进先出的数据结构,常用于数据缓存和数据传输。FIFO的参数包括宽度和深度。宽度指的是FIFO一次读写操作的数据位数,而深度指的是FIFO可以存储多少个N位的数据(如果宽度为N)\[1\]。 FIFO的工作原理是通过读写时钟来实现数据的读写操作。读时钟用于读操作,每当读时钟沿来临时,FIFO会输出一个数据。写时钟用于写操作,每当写时钟沿来临时,FIFO会接收一个数据并存储在内部\[1\]。 FIFO还有两个标志位,即满标志和空标志。满标志是由FIFO的状态电路在FIFO已满或将要满时送出的一个信号,用于阻止写操作继续向FIFO中写数据而造成溢出。空标志是由FIFO的状态电路在FIFO已空或将要空时送出的一个信号,用于阻止读操作继续从FIFO中读出数据而造成无效数据的读出\[1\]。 在Verilog中,可以使用异步FIFO实现FIFO的功能。异步FIFO读写操作采用相互异步的不同时钟。在现代集成电路芯片中,由于设计规模的扩大,一个系统往往含有多个时钟域。异步FIFO是解决多时钟域接口电路问题的一种简便、快捷的解决方案。使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据\[3\]。 总结起来,FIFO是一种先进先出的数据结构,通过读写时钟实现数据的读写操作。它具有宽度、深度、满标志和空标志等参数。在Verilog中,可以使用异步FIFO实现FIFO的功能\[1\]\[3\]。 #### 引用[.reference_title] - *1* *2* *3* [异步FIFO---Verilog实现](https://blog.csdn.net/alangaixiaoxiao/article/details/81432144)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值