異步FIFO
一、原理介紹
這里我就不多寫原理了(Doreen大佬寫的很好),主要記錄一下自己學習異步FIFO過程中的理解。
1.1 ram讀寫
看“聲明”中的帖子時,發現同步FIFO和異步FIFO在讀寫ram時有一個明顯的區別。即同步FIFO讀寫ram需要外部輸入的讀寫信號以及空滿信號聯合判斷能否讀寫,而異步FIFO是通過外部輸入的讀寫信號以及空滿信號聯合對讀寫指針的自增進行限制,從而達到控制是否讀寫的目的。
由于空滿標志的判斷需要將讀寫指針同步到對方的時鐘域下,要打兩拍,所以說空滿標志的判斷是不及時的。例如FIFO深度1024,寫入1024個數據后,開始讀出數據。當讀出一個數據后,就應該可以繼續寫入了,但由于讀地址有兩拍的滯后性,此時出現“假滿”,還不能寫數據,不過這種情況不是不可接受的,只是FIFO的效率降低一點罷了。“假空”也是類似的。
二、異步FIFO代碼
2.1 代碼
`timescale 1ns/1ps
module AsyncFIFO #(
parameter DATA_WIDTH = 8,
parameter FIFO_DEPTH = 16
)(
input wire wr_clk_i ,
input wire rd_clk_i ,
input wire wr_rstn_i ,
input wire rd_rstn_i ,
input wire rd_en_i ,
input wire wr_en_i ,
input wire [DATA_WIDTH-1:0] wr_data_i ,
output wire [DATA_WIDTH-1:0] rd_data_o ,
output wire fifo_full_o ,
output wire fifo_empty_o
);
localparam DEPTH = $clog2(FIFO_DEPTH);
reg [DEPTH:0] wr_ptr_d1;
reg [DEPTH:0] rd_ptr_d1;
reg [DEPTH:0] wr_ptr_gray_sync1;
reg [DEPTH:0] wr_ptr_gray_sync2;
reg [DEPTH:0] rd_ptr_gray_sync1;
reg [DEPTH:0] rd_ptr_gray_sync2;
reg fifo_empty_d1;
wire [DEPTH:0] wr_ptr_gray = (wr_ptr_d1 >> 1) ^ wr_ptr_d1;
wire [DEPTH:0] rd_ptr_gray = (rd_ptr_d1 >> 1) ^ rd_ptr_d1;
wire fifo_empty = (rd_ptr_gray == wr_ptr_gray_sync2) ? 1'b1 : 1'b0;
wire wr_able = wr_en_i && !fifo_full_o;
wire rd_able = rd_en_i && !fifo_empty;
wire [DEPTH:0] wr_ptr = (wr_able) ? wr_ptr_d1 + 1'b1 : wr_ptr_d1;
wire [DEPTH:0] rd_ptr = (rd_able) ? rd_ptr_d1 + 1'b1 : rd_ptr_d1;
reg [DATA_WIDTH-1:0] ram [0:FIFO_DEPTH-1];
reg [DATA_WIDTH-1:0] rd_data;
wire [DEPTH-1:0] wr_addr = wr_ptr_d1[DEPTH-1:0];
wire [DEPTH-1:0] rd_addr = rd_ptr_d1[DEPTH-1:0];
always @(posedge wr_clk_i) if(wr_able) ram[wr_addr] <= wr_data_i;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) rd_data <= 'd0; else if(rd_able) rd_data <= ram[rd_addr];
always @(posedge wr_clk_i or negedge wr_rstn_i) if(!wr_rstn_i) wr_ptr_d1 <= 'd0; else wr_ptr_d1 <= wr_ptr;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) rd_ptr_d1 <= 'd0; else rd_ptr_d1 <= rd_ptr;
always @(posedge wr_clk_i or negedge wr_rstn_i) if(!wr_rstn_i) rd_ptr_gray_sync1 <= 'd0; else rd_ptr_gray_sync1 <= rd_ptr_gray;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) wr_ptr_gray_sync1 <= 'd0; else wr_ptr_gray_sync1 <= wr_ptr_gray;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) fifo_empty_d1 <= 'd0; else fifo_empty_d1 <= fifo_empty;
always @(posedge wr_clk_i or negedge wr_rstn_i) if(!wr_rstn_i) rd_ptr_gray_sync2 <= 'd0; else rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) wr_ptr_gray_sync2 <= 'd0; else wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
assign rd_data_o = rd_data;
assign fifo_full_o = (wr_ptr_gray == {~rd_ptr_gray_sync2[DEPTH:DEPTH-1], rd_ptr_gray_sync2[DEPTH-2:0]}) ? 1'b1 : 1'b0;
assign fifo_empty_o = fifo_empty_d1;
endmodule
設計的整體思路都是模仿Doreen的方法。
最初寫代碼的時候,fifo_empty_o沒有像上面的代碼一樣延一個時鐘周期,仿真時一直拉高rd_en_i時能正常讀出ram的最后一個值,但是在讀出最后一個值的同時fifo_empty_o就拉高了,我是認為這里有點奇怪,但是覺得既然能正常讀出,應該沒有多大的問題。
但是后來我想到,如果fifo輸出了fifo_empty_o給讀取電路,讀取電路就應該立即拉低rd_en_i,而不是像上面說的恒拉高rd_en_i,此時就會出現漏讀,像這樣,

左邊的藍色框正常寫入8個數據,wr_en_i有1ps的延時,所以最左側的上升沿沒有跟寫時鐘上升沿對齊。紅框中是讀出數據,rd_en_i同樣有1ps的延時,只讀出了前7個數據,所以fifo_empty_o應該要延一個時鐘周期。
延一個時鐘周期的圖如下,

可見紅框中正常讀出了8個數,只是數字8沒有截出來。
然后考慮到萬一讀電路保持rd_en_i恒為高,能不能正常工作,

這里rd_en_i就是恒拉高,在讀出8的下一個時鐘上升沿,fifo_empty_o才拉高,我認為這樣才是正確的,而且rd_en_i恒拉高沒有產生任何影響。
下面是從還沒有寫入數據就開始讀,

在還沒有寫入數據的時候是讀不出數據的,并且由于指針同步造成的假空,導致fifo_empty_o頻繁翻轉,但是讀數據是正常讀出了8個數。
上面是慢到快,經過驗證,快到慢、非整數倍時鐘都一樣能正常工作。
針對fifo_empty_o是否需要延遲這一個時鐘周期,或者本來就應該是這樣(我最初的寫法有問題),歡迎指正。
2.2 仿真代碼
`timescale 1ns/1ps
module AsyncFIFO_tb();
localparam DATA_WIDTH = 8;
localparam FIFO_DEPTH = 8;
reg wr_clk_i;
reg rd_clk_i;
reg wr_rstn_i;
reg rd_rstn_i;
reg rd_en_i;
reg wr_en_i;
reg [DATA_WIDTH-1:0] wr_data_i;
wire [DATA_WIDTH-1:0] rd_data_o;
wire fifo_full_o;
wire fifo_empty_o;
AsyncFIFO #(
.DATA_WIDTH(DATA_WIDTH),
.FIFO_DEPTH(FIFO_DEPTH)
)myFIFO(
.wr_clk_i (wr_clk_i),
.rd_clk_i (rd_clk_i),
.wr_rstn_i (wr_rstn_i),
.rd_rstn_i (rd_rstn_i),
.rd_en_i (rd_en_i),
.wr_en_i (wr_en_i),
.wr_data_i (wr_data_i),
.rd_data_o (rd_data_o),
.fifo_full_o (fifo_full_o),
.fifo_empty_o (fifo_empty_o)
);
initial begin
$fsdbDumpfile("test.fsdb");
$fsdbDumpvars(0, AsyncFIFO_tb);
end
always #20 wr_clk_i = ~wr_clk_i;
always #10 rd_clk_i = ~rd_clk_i;
initial begin
wr_clk_i = 0;
rd_clk_i = 0;
wr_rstn_i = 1;
rd_rstn_i = 1;
wr_en_i = 0;
rd_en_i = 0;
wr_data_i = 0;
#10;
wr_rstn_i = 0;
rd_rstn_i = 0;
#10;
wr_rstn_i = 1;
rd_rstn_i = 1;
repeat(10) @(posedge wr_clk_i);
@(posedge rd_clk_i) #1 rd_en_i = 1;
repeat(10) @(posedge wr_clk_i);
@(posedge wr_clk_i) #1 wr_en_i = 1; wr_data_i = 1;
@(posedge wr_clk_i) #1 wr_data_i = 2;
@(posedge wr_clk_i) #1 wr_data_i = 3;
@(posedge wr_clk_i) #1 wr_data_i = 4;
@(posedge wr_clk_i) #1 wr_data_i = 5;
@(posedge wr_clk_i) #1 wr_data_i = 6;
@(posedge wr_clk_i) #1 wr_data_i = 7;
@(posedge wr_clk_i) #1 wr_data_i = 8;
@(posedge wr_clk_i) #1 wr_en_i = 0;
//@(posedge rd_clk_i) #1 rd_en_i = 1;
//repeat(9) @(posedge rd_clk_i);
//rd_en_i = 0;
//repeat(10) @(posedge rd_clk_i);
repeat(100) @(posedge wr_clk_i);
$finish;
end
endmodule
聲明
本文的第一節和第二節參考自<掰開揉碎講 FIFO(同步FIFO和異步FIFO) - Doreen的FPGA自留地 - 博客園>
http://www.rzrgm.cn/DoreenLiu/p/17348480.html
浙公網安備 33010602011771號