FIFO(First In First Out)是異步數據傳輸時經常使用的存儲器。該存儲器的特點是數據先進先出(后進后出)。其實,多位寬數據的異步傳輸問題,無論是從快時鐘到慢時鐘域,還是從慢時鐘到快時鐘域,都可以使用 FIFO 處理。
FIFO 原理
工作流程
(1) 復位之后,在寫時鐘和非滿狀態信號的控制下,數據可以寫入 FIFO 中。RAM 的寫地址從 0 開始,每寫一次數據寫地址指針加一,指向下一個存儲單元。當 FIFO 寫滿后,數據將不能再寫入,否則數據會因覆蓋而丟失。
(2) FIFO 數據為非空、或滿狀態時,在讀時鐘和非空狀態信號的控制下,數據可以從 FIFO 中讀出。RAM 的讀地址從 0 開始,每讀一次數據讀地址指針加一,即指向下一個存儲單元。當 FIFO 讀空后,就不能再讀數據,否則讀出的數據將是錯誤的。
(3) FIFO 的存儲結構為雙口 RAM,允許讀寫同時進行。FIFO 的讀寫指針是循環計數的,即 讀寫指針對應的 RAM 地址超過 FIFO 深度時,會溢出歸零,重新計數。
典型異步 FIFO 結構圖如下所示。相關信號及空滿狀態的原理將在下面一一說明。
讀寫時刻
(1) 關于寫時刻,只要 FIFO 中數據為非滿狀態,就可以進行寫操作;如果 FIFO 為滿狀態,則禁止再寫數據。
(2) 關于讀時刻,只要 FIFO 中數據為非空狀態,就可以進行讀操作;如果 FIFO 為空狀態,則禁止再讀數據。
(3) 總之,如果一段時間內不間斷的對 FIFO 同時進行讀寫操作,則要求寫 FIFO 速率不能大于讀 FIFO 速率。
讀空狀態
(1) 復位時,FIFO 中沒有數據,空狀態信號拉高。當 FIFO 被寫入數據后,空狀態信號拉低,表示非空狀態。當讀數據地址追趕上寫地址,即讀寫地址都相等時,FIFO 為空狀態。
(2) 因為 FIFO 是異步的,所以讀寫地址進行比較時,需要同步打拍邏輯,就需要耗費一定的時間。因此,空狀態的指示信號不是實時的,會有一定的延時。如果在這段延遲時間內又有新的數據寫入 FIFO,就會出現空狀態指示信號有效,但實際上 FIFO 中存在數據的現象。
(3) 嚴格來講該空狀態指示是錯誤的。但是產生空狀態的意義在于防止讀操作對空狀態的 FIFO 進行數據讀取。產生空狀態信號時,實際 FIFO 中有數據,相當于提前判斷了空狀態信號,此時不再進行讀 FIFO 操作也是安全的。所以,該設計從應用上來說是沒有問題的。
(4) 牢記,讀空狀態信號,是在讀時鐘域產生的。
寫滿狀態
(1) 復位時,FIFO 中沒有數據,滿信號是拉低的,表示 FIFO 中的數據沒有寫滿 (其實 FIFO 是空的 )。當 FIFO 開始寫數據且讀操作不進行或讀速率相對較慢時,只要寫數據地址超過讀數據地址的 FIFO 深度時,便會產生滿狀態信號。此時寫地址和讀地址也是相等的,但是意義是不一樣的。
(2) 此時經常使用多余的 1bit 分別當做讀寫地址的拓展位,來區分讀寫地址相同的時候,FIFO 的狀態是空還是滿狀態。當讀寫地址與拓展位均相同的時候,表明讀寫數據的數量是一致的,則此時 FIFO 是空狀態。如果讀寫地址相同,拓展位相反,表明寫數據的數量已經超過讀數據數量的一個 FIFO 深度了,此時 FIFO 是滿狀態。當然,此條件成立的前提是空狀態禁止讀操作、滿狀態禁止寫操作。
(3) 同理,由于異步延遲邏輯的存在,滿狀態信號也不是實時的。但是也相當于提前判斷了滿狀態信號,此時不再進行寫 FIFO 操作也不會影響應用的正確性。
(4) 牢記,寫滿狀態信號,是在寫時鐘域產生的。
格雷碼
(1) 當讀寫時鐘都是同一個時鐘時,此時 FIFO 是同步的,直接對讀寫指針進行比對,產生空、滿信號即可。
(2) 當讀寫時鐘是異步的時候,因為讀時鐘域產生讀空信號,寫時鐘域產生寫滿信號,所以產生空邏輯信號時,需要將寫指針同步到讀時鐘域,再與讀指針進行比較;產生滿邏輯信號時,需要將讀指針同步到寫時鐘域,再與寫指針進行比較。
(3) 因為讀寫指針的信號寬度一般都是大于 1bit 的,所以同步處理時不能直接對多位寬的讀寫指針進行延遲打拍,需要借助格雷碼對讀寫指針進行轉換,保證每一個周期內地址指針只有 1bit 變化,然后再進行延遲打拍的同步處理。
(4) 4bit 的二進制碼與格雷碼之間的變化關系如下所示,其中 ⊕ 表示異或操作符。由圖可知,二進制碼對應的十六進制碼遞增時,二進制碼對應的相鄰的兩個格雷碼之間只有 1bit 數據有變化。當多位寬信號每次只有 1bit 數據變化時,可以使用延遲打拍的方法對其進行同步處理。
(5) 下面對空邏輯的產生進行舉例說明:
5.1) 首先需要對寫指針 waddr 進行組合邏輯的格雷碼變換 waddr_gray。
5.2) 為了保證 waddr_gary 在讀時鐘域每次被采集時只有 1bit 數據變化,則 waddr_gray 需要在其源時鐘域即寫時鐘域進行一拍緩存 waddr_gray_d。因為 waddr 到 waddr_gray 的組合邏輯變換時,每次兩者之間不只是有 1bit 變化的。
5.3) 在讀時鐘域對 waddr_gray_d 進行打拍同步,得到讀時鐘域同步后的寫指針為 waddr_gray_rclk。
5.4) 根據格雷碼變換規則,空信號有效時二進制碼相等的讀寫指針,變為格雷碼之后仍然相等。所以直接使用 waddr_gray_rclk 與讀指針進行組合邏輯變換后的格雷碼進行相等比較,即可產生讀空信號邏輯。
5.5) 需要說明的是,滿信號有效時,帶有拓展位的讀寫指針高 1bit 相反、低位相同。所以變為格雷碼之后,寫滿信號產生的條件,則是讀寫指針高 2bit 相反、低位相同 (請讀者思考一下為什么?)。
FIFO 設計
設計要求
為設計應用于各種場景的 FIFO,這里對設計提出如下要求:
(1) FIFO 是異步的,即讀寫控制信號來自不同的時鐘域。
(2) FIFO 深度、寬度參數化,輸出空、滿狀態信號,并輸出一個可配置的滿狀態信號。當 FIFO 內部數據達到設置的參數數量時,該信號拉高,此時需要對格雷碼進行反解碼。
(3) 輸入數據和輸出數據位寬可以不一致,但要保證寫數據、寫地址位寬與讀數據、讀地址位寬的一致性。例如寫數據位寬 8bit,寫地址位寬為 6bit(64 個數據)。如果輸出數據位寬要求 32bit,則輸出地址位寬應該為 4bit(16 個數據)。
雙口 RAM 設計
RAM 地址位寬、數據位寬等端口參數可配置,讀寫位寬一致。實際中 RAMDP(Dual Port) 是需要使用 Memory IP 的,這里創建的 RAM 并沒有考慮到異步問題。
Verilog 描述如下。
module ramdp
#( parameter AW = 5 ,
parameter DW = 16
)
(
input CLK_WR , //寫時鐘
input [DW-1:0] D , //寫數據
input WR_EN , //寫使能
input [AW-1:0] ADDR_WR ,//寫地址
input CLK_RD , //讀時鐘
input RD_EN , //讀使能
input [AW-1:0] ADDR_RD ,//讀地址
output reg [DW-1:0] Q //讀數據
);
reg [DW-1:0] mem [(1<
計數器設計
計數器用于產生讀寫地址信息,位寬可配置,不需要設置結束值,讓其溢出后自動重新計數即可。同時該計數器還具有格雷碼轉換與緩存的功能。
Verilog 描述如下。
module ccnt_gray
#(parameter W = 32'd8
)
(
input rstn ,
input clk ,
input en ,
output [W-1:0] cnt ,
output [W-1:0] cnt_gray ,
output [W-1:0] cnt_gray_d
);
reg [W-1:0] cnt_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
cnt_r <= 'b0 ;
end
else if (en) begin
cnt_r <= cnt_r + 1'b1 ;
end
end
assign cnt = cnt_r ;
assign cnt_gray = cnt ^ (cnt>>1);
reg [W-1:0] cnt_gray_buf ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
cnt_gray_buf <= 'b0 ;
end
else begin
cnt_gray_buf <= cnt_gray ;
end
end
assign cnt_gray_d = cnt_gray_buf ;
endmodule
多位寬數據同步設計
讀寫指針進行格雷碼變換并緩存后,每一個計數周期內地址指針只有 1bit 變化,所以可以直接使用延遲打拍的方法進行同步。數據寬度、同步級數均可配置。
Verilog 描述如下。
module data1c_sync
#(parameter DW = 8,
parameter STAGE = 3
)
(
input rstn ,
input clk ,
input [DW-1:0] data_in ,
output [DW-1:0] data_out
);
reg [DW-1: 0] data_r [STAGE-1: 0];
integer i ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
for (i=0; i
格雷碼反解碼
因為該 FIFO 還存在一個可配置的滿狀態信號輸出,所以需要對格雷碼同步后的讀指針進行反解碼,然后在寫時鐘域與寫指針進行比較,以判讀當前 FIFO 中數據的具體個數。
module gray_decode
#(parameter W = 32'd8
)
(
input [W-1:0] gray ,
output [W-1:0] gray_decode
);
integer i ;
reg [W-1:0] gray_decode_r ;
always @(*) begin
gray_decode_r[W-1] = gray[W-1];
for (i=W-2; i>=0; i=i-1) begin
gray_decode_r[i] = gray_decode_r[i+1] ^ gray[i];
end
end
assign gray_decode = gray_decode_r ;
endmodule
FIFO 設計
該模塊為 FIFO 的主體部分,產生讀寫控制邏輯,包括讀寫指針、讀寫有效時刻以及空、滿、可編程滿狀態的邏輯。
實際上此模塊已經是典型的 FIFO 設計,有需要的讀者可以直接使用該層次的 FIFO 代碼進行測試,甚至應用到自己的設計之中。
module fifo
#( parameter DW = 16 ,
parameter DEPTH = 32 ,
parameter PROG_DEPTH = 16) //可設置深度
(
input rstn, //讀寫使用一個復位
input wclk, //寫時鐘
input wren, //寫使能
input [DW-1: 0] wdata, //寫數據
output wfull, //寫滿標志
output prog_full, //可編程滿標志
input rclk, //讀時鐘
input rden, //讀使能
output [DW-1 : 0] rdata, //讀數據
output rempty //讀空標志
);
localparam AW = log2b(DEPTH);
//==================== push/wr counter ===============
//wptr/waddr using one more bit to indict new-loop
wire [AW:0] waddr ;
wire [AW:0] waddr_gray ;
wire [AW:0] waddr_gray_d ;
ccnt_gray #(.W(AW+1))
u_push_cnt(
.rstn (rstn),
.clk (wclk),
.en (wren && !wfull), //full 時禁止寫
.cnt (waddr),
.cnt_gray (waddr_gray),
.cnt_gray_d (waddr_gray_d)
);
// sync: wptr from wclk to rclk
wire [AW:0] waddr_gray_rclk ;
data1c_sync #(.DW(AW+1), .STAGE(3))
u_waddr_to_rclk
(
.rstn (rstn),
.clk (rclk),
.data_in (waddr_gray_d),
.data_out (waddr_gray_rclk)
);
//============== pop/rd counter ===================
wire [AW:0] raddr ;
wire [AW:0] raddr_gray ;
wire [AW:0] raddr_gray_d ;
ccnt_gray #(.W(AW+1))
u_pop_cnt(
.rstn (rstn),
.clk (rclk),
.en (rden && !rempty), //full 時禁止寫
.cnt (raddr),
.cnt_gray (raddr_gray),
.cnt_gray_d (raddr_gray_d)
);
// sync: rdtr from rclk to wclk
wire [AW:0] raddr_gray_wclk ;
data1c_sync #(.DW(AW+1), .STAGE(3) )
u_raddr_to_wclk
(
.rstn (rstn),
.clk (wclk),
.data_in (raddr_gray_d),
.data_out (raddr_gray_wclk)
);
//============== full/empty logic ===================
//(1) empty logic
assign rempty = (raddr_gray == waddr_gray_rclk);
//(2) full logic
assign wfull = (waddr_gray[AW:AW-1] == ~raddr_gray_wclk[AW:AW-1]) &&
(waddr_gray[AW-2:0] == raddr_gray_wclk[AW-2:0]) ;
//(3) porgrammable full
//waddr gray decode
wire [AW:0] raddr_degray_wclk ;
gray_decode #(.W(AW+1))
u_waddr_degray_rclk (
.gray (raddr_gray_wclk),
.gray_decode (raddr_degray_wclk)
);
//prog full
wire [AW:0] waddr_delta = waddr >= raddr_degray_wclk ?
(waddr - raddr_degray_wclk) :
((1<<(AW+1)) + waddr - raddr_degray_wclk) ;
assign prog_full = waddr_delta >= PROG_DEPTH ;
//雙口 ram 例化
ramdp #(.AW(AW), .DW (DW))
u_ramdp
(
.CLK_WR (wclk),
.WR_EN (wren & !wfull), //寫滿時禁止寫
.ADDR_WR (waddr[AW-1:0]),
.D (wdata[DW-1:0]),
.CLK_RD (rclk),
.RD_EN (rden & !rempty), //讀空時禁止讀
.ADDR_RD (raddr[AW-1:0]),
.Q (rdata[DW-1:0])
);
function integer log2b ;
input integer depth ;
for (log2b=0; (1<
FIFO 調用
下面可以調用設計的 FIFO,完成多位寬數據傳輸的異步處理。
寫數據位寬為 4bit,寫深度為 32。
讀數據位寬為 16bit,讀深度為 8,可配置 full 深度為 16。
該模塊只是 FIFO 的一個具體應用,用于數據的異步傳輸、緩存與整合。
//ensure write rate < read rate
module fifo_buf
#( parameter DWI = 4 , //width 4
parameter AWI = 5 , //depth 32
parameter DWO = 16 ,
parameter AWO = 3 ,
parameter PROG_DEPTH = 16
)
(
input rstn, //讀寫使用一個復位
//data in
input din_clk, //寫時鐘
input din_en, //寫使能
input [DWI-1: 0] din, //寫數據
//data out
input dout_clk, //讀時鐘
output dout_valid, //讀使能
output [DWO-1 : 0] dout //讀數據
);
wire wfull ; //寫滿標志
wire prog_full ; //可編程滿標志
wire rempty ; //讀空標志
wire [DWI-1:0] rdata_fifo ;
wire rden_fifo ;
fifo #(.DW(DWI), .DEPTH(1<
testbench
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_valid ;
//reset
initial begin
clk_slow = 0 ;
clk_fast = 0 ;
rstn = 0 ;
#50 rstn = 1 ;
end
//讀時鐘 clock_slow 較快于寫時鐘 clk_fast 的 1/4
//保證讀數據稍快于寫數據
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_buf.u_fifo.rden = 1'b0 ;
repeat(32) begin
@(negedge clk_fast) ;
din_en = 1'b1 ;
din = {$random()} % 16;
end
@(negedge clk_fast) din_en = 1'b0 ;
//(2) 測試數據讀寫
#500 ;
rstn = 0 ;
#10 rstn = 1 ;
release test.u_data_buf.u_fifo.rden ;
repeat(60) begin
@(negedge clk_fast) ;
if (!test.u_data_buf.u_fifo.wfull) begin
din_en = 1'b1 ;
din = {$random()} % 16;
end
else begin
din_en = 1'b0 ;
end
end
//(3) 停止讀取再一次測試 empyt、full、prog_full 信號
#800 ;
force test.u_data_buf.u_fifo.rden = 1'b0 ;
repeat(18) begin
@(negedge clk_fast) ;
din_en = 1'b1 ;
din = {$random()} % 16;
end
end
fifo_buf #(.DWI(4), .AWI(5), .DWO(16), .AWO(3), .PROG_DEPTH(16))
u_data_buf(
.rstn (rstn),
.din_clk (clk_fast),
.din (din),
.din_en (din_en),
.dout_clk (clk_slow),
.dout (dout),
.dout_valid (dout_valid));
`else // !`ifdef SMALL2BIG
`endif
//stop sim
initial begin
forever begin
#100;
if ($time >= 5000) $finish ;
end
end
endmodule
仿真分析
根據 testbench 中的 3 步測試激勵,分析如下:
測試 (1) : FIFO 端口及一些內部信號時序結果如下。
由圖可知,FIFO 內部開始寫數據,空狀態信號 rempty 拉低(紅色 M1 ) 之前有一段時間延遲,這是寫地址同步延時導致的。
由于此時沒有進行讀 FIFO 操作,所以 prog_full 與 full 拉高 (黃色 M2 與綠色 M3) 幾乎沒有延遲。
測試 (2) : FIFO 同時進行讀寫時,fifo 與 fifo_buf 模塊的端口信號如下所示。
由圖可知,數據開始傳輸時,fifo 模塊的等位寬數據輸出、fifo_buf 整合之后的數據輸出,均與輸入數據對應,沒有傳輸錯誤。
測試 (3) :整個 FIFO 讀寫行為及讀停止的時序仿真圖如下所示。
由圖可知,讀寫操作同時進行時,wfull 信號會不斷翻轉 (M4 時刻之前),這是因為此時 fifo 的使用方法是非滿即寫,非空即讀。
M4 時刻禁止寫操作后(din_en 恒為 0),由于讀一致存在,所以 full 信號會拉低并保持,表示 fifo 中數據一直未滿。而 prog_full 也會相繼拉低 (M5 時刻),表示 FIFO 中的數據已經低于配置的數目。
當恢復寫操作后 (din_en 恒為 1,對應 M6 時刻),prog_full 與 full 信號相繼拉高。
-
存儲器
+關注
關注
38文章
7635瀏覽量
166406 -
fifo
+關注
關注
3文章
400瀏覽量
44615 -
計數器
+關注
關注
32文章
2284瀏覽量
96029 -
時鐘
+關注
關注
11文章
1879瀏覽量
132840 -
指針
+關注
關注
1文章
484瀏覽量
70994
發布評論請先 登錄
FIFO隊列原理簡述
同步FIFO設計詳解及代碼分享

AXI接口FIFO簡介

FIFO的操作
異步FIFO結構
什么是fifo fifo什么意思 GPIF和FIFO的區別
如何配置自己需要的FIFO?FIFO配置全攻略

異步fifo詳解
FIFO設計—同步FIFO

同步FIFO和異步FIFO的區別 同步FIFO和異步FIFO各在什么情況下應用
同步FIFO和異步FIFO區別介紹

評論