Cummings异步FIFO——第一篇

0. 参考

Simulation and Synthesis Techniques for Asynchronous FIFO Design --- Clifford E. Cummings, Sunburst Design

1. 异步FIFO

在跨时钟域传输的时候容易发生亚稳态。当在不同时钟域之间传递的多个信号时,需要用到异步FIFO。

异步FIFO的难点在于生成读写地址和空满指示位。

 

2. FIFO指针

2.1 同步fifo指针

对于同步FIFO而言,读写时钟相同,可以用一个计数器来表示FIFO的状态,如果只写数据,则计数增加;如果只读,则计数减少;级读又写,则计数不变。当计数到某个值,表示FIFO为满;计数为零,表示FIFO空。此时的读地址在每次读有效的时候增加就可以了,当读到最高为会回到零;写地址一样。

2.2 异步FIFO指针

写指针:写指针指向下一个要写的地址。当FIFO reset之后,写指针为0。当写操作有效的时候,在下一个时钟沿出向写地址指向的位置写数据,然后写指针累加,指向下一个写地址。

读地址:读指针也要指向下一个要读的地址,FIFO reset之后,读指针也为0,此时空标志位有效,当写了一个数据后,空表示为清零。读操作有效的时候,在下一个时钟沿,向读地址指示的位置读数据,并使读地址累加。

满状态:如果写地址赶上读地址,此时读写地址相同,写地址将读地址套圈了,那么FIFO满了。

空状态:reset之后是空;当读地址赶上写地址,FIFO空。

地址上增加一位空满标志位:n位地址,最高位为标志位,低n-1位位真正的FIFO地址。除了最高位,读写地址相同,则为满;如果读写地址相同——最高位也相同,那么空。

2.3 二进制的地址问题

二进制地址累加时,相邻地址经常存在多位同时跳变比如01到10,有两位同时跳变。在异步采样时可能没有采样到同时跳变后的值,可能采样到00,11的情况。

解决:用格雷码表示地址

3.格雷码计数器

特点: 1.相邻码只改变1bit 2.只能用来记2的指数倍数,不能记奇数个数

如下图:

Cummings异步FIFO——第一篇_第1张图片

格雷码与二进制的转换:

Cummings异步FIFO——第一篇_第2张图片

Cummings异步FIFO——第一篇_第3张图片

格雷码计数器的功能:在写时钟域内,二进制地址累加,然后将二进制的写地址转换成格雷码地址,格雷码地址要传给读时钟域;在写时钟域中格雷码转换器将二进制的读地址转换成格雷码地址,格雷码地址要传给写时钟域。

3.1 第一种格雷码计数器

一种格雷码编码风格如下,只需要一组地址寄存器来保存地址:

Cummings异步FIFO——第一篇_第4张图片

上图中上半部分表示生成n位的格雷码;下半部分红框部分将格雷码最高位与次高位异或,然后将生成的addrmsb与格雷码的低n-2位合并成一个N-1位的格雷码地址,不知有什么用??

// 上半图的n位格雷码计数器
wire clk,rst_n;
wire [n-1:0] bin,bnext,gnext;
wire inc,full; // 写时钟域的格雷码转换器
reg [n-1:0] ptr; //输出的n位格雷码
integer i;
always@(ptr) begin  //格雷码转二进制
    bin[n-1] = ptr[n-1];
    for(i=n-2;i>=0;i=i+1)
        bin[i] = bin[i+1] ^ bin[i];
end
assign bnext = bnext+(inc && !full)?1:0;
assign gnext = (bnext>>1) ^ bnext; //二进制转格雷码
always @(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0) ptr<=0;
    else begin
        ptr <= bnext;
    end
end

 

3.2 第二种格雷码计数器

用寄存器保存二进制码,去除格雷码到二进制的转换逻辑。

减小寄存器之间的组合逻辑长度,可以增加频率,特别是在FPGA中。

如下图:

 

Cummings异步FIFO——第一篇_第5张图片

 

 

wire clk,rst_n;
wire [n-1:0] bnext,gnext;
wire inc,full; // 写时钟域的格雷码转换器
reg [n-1:0] ptr; //输出的n位格雷码
reg [n-1:0] bin;
integer i;
​
assign bnext = bnext+(inc && !full)?1:0;
assign gnext = (bnext>>1) ^ bnext; //二进制转格雷码
always @(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0) {ptr,bin}<=0;
    else {ptr,bin} <={gnext,bnext};
end

4. fifo结构图

Cummings异步FIFO——第一篇_第6张图片

5. 空满标志位

空标志位在读时钟域产生;满标志位在写时钟域产生。

5.1 空标志位

地址比实际地址增加一位。

读时钟域的格雷码读地址 与 同步过来的格雷码写地址相同,说明满

assign rempty_val = (rgraynext == rq2_wptr); // 读时钟域的格雷码读地址 与 同步过来的格雷码写地址相同,说明满
always @(posedge rclk or negedge rrst_n) // 读时钟
    if (!rrst_n) rempty <= 1'b1;
    else rempty <= rempty_val;

5.2 满标志位

地址比实际地址增加一位。

根据格雷码的特性,写时钟域与的格雷码写地址与同步过来的格雷码读地址相比,最高两位都不同,其他位相同,说明满了。

assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
always @(posedge wclk or negedge wrst_n)
    if (!wrst_n) wfull <= 1'b0;
    else wfull <= wfull_val;

5.3 不同时钟频率的考虑

问题一:快时钟是慢时钟的两倍,那么快域地址变化两次,慢时钟域采样一次,前后采样值变化了两次,会产生多位同步的问题吗?

不会。快时钟域第一次改变一位,比如从A到B,慢时钟域没有采样,当快时钟域改变第二次B到C之后,慢时钟域才采样,虽然这期间快时钟域的地址从A到C变了两次,但是慢时钟域只看到第二次B到C,只跳变了一位,因此不会产生多位同步问题。

问题二:快时钟域是否会引起full+1的情况——写溢出,或者empty+1——读溢出?

不会。对于满是在写时钟域产生,如果写时钟比读时钟快,如果waddr追上raddr,那么full有效,此时不能再写了,也就不会产生full+1情况。对于空是在写时钟域产生,如果读时钟比写时钟快,如果raddr赶上waddr,那么就不能再读,也就不会产生empty+1.

5.4 空满标志位取消

空满的set是立即生效的。比如读时钟域中,将采样到的写时钟域的格雷码写地址 与当前的读地址比较,如果相等,则立马使empty有效;full类似。但是读写标志位clear是有延迟的。

当empty有效的时候,读时钟域采样的写地址与读地址相同,如果此时写入数据,那么在写时钟域里写地址是增加的,但是这个增加了的写地址需要两个读时钟的同步才能让读时钟域里的地址比较器看到,所以empty的clear有两个读时钟周期的延迟;同样full的clear有两个写时钟周期的延迟。

但这不会使FIFO发生功能错误,可忽略。

5.5 reset时候地址多位跳变

没影响,因为reset就表明FIFO里的数据是无效的,此时不对FIFO进行读写。

6. 代码

这些代码是Cummings论文中给的。

6.1 顶层

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

6.2 内存读写模块

读数据是直接从mem中读,不需要时钟。读地址A指示的是下一次要读的地址——此时FIFO不为空,读A地址是有效的。当前rdata信号上保存的是下一次要读的数据。如果外部读时钟域打算读数据,那么给一个读有效rinc,在rclk上升沿就可以直接把rdata取走,在rinc上升沿FIFO内部会根据A+1地址判断是否empty。(可以参考3.2节)。

写数据winc有效时,下一个写时钟沿wclk要写入数据。满标志表示如果在下一个时钟沿写数据,就写到读地址处(读写地址相同)。如果满,则下一个时钟沿不能写。所以在下一个写时钟沿到来时要判断满标志,如果满了,则不能写。

同样,读地址表示下一个读时钟沿要读的数据,空表示下一个时钟沿是否可以读。对于读数据的设备,它需要在读的时候判断是否空了,至于FIFO的rdata输出端则不需要进行empty判断,FIFO将下一次要读的数据放在rdata处,如果读数据的设备要读,就在rclk时钟沿读就行了。

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<

6.3 读地址到写时钟域同步

写时钟控制,打两拍

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 or negedge wrst_n)
    if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
    else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
endmodule

6.4 写地址到读时钟域同步

读时钟控制,打两拍

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 or negedge rrst_n)
    if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
    else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
endmodule

6.5 空标志位产生

产生空标志位、n-1位当前的二进制读地址、n位格雷码读地址

产生空标志位的结构框图如下:

Cummings异步FIFO——第一篇_第7张图片

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] rgraynext, rbinnext;
//-------------------
// GRAYSTYLE2 pointer
//-------------------
always @(posedge rclk or negedge rrst_n)  //格雷码计数器的第二种写法
if (!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0];//给到memory模块
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext;
//---------------------------------------------------------------
// FIFO empty when the next rptr == synchronized wptr or on reset
//---------------------------------------------------------------
    assign rempty_val = (rgraynext == rq2_wptr);//空判断是根据下一次要读的地址来判断的
always @(posedge rclk or negedge rrst_n)
    if (!rrst_n) rempty <= 1'b1;
    else rempty <= rempty_val;
endmodule

6.6 满标志产生

产生满标志、n-1位当前写地址、n位格雷码写地址

产生满标志位的结构框图如下:

Cummings异步FIFO——第一篇_第8张图片

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] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
//------------------------------------------------------------------
// Simplified version of the three necessary full-tests:
// assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
// (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
// (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
//------------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
    // 空判断是根据下一次要写的地址判断的。
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule

 

 

 

 

你可能感兴趣的:(数字IC,fifo)