FIFO(First In First Out)是異步數(shù)據(jù)傳輸時經(jīng)常使用的存儲器。該存儲器的特點是數(shù)據(jù)先進先出(后進后出)。其實,多位寬數(shù)據(jù)的異步傳輸問題,無論是從快時鐘到慢時鐘域,還是從慢時鐘到快時鐘域,都可以使用 FIFO 處理。
復位之后,在寫時鐘和狀態(tài)信號的控制下,數(shù)據(jù)寫入 FIFO 中。RAM 的寫地址從 0 開始,每寫一次數(shù)據(jù)寫地址指針加一,指向下一個存儲單元。當 FIFO 寫滿后,數(shù)據(jù)將不能再寫入,否則數(shù)據(jù)會因覆蓋而丟失。
FIFO 數(shù)據(jù)為非空、或滿狀態(tài)時,在讀時鐘和狀態(tài)信號的控制下,可以將數(shù)據(jù)從 FIFO 中讀出。RAM 的讀地址從 0 開始,每讀一次數(shù)據(jù)讀地址指針加一,指向下一個存儲單元。當 FIFO 讀空后,就不能再讀數(shù)據(jù),否則讀出的數(shù)據(jù)將是錯誤的。
FIFO 的存儲結構為雙口 RAM,所以允許讀寫同時進行。典型異步 FIFO 結構圖如下所示。端口及內(nèi)部信號將在代碼編寫時進行說明。
關于寫時刻,只要 FIFO 中數(shù)據(jù)為非滿狀態(tài),就可以進行寫操作;如果 FIFO 為滿狀態(tài),則禁止再寫數(shù)據(jù)。
關于讀時刻,只要 FIFO 中數(shù)據(jù)為非空狀態(tài),就可以進行讀操作;如果 FIFO 為空狀態(tài),則禁止再讀數(shù)據(jù)。
不管怎樣,一段正常讀寫 FIFO 的時間段,如果讀寫同時進行,則要求寫 FIFO 速率不能大于讀速率。
開始復位時,F(xiàn)IFO 沒有數(shù)據(jù),空狀態(tài)信號是有效的。當 FIFO 中被寫入數(shù)據(jù)后,空狀態(tài)信號拉低無效。當讀數(shù)據(jù)地址追趕上寫地址,即讀寫地址都相等時,F(xiàn)IFO 為空狀態(tài)。
因為是異步 FIFO,所以讀寫地址進行比較時,需要同步打拍邏輯,就需要耗費一定的時間。所以空狀態(tài)的指示信號不是實時的,會有一定的延時。如果在這段延遲時間內(nèi)又有新的數(shù)據(jù)寫入 FIFO,就會出現(xiàn)空狀態(tài)指示信號有效,但是 FIFO 中其實存在數(shù)據(jù)的現(xiàn)象。
嚴格來講該空狀態(tài)指示是錯誤的。但是產(chǎn)生空狀態(tài)的意義在于防止讀操作對空狀態(tài)的 FIFO 進行數(shù)據(jù)讀取。產(chǎn)生空狀態(tài)信號時,實際 FIFO 中有數(shù)據(jù),相當于提前判斷了空狀態(tài)信號,此時不再進行讀 FIFO 數(shù)據(jù)操作也是安全的。所以,該設計從應用上來說是沒有問題的。
開始復位時,F(xiàn)IFO 沒有數(shù)據(jù),滿信號是無效的。當 FIFO 中被寫入數(shù)據(jù)后,此時讀操作不進行或讀速率相對較慢,只要寫數(shù)據(jù)地址超過讀數(shù)據(jù)地址一個 FIFO 深度時,便會產(chǎn)生滿狀態(tài)信號。此時寫地址和讀地址也是相等的,但是意義是不一樣的。
此時經(jīng)常使用多余的 1bit 分別當做讀寫地址的拓展位,來區(qū)分讀寫地址相同的時候,F(xiàn)IFO 的狀態(tài)是空還是滿狀態(tài)。當讀寫地址與拓展位均相同的時候,表明讀寫數(shù)據(jù)的數(shù)量是一致的,則此時 FIFO 是空狀態(tài)。如果讀寫地址相同,拓展位為相反數(shù),表明寫數(shù)據(jù)的數(shù)量已經(jīng)超過讀數(shù)據(jù)數(shù)量的一個 FIFO 深度了,此時 FIFO 是滿狀態(tài)。當然,此條件成立的前提是空狀態(tài)禁止讀操作、滿狀態(tài)禁止寫操作。
同理,由于異步延遲邏輯的存在,滿狀態(tài)信號也不是實時的。但是也相當于提前判斷了滿狀態(tài)信號,此時不再進行寫 FIFO 操作也不會影響應用的正確性。
為設計應用于各種場景的 FIFO,這里對設計提出如下要求:
RAM 端口參數(shù)可配置,讀寫位寬可以不一致。建議 memory 數(shù)組定義時,以長位寬地址、短位寬數(shù)據(jù)的參數(shù)為參考,方便數(shù)組變量進行選擇訪問。
Verilog 描述如下。
module ramdp
#( parameter AWI = 5 ,
parameter AWO = 7 ,
parameter DWI = 64 ,
parameter DWO = 16
)
(
input CLK_WR , //寫時鐘
input WR_EN , //寫使能
input [AWI-1:0] ADDR_WR ,//寫地址
input [DWI-1:0] D , //寫數(shù)據(jù)
input CLK_RD , //讀時鐘
input RD_EN , //讀使能
input [AWO-1:0] ADDR_RD ,//讀地址
output reg [DWO-1:0] Q //讀數(shù)據(jù)
);
//輸出位寬大于輸入位寬,求取擴大的倍數(shù)及對應的位數(shù)
parameter EXTENT = DWO/DWI ;
parameter EXTENT_BIT = AWI-AWO > 0 ? AWI-AWO : 'b1 ;
//輸入位寬大于輸出位寬,求取縮小的倍數(shù)及對應的位數(shù)
parameter SHRINK = DWI/DWO ;
parameter SHRINK_BIT = AWO-AWI > 0 ? AWO-AWI : 'b1;
genvar i ;
generate
//數(shù)據(jù)位寬展寬(地址位寬縮?。? if (DWO >= DWI) begin
//寫邏輯,每時鐘寫一次
reg [DWI-1:0] mem [(1<<AWI)-1 : 0] ;
always @(posedge CLK_WR) begin
if (WR_EN) begin
mem[ADDR_WR] <= D ;
end
end
//讀邏輯,每時鐘讀 4 次
for (i=0; i<EXTENT; i=i+1) begin
always @(posedge CLK_RD) begin
if (RD_EN) begin
Q[(i+1)*DWI-1: i*DWI] <= mem[(ADDR_RD*EXTENT) + i ] ;
end
end
end
end
//=================================================
//數(shù)據(jù)位寬縮?。ǖ刂肺粚捳箤挘? else begin
//寫邏輯,每時鐘寫 4 次
reg [DWO-1:0] mem [(1<<AWO)-1 : 0] ;
for (i=0; i<SHRINK; i=i+1) begin
always @(posedge CLK_WR) begin
if (WR_EN) begin
mem[(ADDR_WR*SHRINK)+i] <= D[(i+1)*DWO -1: i*DWO] ;
end
end
end
//讀邏輯,每時鐘讀 1 次
always @(posedge CLK_RD) begin
if (RD_EN) begin
Q <= mem[ADDR_RD] ;
end
end
end
endgenerate
endmodule
計數(shù)器用于產(chǎn)生讀寫地址信息,位寬可配置,不需要設置結束值,讓其溢出后自動重新計數(shù)即可。Verilg 描述如下。
module ccnt
#(parameter W )
(
input rstn ,
input clk ,
input en ,
output [W-1:0] count
);
reg [W-1:0] count_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
count_r <= 'b0 ;
end
else if (en) begin
count_r <= count_r + 1'b1 ;
end
end
assign count = count_r ;
endmodule
該模塊為 FIFO 的主體部分,產(chǎn)生讀寫控制邏輯,并產(chǎn)生空、滿、可編程滿狀態(tài)信號。
鑒于篇幅原因,這里只給出讀數(shù)據(jù)位寬大于寫數(shù)據(jù)位寬的邏輯代碼,寫數(shù)據(jù)位寬大于讀數(shù)據(jù)位寬的代碼描述詳見附件。
module fifo
#( parameter AWI = 5 ,
parameter AWO = 3 ,
parameter DWI = 4 ,
parameter DWO = 16 ,
parameter PROG_DEPTH = 16) //可設置深度
(
input rstn, //讀寫使用一個復位
input wclk, //寫時鐘
input winc, //寫使能
input [DWI-1: 0] wdata, //寫數(shù)據(jù)
input rclk, //讀時鐘
input rinc, //讀使能
output [DWO-1 : 0] rdata, //讀數(shù)據(jù)
output wfull, //寫滿標志
output rempty, //讀空標志
output prog_full //可編程滿標志
);
//輸出位寬大于輸入位寬,求取擴大的倍數(shù)及對應的位數(shù)
parameter EXTENT = DWO/DWI ;
parameter EXTENT_BIT = AWI-AWO ;
//輸出位寬小于輸入位寬,求取縮小的倍數(shù)及對應的位數(shù)
parameter SHRINK = DWI/DWO ;
parameter SHRINK_BIT = AWO-AWI ;
//==================== push/wr counter ===============
wire [AWI-1:0] waddr ;
wire wover_flag ; //多使用一位做寫地址拓展
ccnt #(.W(AWI+1))
u_push_cnt(
.rstn (rstn),
.clk (wclk),
.en (winc && !wfull), //full 時禁止寫
.count ({wover_flag, waddr})
);
//============== pop/rd counter ===================
wire [AWO-1:0] raddr ;
wire rover_flag ; //多使用一位做讀地址拓展
ccnt #(.W(AWO+1))
u_pop_cnt(
.rstn (rstn),
.clk (rclk),
.en (rinc & !rempty), //empyt 時禁止讀
.count ({rover_flag, raddr})
);
//==============================================
//窄數(shù)據(jù)進,寬數(shù)據(jù)出
generate
if (DWO >= DWI) begin : EXTENT_WIDTH
//格雷碼轉(zhuǎn)換
wire [AWI:0] wptr = ({wover_flag, waddr}>>1) ^ ({wover_flag, waddr}) ;
//將寫數(shù)據(jù)指針同步到讀時鐘域
reg [AWI:0] rq2_wptr_r0 ;
reg [AWI:0] rq2_wptr_r1 ;
always @(posedge rclk or negedge rstn) begin
if (!rstn) begin
rq2_wptr_r0 <= 'b0 ;
rq2_wptr_r1 <= 'b0 ;
end
else begin
rq2_wptr_r0 <= wptr ;
rq2_wptr_r1 <= rq2_wptr_r0 ;
end
end
//格雷碼轉(zhuǎn)換
wire [AWI-1:0] raddr_ex = raddr << EXTENT_BIT ;
wire [AWI:0] rptr = ({rover_flag, raddr_ex}>>1) ^ ({rover_flag, raddr_ex}) ;
//將讀數(shù)據(jù)指針同步到寫時鐘域
reg [AWI:0] wq2_rptr_r0 ;
reg [AWI:0] wq2_rptr_r1 ;
always @(posedge wclk or negedge rstn) begin
if (!rstn) begin
wq2_rptr_r0 <= 'b0 ;
wq2_rptr_r1 <= 'b0 ;
end
else begin
wq2_rptr_r0 <= rptr ;
wq2_rptr_r1 <= wq2_rptr_r0 ;
end
end
//格雷碼反解碼
//如果只需要空、滿狀態(tài)信號,則不需要反解碼
//因為可編程滿狀態(tài)信號的存在,地址反解碼后便于比較
reg [AWI:0] wq2_rptr_decode ;
reg [AWI:0] rq2_wptr_decode ;
integer i ;
always @(*) begin
wq2_rptr_decode[AWI] = wq2_rptr_r1[AWI];
for (i=AWI-1; i>=0; i=i-1) begin
wq2_rptr_decode[i] = wq2_rptr_decode[i+1] ^ wq2_rptr_r1[i] ;
end
end
always @(*) begin
rq2_wptr_decode[AWI] = rq2_wptr_r1[AWI];
for (i=AWI-1; i>=0; i=i-1) begin
rq2_wptr_decode[i] = rq2_wptr_decode[i+1] ^ rq2_wptr_r1[i] ;
end
end
//讀寫地址、拓展位完全相同是,為空狀態(tài)
assign rempty = (rover_flag == rq2_wptr_decode[AWI]) &&
(raddr_ex >= rq2_wptr_decode[AWI-1:0]);
//讀寫地址相同、拓展位不同,為滿狀態(tài)
assign wfull = (wover_flag != wq2_rptr_decode[AWI]) &&
(waddr >= wq2_rptr_decode[AWI-1:0]) ;
//拓展位一樣時,寫地址必然不小于讀地址
//拓展位不同時,寫地址部分比如小于讀地址,實際寫地址要增加一個FIFO深度
assign prog_full = (wover_flag == wq2_rptr_decode[AWI]) ?
waddr - wq2_rptr_decode[AWI-1:0] >= PROG_DEPTH-1 :
waddr + (1<<AWI) - wq2_rptr_decode[AWI-1:0] >= PROG_DEPTH-1;
//雙口 ram 例化
ramdp
#( .AWI (AWI),
.AWO (AWO),
.DWI (DWI),
.DWO (DWO))
u_ramdp
(
.CLK_WR (wclk),
.WR_EN (winc & !wfull), //寫滿時禁止寫
.ADDR_WR (waddr),
.D (wdata[DWI-1:0]),
.CLK_RD (rclk),
.RD_EN (rinc & !rempty), //讀空時禁止讀
.ADDR_RD (raddr),
.Q (rdata[DWO-1:0])
);
end
//==============================================
//big in and small out
/*
else begin: SHRINK_WIDTH
……
end
*/
endgenerate
endmodule
下面可以調(diào)用設計的 FIFO,完成多位寬數(shù)據(jù)傳輸?shù)漠惒教幚怼?
寫數(shù)據(jù)位寬為 4bit,寫深度為 32。
讀數(shù)據(jù)位寬為 16bit,讀深度為 8,可配置 full 深度為 16。
module fifo_s2b(
input rstn,
input [4-1: 0] din, //異步寫數(shù)據(jù)
input din_clk, //異步寫時鐘
input din_en, //異步寫使能
output [16-1 : 0] dout, //同步后數(shù)據(jù)
input dout_clk, //同步使用時鐘
input dout_en ); //同步數(shù)據(jù)使能
wire fifo_empty, fifo_full, prog_full ;
wire rd_en_wir ;
wire [15:0] dout_wir ;
//讀空狀態(tài)時禁止讀,否則一直讀
assign rd_en_wir = fifo_empty ? 1'b0 : 1'b1 ;
fifo #(.AWI(5), .AWO(3), .DWI(4), .DWO(16), .PROG_DEPTH(16))
u_buf_s2b(
.rstn (rstn),
.wclk (din_clk),
.winc (din_en),
.wdata (din),
.rclk (dout_clk),
.rinc (rd_en_wir),
.rdata (dout_wir),
.wfull (fifo_full),
.rempty (fifo_empty),
.prog_full (prog_full));
//緩存同步后的數(shù)據(jù)和使能
reg dout_en_r ;
always @(posedge dout_clk or negedge rstn) begin
if (!rstn) begin
dout_en_r <= 1'b0 ;
end
else begin
dout_en_r <= rd_en_wir ;
end
end
assign dout = dout_wir ;
assign dout_en = dout_en_r ;
endmodule
testbench
`timescale 1ns/1ns
`define SMALL2BIG
module test ;
`ifdef SMALL2BIG
reg rstn ;
reg clk_slow, clk_fast ;
reg [3:0] din ;
reg din_en ;
wire [15:0] dout ;
wire dout_en ;
//reset
initial begin
clk_slow = 0 ;
clk_fast = 0 ;
rstn = 0 ;
#50 rstn = 1 ;
end
//讀時鐘 clock_slow 較快于寫時鐘 clk_fast 的 1/4
//保證讀數(shù)據(jù)稍快于寫數(shù)據(jù)
parameter CYCLE_WR = 40 ;
always #(CYCLE_WR/2/4) clk_fast = ~clk_fast ;
always #(CYCLE_WR/2-1) clk_slow = ~clk_slow ;
//data generate
initial begin
din = 16'h4321 ;
din_en = 0 ;
wait (rstn) ;
//(1) 測試 full、prog_full、empyt 信號
force test.u_data_buf2.u_buf_s2b.rinc = 1'b0 ;
repeat(32) begin
@(negedge clk_fast) ;
din_en = 1'b1 ;
din = {$random()} % 16;
end
@(negedge clk_fast) din_en = 1'b0 ;
//(2) 測試數(shù)據(jù)讀寫
#500 ;
rstn = 0 ;
#10 rstn = 1 ;
release test.u_data_buf2.u_buf_s2b.rinc;
repeat(100) begin
@(negedge clk_fast) ;
din_en = 1'b1 ;
din = {$random()} % 16;
end
//(3) 停止讀取再一次測試 empyt、full、prog_full 信號
force test.u_data_buf2.u_buf_s2b.rinc = 1'b0 ;
repeat(18) begin
@(negedge clk_fast) ;
din_en = 1'b1 ;
din = {$random()} % 16;
end
end
fifo_s2b u_data_buf2(
.rstn (rstn),
.din (din),
.din_clk (clk_fast),
.din_en (din_en),
.dout (dout),
.dout_clk (clk_slow),
.dout_en (dout_en));
`else
`endif
//stop sim
initial begin
forever begin
#100;
if ($time >= 5000) $finish ;
end
end
endmodule
根據(jù) testbench 中的 3 步測試激勵,分析如下:
測試 (1) : FIFO 端口及一些內(nèi)部信號時序結果如下。
由圖可知,F(xiàn)IFO 內(nèi)部開始寫數(shù)據(jù),空狀態(tài)信號拉低之前有一段時間延遲,這是同步讀寫地址信息導致的。
由于此時沒有進行讀 FIFO 操作,相對于寫數(shù)據(jù)操作,full 和 prog_full 拉高幾乎沒有延遲。
測試 (2) : FIFO 同時進行讀寫時,數(shù)字頂層異步處理模塊的端口信號如下所示,兩圖分別顯示了數(shù)據(jù)開始傳輸、結束傳輸時的讀取過程。
由圖可知,數(shù)據(jù)在開始、末尾均能正確傳輸,完成了不同時鐘域之間多位寬數(shù)據(jù)的異步處理。
測試 (3) :整個 FIFO 讀寫行為及讀停止的時序仿真圖如下所示。
由圖可知,讀寫同時進行時,讀空狀態(tài)信號 rempty 會拉低,表明 FIFO 中有數(shù)據(jù)寫入。一方面讀數(shù)據(jù)速率稍高于寫速率,且數(shù)據(jù)之間傳輸會有延遲,所以中間過程中 rempty 會有拉高的行為。
讀寫過程中,full 與 prog_full 信號一直為低,說明 FIFO 中數(shù)據(jù)并沒有到達一定的數(shù)量。當停止讀操作后,兩個 full 信號不久便拉高,表明 FIFO 已滿。仔細對比讀寫地址信息,F(xiàn)IFO 行為沒有問題。
完整的 FIFO 設計見附件,包括輸入數(shù)據(jù)位寬小于輸出數(shù)據(jù)位寬時的異步設計和仿真。
點擊這里下載源碼
更多建議: