FPGA的Verilog设计(二)——异步FIFO


阅读本文前,建议先阅读下面几篇文章:
同步FIFO
二进制转格雷码的实现

前言

  在上篇文章同步FIFO中简要介绍了FIFO的基本概念以及同步FIFO的实现。本篇文章将重点介绍异步FIFO的工作原理以及硬件实现。

异步FIFO的工作原理

1. 概述

  异步FIFO的读写时钟不同,FIFO的读写需要进行异步处理,异步FIFO常用于多bit数据跨时钟域处理。异步FIFO一般有复位rst_n、读端口和写端口。读端口一般包括读时钟(rd_clk)、读使能(rd_en)、读数据(data_out)、读空(empty)。写端口一般包括写时钟(wr_clk)、写使能(wr_en)、写数据(data_in)、写满(wr_full)。

2. 地址的跨时钟问题

  实现异步FIFO难点仍在于如何得到读空和写满信号。由于异步FIFO的读写具有不同的时钟,读写独立,但是空满信号的判决仍需要通过读写地址(多bit数据)来实现,这里涉及到了多bit数据的跨时钟域处理,当然这里无法使用异步FIFO来实现这个跨时钟处理,毕竟我们就是为了做异步FIFO。多bit跨时钟处理还可以通过改变编码方式来降低亚稳态的发生概率。当读地址由4’b0111向4’b1000变化时,所有位都需要变化,如果写时钟恰好在地址变化时采样,写时钟得到的读地址是不确定的(为0000~1111中任意一个),因此为了降低该亚稳态的发生概率,地址采用格雷码编码。格雷码每次只变化一位,可以有效降低亚稳态的发生概率,同时单bit又可以采用打两拍的方法再次降低亚稳态发生的概率。
  综上所述,为有效解决地址的跨时钟问题,采取格雷码编码+打两拍的方式降低地址变化发生亚稳态的概率二进制转格雷码的实现

3. 空满信号的判决条件

  由于异步FIFO的读写地址采用格雷码,空满信号的判决条件不同于同步FIFO。同同步FIFO,可以将读写地址扩展一位,用于判断是否读写完一轮,即深度为8,地址为4bit。以下为深度为8的异步FIFO进行读写情况,以下只列举了四种操作,其余读写少于深度个数据的情况也是类似的。
在这里插入图片描述

  综上所述,读空empty信号拉高的判决条件是读写地址的格雷码相同。写满信号拉高的判决条件是读写地址的格雷码的高2位不同,其余位相同
  但是上述判决条件可能存在一些问题。由于读写地址需要打两拍,如果打拍期间还有读写操作,那读写地址又改变了,得到的并不是真正的空满信号,那会出现数据丢失的情况吗?举例说明如下:

  • 判断空信号:判断空信号,需要比较rd_clk的读地址rd_addr从wr_clk打拍到rd_clk的写地址wr_addr_w2r,如果在打拍过程中,还写入数据(wr_addr增加),那么用于判断空信号的wr_addr_w2r会小于实际的写地址wr_addr。如果此时判断为空,其实FIFO并不是真空,只不过此时FIFO不能再读出数据,此种情况并不会发生数据丢失。并且下一个时钟,空信号便会失效,并不会影响FIFO的正常使用。
  • 判断满信号:判断满信号,需要比较wr_clk的写地址wr_addr从rd_clk打拍到wr_clk的读地址rd_addr_r2w,如果在打拍过程中,还读出数据(rd_addr增加),那么用于判断满信号的rd_addr_r2w会小于实际的读地址rd_addr。如果此时判断为满,其实FIFO并不是真满,只不过此时FIFO不能再写入数据,此种情况并不会发生数据丢失。并且下一个时钟,满信号便会失效,并不会影响FIFO的正常使用。

异步FIFO的实现

module async_fifo#(  
    parameter    DEPTH = 16,  
    parameter    WIDTH = 8,
    parameter    ADDR_BIT = 4,      //log2(DEPTH)
    parameter    RAM_STYLE_VAL = "distributed" 
)(  
    input        wr_clk,  
    input        rst_n,  
    input        wr_en,  
    input  [WIDTH-1:0]  data_in,  
    input        rd_clk,  
    input        rd_en,  
    output  [WIDTH-1:0]  data_out,  
    output        full,  
    output        empty
);
	(*ram_style=RAM_STYLE_VAL*)
    reg  [WIDTH-1:0]   mem [DEPTH-1:0];
    reg  [WIDTH-1:0]   data_out_r;
    reg  [ADDR_BIT:0]  wr_addr;
    wire [ADDR_BIT:0]  wr_addr_gray;
    reg  [ADDR_BIT:0]  wr_addr_w2r1;
    reg  [ADDR_BIT:0]  wr_addr_w2r2;
    reg  [ADDR_BIT:0]  rd_addr;
    wire [ADDR_BIT:0]  rd_addr_gray;
    reg  [ADDR_BIT:0]  rd_addr_r2w1;
    reg  [ADDR_BIT:0]  rd_addr_r2w2;
    
    //*********************** Address ***********************//
    //write address
    always @ (posedge wr_clk or negedge rst_n)begin  
    	if(!rst_n)    wr_addr <= 'd0;  
    	else if(wr_en)    wr_addr <= wr_addr + 1;  
    	else    wr_addr <= wr_addr;
    end

    //write address - > gray
    assign  wr_addr_gray = (wr_addr>>1)^wr_addr;
    
    //Write address synchronization to read clock domain
    always @ (posedge wr_clk or negedge rst_n)begin  
    	if(!rst_n)    
    	   {wr_addr_w2r2,wr_addr_w2r1} <= 'd0;
      	else  
      	   {wr_addr_w2r2,wr_addr_w2r1} <= {wr_addr_w2r1,wr_addr_gray};
    end
    //read address
    always @ (posedge rd_clk or negedge rst_n)begin  
    	if(!rst_n)    
    	   rd_addr <= 'd0;  
    	else if(rd_en)   
    	   rd_addr <= rd_addr + 1;  
    	else    
    	   rd_addr <= rd_addr;  
    end
    //write address - > gray
    assign  rd_addr_gray = (rd_addr>>1)^rd_addr;
    //Read address synchronization to write clock domain
    always @ (posedge rd_clk or negedge rst_n)begin  
    	if(!rst_n)    
    	   {rd_addr_r2w2,rd_addr_r2w1} <= 'd0;  
    	else    
    	   {rd_addr_r2w2,rd_addr_r2w1} <= {rd_addr_r2w1,rd_addr_gray};
    end
    //************************* Data *************************//
    //write data
    always @ (posedge wr_clk)begin  
        if(wr_en) 
            mem[wr_addr] <= data_in;
        else      
            mem[wr_addr] <= mem[wr_addr];
    end
    //read data delay 1clk
    assign  data_out = data_out_r;
    always @ (posedge rd_clk or negedge rst_n)begin  
    	if(!rst_n)    
    	   data_out_r <= {WIDTH{1'b0}};  
    	else if(rd_en==1)    
    	   data_out_r <= mem[rd_addr];  
    	else    
    	   data_out_r <= data_out_r;
    end
    //********************** Full/Empty *********************//
    //Empty signal judgment
    assign empty = (wr_addr_w2r2 == rd_addr_gray);
    //Full signal judgment
    assign full  = ({~(rd_addr_r2w2[4:3]),rd_addr_r2w2[2:0]}==wr_addr_gray[4:0]);
    
endmodule

异步FIFO的仿真测试

module tb_async;
    reg      wr_clk,rd_clk;
    reg      rst_n;
    reg      wr_en;
    reg      rd_en;
    reg  [7:0] data_in;
    wire [7:0] data_out;
    wire    full;
    wire    empty;
    
    parameter WR_PERIOD = 10;
    parameter RD_PERIOD = 20 ;
    
    async_fifo async_fifo(  
        .wr_clk     (wr_clk     ),  
        .rst_n      (rst_n      ),  
        .wr_en      (wr_en      ),  
        .data_in    (data_in    ),  
        .rd_clk     (rd_clk     ),
        .rd_en      (rd_en      ), 
        .data_out   (data_out   ), 
        .full       (full       ),  
        .empty      (empty      )     
        );
        
    initial begin  
        wr_clk = 0;rd_clk=0;rst_n = 0;  
        wr_en <= 0;rd_en <= 0;data_in  = 'd0;    
        #15  rst_n = 1; 
        write_data(16); 
        #100
        read_data(16);
        #100
        $finish;
    end
    always # (WR_PERIOD/2) wr_clk = ~wr_clk;
    always # (RD_PERIOD/2) rd_clk = ~rd_clk;
    
    task  write_data(input [7:0] t);  
    begin
        repeat(t)begin
            @(posedge wr_clk)
            data_in <= {$random}%256;
            wr_en   <= 1;
        end
        @(posedge wr_clk) wr_en <= 0;data_in <= 'd0;
    end
    endtask  
    
    task  read_data(input [7:0] t);  
        integer  i;  
        begin    
            repeat(t)begin
                @(posedge rd_clk)
                rd_en   <= 1;
            end
            @(posedge rd_clk) rd_en <= 0;
        end  
    endtask      
endmodule

  仿真结果如下,可以发现该异步fifo逻辑正确。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值