异步FIFO设计

Asynchronous FIFO Design

总结来自Clifford E. Cummings论文 《Simulation and Synthesis Techniques for Asynchronous FIFO Design》

一、设计难点#

  • 使用格雷码计数时空和满的判断。
  • 同步FIFO读写时钟相同,而异步FIFO读写来自不同两个读写时钟,需要考虑跨时钟域设计。

二、设计思路#

把多bit信号跨时钟域是有问题的。设计一般通过FIFO对多bit信号进行安全的跨时钟域传递。

2.1 空满判断#

1、二进制空满判断#

如图1,在二进制计数nbit地址时,判断full和empty的条件都是waddr==raddr,为了区分,增加1bit,这样地址位宽范围变为[n:0]。此时,当 waddr[n:0]==raddr[n:0] 时,为empty。而full时,waddr会绕raddr一圈,因此判定为

{~waddr[n],waddr[n-1:0]}==raddr[n:0]

image-20240802163021289

2、格雷码空满判断#

但采用格雷码计数时,虽然MSB依然是前半部分是0,后半部分是1,但[n-1:0]却不是循环,而是对称,如下图所示。

image-20240805093747915 image-20240805093559025

但是也能看出来,当最高位变成1后,除了最高的前两位,其余位是符合最高位是0时候的顺序规律。因此可通过

{~waddr[n],~waddr[n-1:0],waddr[n-2:0]}==raddr[n:0]判断full。当 waddr[n:0]==raddr[n:0] 时,为empty。

3、保守空满#

思考一下,当判断空或满的时候,总有一个指针是通过两级触发器同步过来的,也就意味着有延迟,那这样的判断真的准确吗?下面分别从空和满的角度去分析:

  1. 假空

判断空的时候,需要用读指针和经过读时钟同步过来的写指针来比较。如果在同步写指针时,继续写操作,同步过来的仍然是之前落后的写指针,此时即使读指针和经过读时钟同步过来的写指针满足空的条件,FIFO仍有新写入的数据,即假空。

  1. 假满

判断满的时候,需要写指针和经过写时钟同步过来的读指针进行比较。如果在同步读指针时,继续读操作,同步过来的仍然是之前落后的读指针,此时即使写指针和经过写时钟同步过来的读指针满足满的条件,FIFO仍然有数据已经被读出,即假满。

综上所述,假满和假空虽然存在,但最多会降低运行吞吐率,性能降低,功能仍正确,而不会出现上溢出和下溢出。

但是想一想为什么要在写侧判断满,而在读测判断空?在读测判断满行不行?

答案是不可以。如果在读侧判断满,当读指针和读时钟同步过来的写指针满足满的条件式,已经有了延迟,如果写操作仍在进行,就会覆盖数据,造成功能不正确

2.2 Multi-bit 跨时钟域同步#

1、快时钟频率是慢时钟的两倍甚至更快,那么快域地址变化两次,慢时钟域采样一次,前后采样值变化了两次,会产生多位同步的问题吗?#

不会,只有多个位同时变化时才会有问题。因为在慢时钟域下只能看到最近的一次跳变,格雷码相邻跳变只有一位不同。

2、Multi-bit 异步复位#

异步复位通常会导致指针位多bit产生变化,这会有问题吗?

因为复位后fifo里的数据已经无效,因此不会有问题,另外,虽然读写各有单独的复位信号,但单侧复位时必须通知另一侧,否则会出错。

2.3 格雷码计数器设计#

文章给出两种结构,下面依次介绍

1、 第一种#

第一种寄存器存储的均是格雷码。

image-20240805173534195

2、第二种#

第二种计数器存储的是二进制,直接用二进制对存储器寻址。二进制到格雷码转换的逻辑转移到下面的寄存器。即格雷码只用于跨时钟域的同步。这样做减少延迟,提高频率。

image-20240805173649528

三、RTL结构与设计#

1、FIFO结构#

结构如下。

image-20240805180008595

根据不同的功能和时钟域,分为以下六个模块:

名字 功能 时钟域
fifo1.v 顶层 所有
fifomem.v 存储,一般用伪双端口ram 所有
sync_r2w.v 写时钟同步读指针
sync_w2r.v 读时钟同步写指针
rptr_empty.v 空判断
wptr_full.v 满判断

2、Verilog代码#

  1. fifo.v
module fifo1 #( parameter DSIZE = 8,
                parameter ASIZE = 4)(
    output [DSIZE-1:0] rdata,
    output wfull,
    output rempty,
    input [DSIZE-1:0] wdata,
    input winc, wclk, wrst_n,
    input rinc, rclk, rrst_n
);

    wire [ASIZE-1:0] waddr, raddr;
    wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;

    sync_r2w sync_r2w (.wq2_rptr(wq2_rptr), .rptr(rptr),
    .wclk(wclk), .wrst_n(wrst_n));

    sync_w2r sync_w2r (.rq2_wptr(rq2_wptr), .wptr(wptr),
    .rclk(rclk), .rrst_n(rrst_n));

    fifomem #(DSIZE, ASIZE) fifomem
    (.rdata(rdata), .wdata(wdata),
    .waddr(waddr), .raddr(raddr),
    .wclken(winc), .wfull(wfull),
    .wclk(wclk));

    rptr_empty #(ASIZE) rptr_empty
    (.rempty(rempty),
    .raddr(raddr),
    .rptr(rptr), .rq2_wptr(rq2_wptr),
    .rinc(rinc), .rclk(rclk),
    .rrst_n(rrst_n));
    
    wptr_full #(ASIZE) wptr_full
    (.wfull(wfull), .waddr(waddr),
    .wptr(wptr), .wq2_rptr(wq2_rptr),
    .winc(winc), .wclk(wclk),
    .wrst_n(wrst_n));
endmodule
  1. fifomem.v
module fifomem #(   parameter DATASIZE = 8, // Memory data word width
                    parameter ADDRSIZE = 4)( // Number of mem address bits
    output  [DATASIZE-1:0]  rdata,
    input   [DATASIZE-1:0]  wdata,
    input   [ADDRSIZE-1:0]  waddr, raddr,
    input                   wclken, wfull, wclk);
`ifdef VENDORRAM
    // instantiation of a vendor's dual-port RAM
    vendor_ram mem (.dout(rdata), .din(wdata),
    .waddr(waddr), .raddr(raddr),
    .wclken(wclken),
    .wclken_n(wfull), .clk(wclk));
`else
    // RTL Verilog memory model
    localparam DEPTH = 1<<ADDRSIZE;
    reg [DATASIZE-1:0] mem [0:DEPTH-1];
    assign rdata = mem[raddr];
    always @(posedge wclk)
        if (wclken && !wfull) mem[waddr] <= wdata;
`endif
endmodule
  1. sync_r2w.v
module sync_r2w #(parameter ADDRSIZE = 4)(
    output reg  [ADDRSIZE:0] wq2_rptr,
    input       [ADDRSIZE:0] rptr,
    input                    wclk, wrst_n
);
reg  [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk, negedge wrst_n) begin
    if(!wrst_n)
        {wq2_rptr,wq1_rptr} <=  'b0;
    else 
        {wq2_rptr,wq1_rptr} <=  {wq1_rptr,rptr};       
end
endmodule
  1. sync_w2r.v
module sync_w2r #(parameter ADDRSIZE = 4)(
    output reg  [ADDRSIZE:0] rq2_wptr,
    input       [ADDRSIZE:0] wptr,
    input                    rclk, rrst_n
);
reg  [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk, negedge rrst_n) begin
    if(!rrst_n)
        {rq2_wptr,rq1_wptr} <=  'b0;
    else 
        {rq2_wptr,rq1_wptr} <=  {rq1_wptr,wptr};       
end
endmodule
  1. rptr_empty.v
module rptr_empty #(parameter ADDRSIZE = 4) (
    output  reg                 rempty, 
    output      [ADDRSIZE-1:0]  raddr, 
    output  reg [ADDRSIZE :0]   rptr, 
    input       [ADDRSIZE :0]   rq2_wptr, 
    input                       rinc, rclk, rrst_n
);
reg  [ADDRSIZE:0]   rbin;
wire [ADDRSIZE:0] bnext,gnext;
assign raddr = rbin[ADDRSIZE-1:0];
// bin counter
assign bnext = rbin + (rinc & ~rempty);
always @(posedge rclk or negedge rrst_n) begin
    if(!rrst_n)
        rbin    <=  {(ADDRSIZE+1){1'b0}};
    else
        rbin    <=  bnext;
end
// gray counter
assign gnext = (bnext>>1) ^ bnext;
always @(posedge rclk or negedge rrst_n) begin
    if(!rrst_n)
        rptr    <=  {(ADDRSIZE+1){1'b0}};
    else
        rptr    <=  gnext;
end
// empty flag
always @(posedge rclk or negedge rrst_n) begin
    if(!rrst_n)
        rempty    <=  1'b1;
    else
        rempty    <=  rq2_wptr == gnext;
end
endmodule
  1. wptr_full.v
module wptr_full #(parameter ADDRSIZE = 4)(
    output reg                  wfull,
    output      [ADDRSIZE-1:0]  waddr,
    output reg  [ADDRSIZE :0]   wptr,
    input       [ADDRSIZE :0]   wq2_rptr,
    input                       winc, wclk, wrst_n
);
reg  [ADDRSIZE:0]   wbin;
wire [ADDRSIZE:0] bnext,gnext;

assign waddr = wbin[ADDRSIZE-1:0];
// bin counter
assign bnext = wbin + (winc & ~wfull);
always @(posedge wclk or negedge wrst_n) begin
    if(!wrst_n)
        wbin    <=  {(ADDRSIZE+1){1'b0}};
    else
        wbin    <=  bnext;
end
// gray counter
assign gnext = (bnext>>1) ^ bnext;
always @(posedge wclk or negedge wrst_n) begin
    if(!wrst_n)
        wptr    <=  {(ADDRSIZE+1){1'b0}};
    else
        wptr    <=  gnext;
end
// full flag
always @(posedge wclk or negedge wrst_n) begin
    if(!wrst_n)
        wfull   <=  1'b0;
    else
        wfull   <=  gnext == {~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]};
end
endmodule
posted @   HiDark  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
主题色彩
点击右上角即可分享
微信分享提示