硬件架构的艺术:异步FIFO设计

1. 应用场景

  异步FIFO用来在两个异步时钟域间传输数据。如图所示是两个系统,分别为“system X”和“system Y”,从“system X”向“system Y”传输数据,两个系统工作在不同时钟域。
在这里插入图片描述

  “system X”使用“xclk”将数据写入FIFO,“system Y”使用“yclk”将数据读出,“fifo_full”和“fifo_empty”分别负责监控上溢(overflow)和下溢(underflow)情况。

  • “fifo_full”指示上溢情况,拉高时数据不应再写入FIFO,否则会将FIFO内的数据覆盖掉。
  • “fifo_empty”指示上溢情况,拉高时不应读取FIFO,否则会读出垃圾数据。

与握手信号不用,异步FIFO用于对性能要求较高的设计中,尤其是时钟延迟比系统资源更为重要的环境中。

2. 异步FIFO结构

  与同步FIFO设计原理相似,异步FIFO的结构也可以分为三个部分,如图所示,fifo write control、fifo read control和fifo memory,其中fifo write control和fifo read control分别工作在wr_clk和rd_clk时钟域,fifo memory工作在wr_clk和rd_clk时钟域。

  • fifo write control:写指针产生、满信号产生和读指针同步器;
  • fifo read control:读指针产生、空信号产生和写指针同步器;
  • fifo memory:缓存数据。
    在这里插入图片描述

3. 关键设计

3.1 异步FIFO与同步FIFO差异

  异步FIFO相较于同步FIFO,主要有两点不同:

  • Fifo memory工作在两个时钟域;
  • Wr_ctrl和rd_ctrl工作在不同时钟域;
  • 读、写指针经过两级同步器会存在延迟。

  异步FIFO不能再使用“计数器”产生fifo空、满信号,原因在于计数器不能被两个时钟同时驱动,此时只能通过比较读、写指针来产生空、满信号,将读指针同步到写时钟域,与写指针比较产生满信号;将写指针同步到读时钟域,与读指针比较产生空信号。

  读写指针的同步都需要跨时钟域传输,若使用握手信号的方式同步指针,效率很低;假如指针仍使用“二进制编码”,使用两级同步器同步指针,可能会出现亚稳态导致用于比较的指针出错。

  如指针从“111”→“000”,经过两级同步器,可能的结果有8种,每一个比特都可能存在采样错误,错误的指针会导致fifo空、满信号异常。

  • 读指针同步错误,FIFO满信号未正常拉高,继续写FIFO会覆盖原数据,导致数据传输错误;
  • 写指针同步错误,FIFO空信号为正常拉高,继续读FIFO会读出垃圾数据,导致数据传输错误。

  异步FIFO的指针同步应该避免使用二进制编码。

3.2 格雷码

  实现异步FIFO指针同步的一种方式是使用格雷码,格雷码是“单位间距码”,即相邻值之间只有1bit不同。格雷码与二进制码的对应关系如图所示。格雷码有以下几个特点:

  • 单位间距码:相邻值之间只有1bit不同
  • 中心对称:除MSB外,其余bit中心对称

在这里插入图片描述
  指针采用格雷码计数,使用两级同步器同步指针很少会出现亚稳态,此外取样后的值最多只有1bit出现错误,但该错误不会影响空、满信号的正确产生。考虑格雷码读、写指针采样错误的情况:

  • 读指针采样错误:相邻格雷码只有1bit不同,若采样出现亚稳态会出现两种情况,一是采样值保持不变,同步的读指针小于当前的真实值,fifo_full会提前拉高,此时不会出现overflow;二是采样值采样成功,同步的读指针等于当前的真实值。两种结果都不会影响“fifo_full”的正确产生;
  • 写指针采样错误:写指针同理,一是同步的写指针小于当前的真实值, fifo_empty会提前拉高,虽然还有数据未读出,但这种情况也不会造成underflow;二是同步的写指针等于当前的真实值。两种结果都不会影响“fifo_empty”的正确产生。

3.3 同步指针的影响

  比较读、写指针的目的是产生FIFO的空满信号,相较于同步FIFO,异步FIFO在两个时钟域分别比较读、写指针,读、写指针都需要经过两级同步器。

3.3.1 FIFO的“假满”

  由于读指针同步到写时钟域存在2*T_wclk的延迟,此时写时钟域采样到的rd_ptr_sync小于等于读时钟域的rd_ptr。rd_ptr_sync小于rd_ptr的情况如图所示,wr_ptr追赶上rd_ptr_sync(wr_ptr和rd_ptr_sync都转换为二进制码),最高位不同其余位相同,此时会拉高fifo_full信号,但实际上FIFO内还有两个地址空间可以写入数据,这种情况就是FIFO的“假满”。FIFO的“假满”虽然阻止了数据的写入,但是对数据的准确性是没有影响的。只有在FIFO实际已经满了但没阻止数据写入才会出现overflow(原数据未读出就被覆盖)。
在这里插入图片描述

3.3.2 FIFO的“假空”

  由于写指针同步到读时钟域存在2*T_rclk的延迟,此时读时钟域采样到的wr_ptr_sync小于等于读时钟域的wr_ptr。wr_ptr_sync小于等于wr_ptr的情况如图所示,wr_ptr_sync等于rd_ptr(指针均以转换为二进制码),所有比特位均相等,此时会拉高fifo_empty信号。FIFO的“假空”虽然在实际有数据的情况下拉高了FIFO的空信号,阻止了数据的读出,但是对数据的准确性是没有影响的,直到读时钟域看到的写指针变化才会拉低FIFO的空信号。只有在FIFO为空的时候没有阻止读操作才会产生问题,即underflow问题。
在这里插入图片描述

3.4 格雷码与二进制码转换

  读、写指针有二进制码转为格雷码后经两级同步器同步到写、读时钟域,在读、写时钟域有时还需要将格雷码转为二进制码进行指针的比较以产生FIFO的空、满信号,下面将给出格雷码与二进制码相互转换的方法。

3.4.1 二进制码转格雷码

  二进制码转格雷码的公式如下所示,其中n表示位宽。在这里插入图片描述

3.4.2 格雷码转二进制码

  格雷码转二进制码的公式如下所示,其中n表示位宽。
在这里插入图片描述

3.5 读、写指针产生

  产生读、写指针有两种方法,一种是直接使用格雷码计数器产生读写指针,一种是使用二进制码计数器产生读写指针,再将读写指针转换为格雷码用于跨时钟域的同步,下面将分析这两种设计方法。

3.5.1 格雷码计数器

  格雷码计数器的结构如图所示,由格雷码转二进制码、二进制加法器和二进制码转格雷码组成。
在这里插入图片描述
  从面积和工作频率两个方面分析该设计。

  • 面积:格雷码转二进制、二进制转格雷码的组合逻辑,格雷码指针寄存器;
  • 工作频率:寄存器输入信号的组合逻辑较为复杂,该电路工作在较高频率可能存在时序违例的情况。

3.5.2 二进制码计数器

  二进制码计数器的结构如图所示,由二进制转格雷码、二进制码寄存器和格雷码寄存器组成。
在这里插入图片描述
  从面积和工作频率两个方面分析该设计。

  • 面积:二进制转格雷码、两个寄存器;
  • 工作频率:寄存器输入只有加法器或二进制转格雷码,组合逻辑延迟相对较小,该电路可以工作在较高频率。

3.6 空、满信号产生

  FIFO的空、满信号通过比较读、写指针可以得到,下文将讨论两种计数器设计下FIFO的空、满信号产生。

3.6.1 格雷码计数器

3.6.1.1 二进制码产生空、满信号

  N位指针可以覆盖FIFO中的2^N个地址。在两个指针相等时,因为FIFO可能处于空状态也可能处于满状态,所以需要使用额外的位对这两种情况进行区分。

  • FIFO满:二进制码指针的最高有效位不同,其余比特位相同;
  • FIFO空:二进制码指针所有比特位相等。

  格雷码计数器设计下,使用二进制码产生空、满信号的结构如图所示。由于读写指针的值都是以格雷码保存,而所有比较和递增是以二进制码形式进行,实现和纠错比较简单。

  读指针经过两级同步后得到rd_ptr_sync,将格雷码转为二进制码rd_ptr_sync_b,在写时钟域还需要将格雷码的wr_ptr转换为二进制码的wr_ptr_b,将rd_ptr_sync_b和wr_ptr_b进行比较,得到FIFO的满信号。

  写指针经过两级同步后得到wr_ptr_sync,将格雷码的wr_ptr_sync转换为二进制码的wr_ptr_sync_b,在读时钟域还需要将格雷码的rd_ptr转为rd_ptr_b,最后将wr_ptr_sync_b和rd_ptr_b进行比较,得到FIFO的空信号。
在这里插入图片描述
  该设计共使用了4个“格雷码转二进制码”,组合逻辑路径较长,较高频率下工作可能会存在时序违例的情况。如果直接使用格雷码指针产生FIFO的空、满信号,就不需要这些转换器,下文将分析使用格雷码指针产生FIFO的空、满信号。

3.6.1.2 格雷码产生空、满信号

  使用格雷码产生FIFO的空满信号要求使用两个格雷码计数器,分别是N位和N-1位。由于格雷码除最高位镜像对称的特性,可以使用N位格雷码计数器产生N-1位格雷码计数器。N-1位格雷码计数器的产生如图所示,将N位格雷码计数器的2个MSB异或得到的值作为N-1位格雷码计数器的最高位,其余N-2位与N位格雷码的N-2位完全一致。
在这里插入图片描述

  格雷码计数器产生空信号使用N位计数器与二进制码相同,当读写指针完全相同时FIFO为空。

  格雷码计数器产生满信号与二进制码不同。如图所示,当rd_ptr和wr_ptr相等时FIFO为空。wr_ptr加一后,wr_ptr和rd_ptr的最高为不同、其余位相同,按照二进制码的判断标准此时FIFO为满,但此时FIFO并没有满,所以二进制码的FIFO满判断方法不再适用于格雷码。
  双格雷码计数器可以很好地解决该问题。N位计数器用于区分写指针比读指针多回绕一次,N-1位计数器用于确定写指针的真实位置。FIFO的满需要满足三个条件:

  • N位格雷码指针最高有效位不同;
  • N-1位格雷码指针最高有效位相同;(该条件等效于N位格雷码指针的次高位不同)
  • N位格雷码指针剩余N-2位全部相等。

3.6.2 二进制码计数器

  格雷码计数器的两种实现方式都略显复杂,二进制码计数器提供了一种简单的实现方法。如图所示,二进制码计数器使用两个寄存器分别缓存二进制指针和格雷码指针。二进制码计数器产生空、满信号如图所示。
在这里插入图片描述
  仅需将同步后的指针转为二进制码,然后与二进制码指针比较,按照二进制码FIFO空、满方法产生FIFO空、满信号。

4. 大容量异步FIFO设计

  大容量同步FIFO是使用SRAM作为FIFO memory实现的,大容量异步FIFO仍可以使用该方法,不过更为常用的方法是使用“大容量同步FIFO和小容量异步FIFO”级联来实现的。原因在于异步FIFO的功能只是跨时钟域传输数据,同步FIFO更适合缓存数据,结合这两种FIFO的特点将其级联,得到大容量异步FIFO。

5. 代码实现

5.1 async_fifo_top

//=================================================================================
// module      : async_fifo_gray_cnt.v
// description : asynchronous fifo , pointer generate by gray counter
// data        : 2022/2/27
// author      : souther meditating
//=================================================================================

module async_fifo(
    wclk,
    wrst_n,
    wr_en,
    wr_data,
    rclk,
    rrst_n,
    rd_en,
    rd_data,
    fifo_full,
    fifo_empty    
);

//=================================================================================
// parameter & localparam
//=================================================================================

//=================================================================================
// parameter
parameter FIFO_DATA_WIDTH       = 16;
parameter FIFO_DEPTH            = 16;

//=================================================================================
// localparam
localparam FIFO_ADDR_WIDTH      = clog2(FIFO_DEPTH);
localparam FIFO_PTR_WIDTH       = FIFO_ADDR_WIDTH + 1;

//=================================================================================
// I/O
//=================================================================================
input                               wclk;
input                               wrst_n;
input                               wr_en;
input   [FIFO_DATA_WIDTH-1:0]       wdata;

input                               rclk;
input                               rrst_n;
input                               rd_en;
output  [FIFO_DATA_WIDTH-1:0]       rdata;

output                              fifo_full;
output                              fifo_empty;

//=================================================================================
// signal
//=================================================================================

// ---- fifo mem ----
wire    [FIFO_ADDR_WIDTH-1:0]       waddr;
wire    [FIFO_ADDR_WIDTH-1:0]       raddr;

// ---- wr_sync_cell ----
wire    [FIFO_PTR_WIDTH-1:0]        rptr_g;
wire    [FIFO_PTR_WIDTH-1:0]        rptr_g_sync;

// ---- rd_sync_cell ----
wire    [FIFO_PTR_WIDTH-1:0]        wptr_g;
wire    [FIFO_PTR_WIDTH-1:0]        wptr_g_sync;

// ---- wr_ptr_full ----



//=================================================================================
// main body
//=================================================================================

//=================================================================================
// 1. fifo_mem
// 2. wr_sync_cell
// 3. rd_sync_cell
// 4. wptr_full
// 5. rptr_empty

//=================================================================================
// fifo_mem

fifo_mem #(
    .DSIZE          (FIFO_DATA_WIDTH        ),
    .FIFO_DEPTH     (FIFO_DEPTH             )
)
u_fifo_mem(
    .raddr          (raddr                  ),
    .rdata          (rdata                  ),
    .wclk           (wclk                   ),
    .wr_en          (wr_en                  ),
    .waddr          (waddr                  ),
    .wdata          (wdata                  ),
    .fifo_full      (fifo_full              )
);

//=================================================================================
// wr_sync_cell

sync_cell #(
    .DSIZE          (FIFO_DATA_WIDTH)
)
u_rd_ptr_sync(
    .dat_i          (rptr_g         ),
    .clk_o          (wclk           ),
    .rst_n_o        (wrst_n         ),
    .dat_o          (rptr_g_sync    )
);

//=================================================================================
// rd_sync_cell

sync_cell #(
    .DSIZE          (FIFO_DATA_WIDTH)
)
u_wr_ptr_sync(
    .dat_i          (wptr_g         ),
    .clk_o          (rclk           ),
    .rst_n_o        (rrst_n         ),
    .dat_o          (wptr_g_sync    )
);

//=================================================================================
// wptr_full

wptr_full #(
    .FIFO_DEPTH     (FIFO_DEPTH     )
)
u_wptr_full (
    .wclk           (wclk           ),
    .wrst_n         (wrst_n         ),
    .wr_en          (wr_en          ),
    .waddr          (waddr          ),
    .wptr_g         (wptr_g         ),
    .rptr_g_sync    (rptr_g_sync    ),
    .fifo_full      (fifo_full      )
);

//=================================================================================
// rptr_empty

rptr_empty #(
    .FIFO_DEPTH     (FIFO_DEPTH     )
)
u_rptr_empty(
    .rclk           (rclk           ),
    .rrst_n         (rrst_n         ),
    .rd_en          (rd_en          ),
    .raddr          (raddr          ),
    .wptr_g_sync    (wptr_g_sync    ),
    .rptr_g         (rptr_g         ),
    .fifo_empty     (fifo_empty     )
);

endmodule;

5.2 async_fifo_mem

//=================================================================================
// module      : fifo_mem.v
// description : register dpram
// data        : 2022/2/27
// author      : souther meditating
//=================================================================================

module fifo_mem (
    raddr,
    rdata,
    wclk,
    wr_en,
    fifo_full,
    waddr,
    wdata
);

// ==========================================================================
// parameter
// ==========================================================================
parameter DSIZE             = 8;
parameter FIFO_DEPTH        = 16;

// ==========================================================================
// localpara
// ==========================================================================
localparam FIFO_ADDR_WIDTH  = $clog2(FIFO_DEPTH);

// ==========================================================================
// I/O
// ==========================================================================
input   [FIFO_ADDR_WIDTH-1:0]       raddr;
output  [DSIZE-1:0]                 rdata;

input                               wclk;
input                               wr_en;
input   [FIFO_ADDR_WIDTH-1:0]       waddr;
input   [DSIZE-1:0]                 wdata;

// ==========================================================================
// signal define
// ==========================================================================
reg     [DSIZE-1:0]                 fifo_mem[FIFO_DEPTH-1:0];

// ==========================================================================
// main body
// ==========================================================================

// ==========================================================================
// write fifo_mem
always@(posedge wclk) begin
    if((wr_en == 1'b1) && (fifo_full != 1'b1)) begin
        fifo_mem[waddr] <= wdata;
    end
end

// ==========================================================================
// read fifo_mem
assign rdata = fifo_mem[raddr];


endmodule

5.3 sync_cell

//=================================================================================
// module      : sync_cell.v
// description : 2-stage synchronizer
// data        : 2022/2/27
// author      : souther meditating
//=================================================================================

module sync_cell(
    dat_i,
    clk_o,
    rst_n_o,
    dat_o
);

//=================================================================================
// parameter & localparam
//=================================================================================
parameter DSIZE                 = 16;

//=================================================================================
// I/O
//=================================================================================
input   [DSIZE-1:0]                 dat_i;

input                               clk_o;
input                               rst_n_o;
output  [DSIZE-1:0]                 dat_o;

//=================================================================================
// signal
//=================================================================================

// ---- temp flip-flop ----
reg     [DSIZE-1:0]                 dat_i_ff1;
reg     [DSIZE-1:0]                 dat_i_ff2;

//=================================================================================
// main body
//=================================================================================

always@(posedge clk_o or negedge rst_n_o) begin
    if(rst_n_o == 1'b0) begin
        dat_i_ff1 <= {DSIZE{1'b0}};
        dat_i_ff2 <= {DSIZE{1'b0}};
    end
    else if() begin
        dat_i_ff1 <= dat_i;
        dat_i_ff2 <= dat_i_ff1;
    end
end

assign dat_o = dat_i_ff2;

endmodule

5.4 wptr_full

//=================================================================================
// module      : wptr_full.v
// description : async_fifo write ctrl
// data        : 2022/2/27
// author      : souther meditating
//=================================================================================

module wptr_full(
    wclk,
    wrst_n,
    wr_en,
    waddr,
    wptr_g,
    rptr_g_sync,
    fifo_full
);

//=================================================================================
// parameter & localparam
//=================================================================================
parameter FIFO_DEPTH        = 16;

localparam FIFO_ADDR_WIDTH  = clog2(FIFO_DEPTH) ;
localparam FIFO_PTR_WIDTH   = FIFO_ADDR_WIDTH + 1;

//=================================================================================
// I/O
//=================================================================================
input                               wclk;
input                               wrst_n;
input                               wr_en;
output  [FIFO_ADDR_WIDTH-1:0]       waddr;

output  [FIFO_PTR_WIDTH-1:0]        wptr_g;
input   [FIFO_PTR_WIDTH-1:0]        rptr_g_sync;

output                              fifo_full;

// ==========================================================================
// signal define
// ==========================================================================
reg     [FIFO_PTR_WIDTH-1:0]        wptr_b;
reg     [FIFO_PTR_WIDTH-1:0]        wptr_g;
reg                                 fifo_full;

wire    [FIFO_PTR_WIDTH-1:0]        wptr_bnext;
wire    [FIFO_PTR_WIDTH-1:0]        wptr_gnext;

//=================================================================================
// main body
//=================================================================================

// ---- binary count ----

assign wptr_bnext   = wbin + (wr_en & (~fifo_full));
assign waddr        = wptr_b[FIFO_ADDR_WIDTH-1:0];

always @(posedge wclk or negedge wrst_n) begin
    if(wrst_n == 1'b0)begin
        wptr_b <= {FIFO_PTR_WIDTH{1'b0}};
    end
    else begin
        wptr_b <= wptr_bnext;
    end
end

// ---- binary to gray ----
assign wptr_gnext = (wptr_bnext >> 1) ^ (wptr_bnext);

always @(posedge wclk or negedge wrst_n) begin
    if(wrst_n == 1'b0) begin
        wptr_g <= {FIFO_PTR_WIDTH{1'b0}};
    end
    else begin
        wptr_g <= wptr_gnext;
    end

end

// ---- fifo full -----
// three necessary condition
assign fifo_full_val = (wptr_gnext == {~rptr_g_sync[FIFO_PTR_WIDTH-1:FIFO_PTR_WIDTH-2],
                                       rptr_g_sync[FIFO_PTR_WIDTH-3:0]});

always@(posedge wclk or negedge wrst_n) begin
    if(wrst_n == 1'b0) begin
        fifo_full <= 1'b0;
    end
    else begin
        fifo_full <= fifo_full_val;
    end
end



endmodule

5.5 rptr_empty

//=================================================================================
// module      : rptr_empty.v
// description : async_fifo read ctrl
// data        : 2022/3/2
// author      : souther meditating
//=================================================================================

module rptr_empty(
    rclk,
    rrst_n,
    rd_en,
    raddr,
    wptr_g_sync,
    rptr_g,
    fifo_empty
);

//=================================================================================
// parameter & localparam
//=================================================================================
parameter FIFO_DEPTH        = 16;

localparam FIFO_ADDR_WIDTH  = clog2(FIFO_DEPTH) ;
localparam FIFO_PTR_WIDTH   = FIFO_ADDR_WIDTH + 1;

//=================================================================================
// I/O
//=================================================================================
input                               rclk;
input                               rrst_n;

input                               rd_en;
output  [FIFO_ADDR_WIDTH-1:0]       raddr;

input   [FIFO_PTR_WIDTH-1:0]        wptr_g_sync;
output  [FIFO_PTR_WIDTH-1:0]        rptr_g;

output                              fifo_empty;

// ==========================================================================
// signal define
// ==========================================================================
reg     [FIFO_PTR_WIDTH-1:0]        rptr_b;
reg     [FIFO_PTR_WIDTH-1:0]        rptr_g;

wire    [FIFO_PTR_WIDTH-1:0]        rptr_bnext;
wire    [FIFO_PTR_WIDTH-1:0]        rptr_gnext;

wire                                fifo_empty_val;
reg                                 fifo_empty;

//=================================================================================
// main body
//=================================================================================

// ---- binary count ----

assign rptr_bnext       = rptr_b + (rd_en & (~fifo_empty));
assign raddr            = rptr_b[FIFO_ADDR_WIDTH-1:0];

always @(posedge rclk or negedge rrst_n) begin
    if(rrst_n == 1'b0) begin
        rptr_b <= {FIFO_PTR_WIDTH{1'b0}};
    end
    else begin
        rptr_b <= rptr_bnext; 
    end
end

// ---- binary to gray ----

assign rptr_gnext = (rptr_bnext >> 1) ^ rptr_bnext;

always@(posedge rclk or negedge rrst_n) begin
    if(rrst_n == 1'b0) begin
        rptr_g <= {FIFO_PTR_WIDTH{1'b0}};
    end
    else begin
        rptr_g <= rptr_gnext;
    end
end

// ---- fifo empty ----
assign fifo_empty_val = (rptr_gnext == wptr_g_sync);

always @(posedge rclk or posedge rrst_n) begin
    if(rrst_n == 1'b0) begin
        fifo_empty <= 1'b1;
    end
    else begin
        fifo_empty <= fifo_empty_val;
    end
end

endmodule

6. 参考资料

[1] 《硬件架构的艺术》
[2] Simulation and Synthesis Techniques for Asynchronous FIFO Design
[3] 同步FIFO设计

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值