一种简单的异步fifo

本文探讨了异步FIFO的设计,特别是如何处理双时钟域中的地址同步问题。通过使用格雷码和两级寄存器同步,确保了读写操作的正确性。即使在时钟域不匹配的情况下,也能有效避免数据错误。此外,还分析了读写时钟域中可能出现的错误情况及其对系统的影响。
摘要由CSDN通过智能技术生成

部分还是跟同步fifo相同的,可以看我的上一篇博客:两种简单的同步fifo(不同的输出时序)

直接上代码:

module fifo#(
    parameter RAM_WIDTH     = 6'd32,
    parameter RAM_DEPTH     = 4'd8,
    parameter ADDR_WIDTH    = 4'd4
)(
    input                            wclk,
    input                            rclk,
    input                            resetn,
    input       [RAM_WIDTH-1:0]      data_in,
    input                            write_en,
    input                            read_en,
    output      [RAM_WIDTH-1:0]      data_out,
    output                           full,
    output                           empty
);
    reg     [RAM_WIDTH-1:0]         ram[RAM_DEPTH-1:0];
    reg     [ADDR_WIDTH-1:0]        write_addr_wclk;
    reg     [ADDR_WIDTH-1:0]        read_addr_rclk;

    wire    [ADDR_WIDTH-1:0]        write_addr_gray_wclk;
    reg     [ADDR_WIDTH-1:0]        write_addr_gray_rclk0;
    reg     [ADDR_WIDTH-1:0]        write_addr_gray_rclk1;

    wire    [ADDR_WIDTH-1:0]        read_addr_gray_rclk;
    reg     [ADDR_WIDTH-1:0]        read_addr_gray_wclk0;
    reg     [ADDR_WIDTH-1:0]        read_addr_gray_wclk1;

    always@(posedge wclk or negedge resetn)//write addr wclk operation
        if(!resetn)
            write_addr_wclk <= 'b0;
        else if(write_en == 1'b1 && full != 1'b1)
            write_addr_wclk <= write_addr_wclk + 1'b1;
        else
            write_addr_wclk <= write_addr_wclk;
    
    always@(posedge rclk or negedge resetn)//read addr read operation
        if(!resetn)
            read_addr_rclk <= 'b0;
        else if(read_en == 1'b1 && empty != 1'b1)
            read_addr_rclk <= read_addr_rclk + 1'b1;
        else
            read_addr_rclk <= read_addr_rclk;
    
    always@(posedge wclk or negedge resetn)//sync
        if(!resetn)begin
            read_addr_gray_wclk0 <= 'b0;
            read_addr_gray_wclk1 <= 'b0;
        end
        else begin
            read_addr_gray_wclk0 <= read_addr_gray_rclk;
            read_addr_gray_wclk1 <= read_addr_gray_wclk0;
        end
    
    always@(posedge rclk or negedge resetn)//sync
        if(!resetn)begin
            write_addr_gray_rclk0 <= 'b0;
            write_addr_gray_rclk1 <= 'b0;
        end
        else begin
            write_addr_gray_rclk0 <= write_addr_gray_wclk;
            write_addr_gray_rclk1 <= write_addr_gray_rclk0;
        end

    always@(posedge wclk)//data out
        if(full == 1'b0 && write_en == 1'b1)
            ram[write_addr_wclk[ADDR_WIDTH-2:0]] <= data_in;


    assign full = (write_addr_gray_wclk[ADDR_WIDTH-1] != read_addr_gray_wclk1[ADDR_WIDTH-1])?
                  (write_addr_gray_wclk[ADDR_WIDTH-2] != read_addr_gray_wclk1[ADDR_WIDTH-2])?
                  (write_addr_gray_wclk[ADDR_WIDTH-3:0] == read_addr_gray_wclk1[ADDR_WIDTH-3:0])?1'b1:1'b0:1'b0:1'b0;
    assign empty = (write_addr_gray_rclk1 == read_addr_gray_rclk)?1'b1:1'b0;
    assign data_out = ((read_en == 1'b1) && (empty != 1'b1))?ram[read_addr_rclk[ADDR_WIDTH-2:0]]:'b0;
    assign write_addr_gray_wclk = (write_addr_wclk>>1)^write_addr_wclk;
    assign read_addr_gray_rclk = (read_addr_rclk>>1)^read_addr_rclk;

endmodule

时钟变成了两个:读时钟与写时钟。与同步fifo不同的是,异步fifo的地址需要分开判断,即写时钟域有写地址与同步过来的读地址,读时钟域有读地址与同步过来的写地址。地址同步直接用的两级寄存器打拍采样,读写时钟谁快谁慢不用在意。

为什么能直接两级寄存器打拍采?我们来分析一下:

写时钟域:

1.在写时钟域内,如果此时fifo为空,读时钟域同步过来的地址与写时钟域相同,这时候fifo空信号拉高,fifo能写入数据。一般来说,同步数据出错是发生寄存器数据出现翻转的时候,因为空的时候没法读数据,同步不会出错。如果运气是真的烂,或者芯片有问题,同步过来的地址出错,fifo可能非满可能满,但这并不影响我们写入数据。如果为非满,不影响往正确的写地址写入数据,只要后续读地址同步正确就行了;如果为满,此时不能往fifo写数据,那么就不可能写错。因为写时钟域只控制写,所以在这种情况下你读指针同步出错了对写时钟域几乎没影响。

2.如果fifo此时为非空,读地址同步正常的话fifo正常,该干嘛干嘛。如果同步出错,因为使用的地址是转换成格雷码后的地址,地址大概率只会发生一位的错误变化,及错误后的地址在实际地址的前一位或者后一位,也只会出现非满和满的情况,不会影响写入。

3.如果fifo此时为满,读地址同步发生错误,一般也是在fifo开始读数据时发生的错误。既然已经读走了数据,那么我们写入数据也没问题了,在下一次读地址同步正确就行。因为地址的格雷码是wire类型的,所以地址变化后能被同步的地址一定是当时的地址。

读时钟域:

1.在读时钟域内,如果此时fifo为空,写时钟域同步过来的地址与读时钟域地址相同,这时候fifo空信号拉高,fifo不会读。如果真出错了,那就错了吧= =这真没办法了。

2.如果为非空,同步出错,写指针往相邻的地址跳变,结果可能是空、满、非空,也不影响读。

3.如果为满,写地址不会变,不影响读;如果同步出错了原有的数据不会被覆盖(因为在写时钟域内写指针是正确的,不会写入新数据),不影响读的结果。

如果有不懂或者我说错的地方欢迎指正与讨论(๑•̀ㅂ•́)و✧

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值