异步FIFO的verilog代码实现(包含将满和将空逻辑)
异步FIFO的verilog代码实现(包含将满和将空逻辑)
代码参考来源:
Clifford E. Cummings, "Simulation and Synthesis Techniques for Asynchronous FIFO Design".
异步FIFO简介
使用场景:在有大量的数据需要进行跨时钟域传输, 并且对数据传输速度要求比较高的场合 。
一个异步 FIFO 一般由如下部分组成:
1. Memory, 作为数据的存储器;
2. 写逻辑部分,主要负责产生写信号和地址;
3. 读逻辑部分,主要负责产生读信号和地址;
4. 地址比较部分,主要负责产生 FIFO 空、满的标志。
跟普通的FIFO相比,异步FIFO实际上多了读写地址的跨时钟域同步的逻辑,以及两个时钟域中读写信号的比较逻辑。
异步FIFO关键技术1 -- 读写信号跨时钟域同步
首先,FIFO的关键是需要判断读空和写满,而这两个信号的产生依赖读地址和写地址。在异步FIFO中,读和写是分在两个时钟域中的,在写时钟域,需要得到读地址的信息进而判断是否写满(写指针是否追上读指针),同理,在读时钟域,也需要写地址的信息。我们知道跨时钟域的单比特数据一般可以用双寄存器法进行同步,但读写地址通常都是多比特的信号,此时如何进行同步呢?
当然,多比特的同步肯定可以通过增加握手信号来解决,但实际上对于数值上连续的信号,可以采用格雷码进行多比特到单比特的传输。格雷码再次不做介绍,具体原理可以参考:https://www.cnblogs.com/zhuruibi/p/8988044.html
有了格雷码,就可以将读写地址同步到各自的时钟域了。
异步FIFO关键技术2 -- 读写地址的比较
跟普通fifo一样,异步fifo也是通过比较读写地址是否相同来判断当前fifo是否空满。区别在于,异步FIFO因为使用了格雷码对地址进行编码传输。比如读信号通过格雷码编码后同步到写时钟域,此时需要只需要写信号对应的格雷码和读信号格雷码是否相同(有效位相同)。而在这个比较时会又一些问题。
下面这张图详细解释了问题所在:
通常FIFO为了防止读写溢出,一般都会增加一个额外的MSB,例如上图中,实际上深度为8的fifo只需要3位地址位表示,增加的额外的一个bit是为了区分读写是否经过了一次“回卷”。在二进制中表示的地址中,空满信号实际上是先看MSB是否相同,再看低3比特是否相同。如果都相同,表示读空,即读地址赶上了写地址。如果MSB不同,低3位相同,则表示写满。在二进制空间这个逻辑没问题。
而到了格雷码中,从上图中可以发现,假设读地址为0,写地址为7。当写地址再加1时,写地址溢出,MSB变为1,实际上表示写地址又回到了0的位置,MSB表示多了一圈。但是对应的格雷码确实最高的两位都发生了变化。从上图可以发现,格雷码中写地址发生回卷后,也就是对应二进制的MSB发生变化后,其最高两位恰好是不回卷时的反。因此只要将读地址的格雷码最高两位进行翻转后再跟写地址比较即可得到正确的满信号。
异步FIFO关键技术3 -- 将满和将空的产生
将满和将空信号实际上表示更加保守的满和空信号。基本思路是,设定一个间隔值,当读写地址之间的间隔小于或等于该间隔就产生将空或将满信号。
对于异步FIFO而言,由于同步过来的地址信号都是格雷码表示的,我们不能直接用格雷码去判断上述的这个间隔,所以需要先对接受到的格雷码进行解码变为二进制,再和当前时钟域下的另一个地址进行将满和将空的生成。
对于将空的判断和空一样,只需要检查写地址与读地址的差是否小于等于间隔。而对将满的判断则需要分两种情况,一种是MSB不同,此时表示写地址有一个回卷,直接将读写地址除去符号位的部分做差与间隔比较。而MSB相同时,需要在差值上再加上FIFO深度。
具体看代码比较清晰。
FIFO逻辑图
增加了read和write地址的同步信号,以及二进制转格雷码的逻辑。
示例代码
module dual_clk_fifo
#(parameter DATESIZE = 8,
parameter ADDRSIZE = 4,
parameter ALMOST_GAP = 3
)
(
input [DATESIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n,
output wire [DATESIZE-1:0] rdata,
output reg wfull,
output reg rempty,
output reg almost_full,
output reg almost_empty
);
wire [ADDRSIZE-1:0] waddr, raddr;
reg [ADDRSIZE:0] wptr, rptr;
wire rempty_val,wfull_val;
//--------------------------------
// RTL Verilog memory model
//--------------------------------
localparam DEPTH = 1<<ADDRSIZE;
reg [DATESIZE-1:0] mem [0:DEPTH-1];
assign rdata = mem[raddr];
always @(posedge wclk)
if (winc && !wfull) mem[waddr] <= wdata;
//--------------------------------
// read-domain to write-domain synchronizer
//--------------------------------
reg [ADDRSIZE:0] wq1_rptr,wq2_rptr;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
//--------------------------------
// Write-domain to read-domain synchronizer
//--------------------------------
reg [ADDRSIZE:0] rq1_wptr,rq2_wptr;
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
//--------------------------------
//Read pointer & empty generation logic
//--------------------------------
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];
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 ;
//--------------------------------
// Write pointer & full generation logic
//--------------------------------
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]));
//------------------------------------------------------------------
wire [ADDRSIZE:0] full_flag;
assign full_flag = {~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]};
assign wfull_val = (wgraynext==full_flag);
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
//--------------------------------
// almost full and empty logic
//--------------------------------
//Gray encoded read and write address decode to bin.
wire [ADDRSIZE:0]rq2_wptr_bin,wq2_rptr_bin;
wire almost_empty_val,almost_full_val;
assign rq2_wptr_bin[ADDRSIZE] = rq2_wptr[ADDRSIZE];
assign wq2_rptr_bin[ADDRSIZE] = wq2_rptr[ADDRSIZE];
genvar i;
generate
for(i=ADDRSIZE-1;i>=0;i=i-1) begin:wpgray2bin
assign rq2_wptr_bin[i] = rq2_wptr_bin[i+1]^rq2_wptr[i];
assign wq2_rptr_bin[i] = wq2_rptr_bin[i+1]^wq2_rptr[i];
end
endgenerate
//--------------------------------
// read almost empty
//--------------------------------
wire [ADDRSIZE:0] rgap_reg;
assign rgap_reg = rq2_wptr_bin - rbin;
assign almost_empty_val = (rgap_reg <= ALMOST_GAP);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) almost_empty <= 1'b1;
else almost_empty <= almost_empty_val;
//--------------------------------
//write almost full
//--------------------------------
wire [ADDRSIZE:0] wgap_reg;
assign wgap_reg = (wbin[ADDRSIZE] ^ wq2_rptr_bin[ADDRSIZE])? wq2_rptr_bin[ADDRSIZE-1:0] - wbin[ADDRSIZE-1:0]:DEPTH + wq2_rptr_bin - wbin;
assign almost_full_val = (wgap_reg <= ALMOST_GAP);
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) almost_full <= 1'b0;
else almost_full <= almost_full_val;
endmodule
简单的testbench:
`timescale 1 ns / 1 ps
module dual_clk_fifo_tb;
parameter DATESIZE = 8;
parameter ADDRSIZE = 3;
parameter ALMOST_GAP = 1;
reg [DATESIZE-1:0]wdata;
reg wrst_n;
reg winc;
reg rinc;
reg wclk;
reg rclk;
reg rrst_n;
wire [DATESIZE-1:0]rdata;
wire wfull;
wire rempty;
wire almost_empty;
wire almost_full;
reg [3:0]a;
reg [3:0]b;
reg [4:0]c;
reg x;
initial begin
$dumpfile("dual_clk_fifo_tb.vcd");
$dumpvars;
wdata = 0;
wrst_n = 0;
rinc = 0;
rclk = 0;
rrst_n = 0;
wclk = 0;
winc = 0;
#2;wrst_n = 0; rrst_n = 0;
#4;wrst_n = 1; rrst_n = 1;
#100;
$finish();
end
always @(posedge wclk or wrst_n)begin
if( wrst_n == 1'b0 )begin
winc = 1'b0;
end
else if( wfull )
winc = 1'b0;
else
winc = 1'b1 ;
end
// rinc generate
always @(posedge rclk or rrst_n)begin
if( rrst_n == 1'b0 )begin
rinc = 1'b0 ;
end
else if( rempty )
rinc = 1'b0;
else
rinc = 1'b1 ;
end
// wdata
always @(posedge wclk or negedge wrst_n)begin
if( wrst_n == 1'b0 )begin
wdata = 0 ;
end
else if( winc )begin
wdata = wdata + 1'b1;
end
end
always #0.5 wclk = ~wclk;
always #2 rclk = ~rclk;
dual_clk_fifo #(
.DATESIZE ( DATESIZE ),
.ADDRSIZE ( ADDRSIZE ),
.ALMOST_GAP ( ALMOST_GAP )
U_DUAL_CLK_FIFO_0(
.wdata ( wdata ),
.winc ( winc ),
.wclk ( wclk ),
.wrst_n ( wrst_n),
.rinc ( rinc ),
.rclk ( rclk ),
.rrst_n ( rrst_n),
.rdata ( rdata ),
.wfull ( wfull ),
.rempty ( rempty),
.almost_empty (almost_empty),
.almost_full (almost_full)
);
endmodule
仿真波形:
从波形上看,当almost_gap设定为2时,almost_full信号比full信号提前了一个数据时间。
由于读地址同步到写端需要打两拍,而且empty信号复位也需要一拍,所以图中经过三个rclk周期后empty才被置为0.