1 異步FIFO結(jié)構(gòu)
在上篇文章中我們給出了FIFO的基本接口圖
并且指出,該圖適用于所有的FIFO,這次我們先看看異步FIFO內(nèi)部的大體框圖
異步FIFO主要由五部分組成:寫控制端、讀控制端、FIFO Memory和兩個(gè)時(shí)鐘同步端
寫控制端用于判斷是否可以寫入數(shù)據(jù)
讀控制端用于判斷是否可以讀取數(shù)據(jù)
FIFO Memory用于存儲(chǔ)數(shù)據(jù)
兩個(gè)時(shí)鐘同步端用于將讀寫時(shí)鐘進(jìn)行同步處理
介紹完內(nèi)部結(jié)構(gòu),我們?cè)诤?strong>基本接口圖做個(gè)聯(lián)動(dòng)
剛才說過,讀/寫控制端用于判斷能否寫入/讀取數(shù)據(jù),判斷能否寫入/讀取數(shù)據(jù)關(guān)鍵在于:
- 寫操作時(shí),寫使能有效且FIFO未滿
- 讀操作時(shí),讀使能有效且FIFO未空
因此兩個(gè)使能信號(hào)和空滿判斷信號(hào)都連接到控制端上
最后我們?cè)偌由蠒r(shí)鐘信號(hào)和復(fù)位信號(hào)
這便是完整的異步FIFO簡(jiǎn)化框圖
2 空滿判斷
在同步FIFO篇中,我們給出了兩個(gè)判斷空滿狀態(tài)的圖
并且也有指出,讀空狀態(tài)可以理解為 讀地址指針追上寫地址指針 ,寫滿狀態(tài)可以理解為寫地址指針再次追上讀地址指針
在同步FIFO中,因?yàn)樽x寫都是在同一個(gè)時(shí)鐘信號(hào)下進(jìn)行的,因此兩個(gè)地址指針可以直接進(jìn)行比較
但在異步FIFO中,讀寫是在不同的時(shí)鐘信號(hào)下進(jìn)行的,因此在進(jìn)行比較之前,應(yīng)當(dāng)先進(jìn)行跨時(shí)鐘與同步
在時(shí)鐘同步之前,我們應(yīng)當(dāng)先將二進(jìn)制地址轉(zhuǎn)換為格雷碼,因?yàn)楦窭状a相鄰的兩個(gè)狀態(tài)之間,只有1 bit數(shù)據(jù)發(fā)生翻轉(zhuǎn)
下面給出二進(jìn)制與格雷碼的對(duì)照?qǐng)D
上面也有說到,讀指針追上寫指針是讀空,寫指針再次追上讀指針是寫滿,為了便于理解,我們做一個(gè)環(huán)形圖
假設(shè)內(nèi)圈為讀,外圈為寫,讀空時(shí)
是讀寫指針應(yīng)當(dāng)指向同一個(gè)地址,就像這樣
此時(shí),讀地址應(yīng)當(dāng)和寫地址完全相同,就以0010為例,0010的格雷碼為0011,可以看出對(duì)于讀空狀態(tài),無論是二進(jìn)制還是格雷碼均是所有位都相同
寫滿
和讀空略有不同,應(yīng)當(dāng)是下面這樣
細(xì)心的小伙伴應(yīng)該可以發(fā)現(xiàn),上面在提到寫滿時(shí),說的是寫指針再次追上讀指針,也就是說,寫滿時(shí),寫指針比讀指針多走一圈,為了便于區(qū)分,將地址位寬從3 bit拓寬到4 bit,因此此時(shí)的寫指針地址可以認(rèn)為是1010
1010的格雷碼是1111, 0010的格雷碼是0011,對(duì)比兩個(gè)格雷碼是不是可以發(fā)現(xiàn),此時(shí) 高兩位相反,低兩位相同 ,這便是格雷碼下
寫滿的判斷條件
Verilog中表示為
//寫滿判斷
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn)
fifo_full <= 0;
else if((wr_ptr_g[$clog2(DEPTH)] != rd_ptr_grr[$clog2(DEPTH)]) && (wr_ptr_g[$clog2(DEPTH) - 1] != rd_ptr_grr[$clog2(DEPTH) - 1]) && (wr_ptr_g[$clog2(DEPTH) - 2 : 0] == rd_ptr_grr[$clog2(DEPTH) - 2 : 0]))
fifo_full <= 1;
else
fifo_full <= 0;
end
//讀空判斷
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn)
fifo_empty <= 0;
else if(wr_ptr_grr[$clog2(DEPTH) : 0] == rd_ptr_g[$clog2(DEPTH) : 0])
fifo_empty <= 1;
else
fifo_empty <= 0;
end
3 時(shí)鐘同步
在同步FIFO設(shè)計(jì)中,因?yàn)樽x寫指針在同一個(gè)時(shí)鐘下,因此可以直接進(jìn)行比較
但在異步FIFO中,由于讀寫指針在不同的時(shí)鐘下,因此需要將兩個(gè)地址指針進(jìn)行時(shí)鐘同步操作
在異步FIFO中,常用的同步方法是兩級(jí)同步打拍延遲,同步地址指針的大致過程如下:
寫操作時(shí),先將寫地址指針轉(zhuǎn)換成格雷碼,然后通過兩級(jí)同步(兩級(jí)同步在讀時(shí)鐘下進(jìn)行),將寫地址指針同步到讀時(shí)鐘域下;讀操作類似
根據(jù)這個(gè)過程圖,也可以看出空滿判斷的方式:
- 寫滿在寫時(shí)鐘下判斷,將寫地址指針的格雷碼與同步過來的讀地址指針格雷碼進(jìn)行比較,符合寫滿條件,即FIFO
虛滿
- 讀空在讀時(shí)鐘下判斷,將讀地址指針的格雷碼與同步過來的寫地址指針格雷碼進(jìn)行比較,符合讀空條件,即FIFO
虛空
留意下,這里我說的是虛空/滿
,并不是輸入錯(cuò)誤喲,具體解釋我放在文章最后,愛思考的朋友現(xiàn)在可以思考一下原因
下面給出時(shí)鐘同步的Verilog代碼
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1); //B2G
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//寫指針同步到讀時(shí)鐘域
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn) begin
wr_ptr_gr <= 0;
wr_ptr_grr <= 0;
end
else begin
wr_ptr_gr <= wr_ptr_g;
wr_ptr_grr <= wr_ptr_gr;
end
end
//讀指針同步到寫時(shí)鐘域
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn) begin
rd_ptr_gr <= 0;
rd_ptr_grr <= 0;
end
else begin
rd_ptr_gr <= rd_ptr_g;
rd_ptr_grr <= rd_ptr_gr;
end
end
4 異步FIFO設(shè)計(jì)
下面給出整體Verilog代碼
module asy_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 8
)(
input [WIDTH - 1 : 0] wr_data,
input wr_clk,
input wr_rstn,
input wr_en,
input rd_clk,
input rd_rstn,
input rd_en,
output fifo_full,
output fifo_empty,
output [WIDTH - 1 : 0] rd_data
);
//定義讀寫指針
reg [$clog2(DEPTH) : 0] wr_ptr, rd_ptr;
//定義一個(gè)寬度為WIDTH,深度為DEPTH的fifo
reg [WIDTH - 1 : 0] fifo [DEPTH - 1 : 0];
//定義讀數(shù)據(jù)
reg [WIDTH - 1 : 0] rd_data;
//寫操作
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn)
wr_ptr <= 0;
else if(wr_en && !fifo_full) begin
fifo[wr_ptr] <= wr_data;
wr_ptr <= wr_ptr + 1;
end
else
wr_ptr <= wr_ptr;
end
//讀操作
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn) begin
rd_ptr <= 0;
rd_data <= 0;
end
else if(rd_en && !fifo_empty) begin
rd_data <= fifo[rd_ptr];
rd_ptr <= rd_ptr + 1;
end
else
rd_ptr <= rd_ptr;
end
//定義讀寫指針格雷碼
wire [$clog2(DEPTH) : 0] wr_ptr_g;
wire [$clog2(DEPTH) : 0] rd_ptr_g;
//讀寫指針轉(zhuǎn)換成格雷碼
assign wr_ptr_g = wr_ptr ^ (wr_ptr >>> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >>> 1);
//定義打拍延遲格雷碼
reg [$clog2(DEPTH) : 0] wr_ptr_gr, wr_ptr_grr;
reg [$clog2(DEPTH) : 0] rd_ptr_gr, rd_ptr_grr;
//寫指針同步到讀時(shí)鐘域
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn) begin
wr_ptr_gr <= 0;
wr_ptr_grr <= 0;
end
else begin
wr_ptr_gr <= wr_ptr_g;
wr_ptr_grr <= wr_ptr_gr;
end
end
//讀指針同步到寫時(shí)鐘域
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn) begin
rd_ptr_gr <= 0;
rd_ptr_grr <= 0;
end
else begin
rd_ptr_gr <= rd_ptr_g;
rd_ptr_grr <= rd_ptr_gr;
end
end
//聲明空滿信號(hào)數(shù)據(jù)類型
reg fifo_full;
reg fifo_empty;
//寫滿判斷
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn)
fifo_full <= 0;
else if((wr_ptr_g[$clog2(DEPTH)] != rd_ptr_grr[$clog2(DEPTH)]) && (wr_ptr_g[$clog2(DEPTH) - 1] != rd_ptr_grr[$clog2(DEPTH) - 1]) && (wr_ptr_g[$clog2(DEPTH) - 2 : 0] == rd_ptr_grr[$clog2(DEPTH) - 2 : 0]))
fifo_full <= 1;
else
fifo_full <= 0;
end
//讀空判斷
always @ (posedge rd_clk or negedge rd_rstn) begin
if(!rd_rstn)
fifo_empty <= 0;
else if(wr_ptr_grr[$clog2(DEPTH) : 0] == rd_ptr_g[$clog2(DEPTH) : 0])
fifo_empty <= 1;
else
fifo_empty <= 0;
end
endmodule
下面是tb
module asy_fifo_tb;
parameter width = 8;
parameter depth = 8;
reg wr_clk, wr_en, wr_rstn;
reg rd_clk, rd_en, rd_rstn;
reg [width - 1 : 0] wr_data;
wire fifo_full, fifo_empty;
wire [width - 1 : 0] rd_data;
//實(shí)例化
asy_fifo myfifo (
.wr_clk(wr_clk),
.rd_clk(rd_clk),
.wr_rstn(wr_rstn),
.rd_rstn(rd_rstn),
.wr_en(wr_en),
.rd_en(rd_en),
.wr_data(wr_data),
.rd_data(rd_data),
.fifo_empty(fifo_empty),
.fifo_full(fifo_full)
);
//時(shí)鐘
initial begin
rd_clk = 0;
forever #25 rd_clk = ~rd_clk;
end
initial begin
wr_clk = 0;
forever #30 wr_clk = ~wr_clk;
end
//波形顯示
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0, myfifo);
$fsdbDumpon();
end
//賦值
initial begin
wr_en = 0;
rd_en = 0;
wr_rstn = 1;
rd_rstn = 1;
#10;
wr_rstn = 0;
rd_rstn = 0;
#20;
wr_rstn = 1;
rd_rstn = 1;
@(negedge wr_clk)
wr_data = {$random}%30;
wr_en = 1;
repeat(7) begin
@(negedge wr_clk)
wr_data = {$random}%30;
end
@(negedge wr_clk)
wr_en = 0;
@(negedge rd_clk)
rd_en = 1;
repeat(7) begin
@(negedge rd_clk);
end
@(negedge rd_clk)
rd_en = 0;
#150;
@(negedge wr_clk)
wr_en = 1;
wr_data = {$random}%30;
repeat(15) begin
@(negedge wr_clk)
wr_data = {$random}%30;
end
@(negedge wr_clk)
wr_en = 0;
#50;
$finish;
end
endmodule
下面貼上運(yùn)行結(jié)果
這里有一點(diǎn)需要說明
藍(lán)色框的位置,已經(jīng)開始寫入數(shù)據(jù),但fifo_empty
信號(hào)并沒有被拉低,而是在第三個(gè)rd_clk
上升沿被拉低,這是因?yàn)樵谂袛郌IFO是否讀空時(shí),是在讀時(shí)鐘下判斷,并且,進(jìn)行判斷時(shí),寫地址格雷碼需要在讀時(shí)鐘域進(jìn)行兩次同步,最后進(jìn)行比較
為了更清晰的解釋,可以將所有的地址指針也加入到波形圖中
注意剛才提到的位置
在位置1,有數(shù)據(jù)寫入,此時(shí)wr_ptr
和wr_ptr_g
都發(fā)生了變化,wr_ptr_gr
和wr_ptr_grr
保持不變
在位置2,wr_ptr_gr
變化,wr_ptr_grr
保持不變
在位置3,wr_ptr_grr
才開始發(fā)生變化
在位置4,wr_ptr_grr
和rd_ptr_g
進(jìn)行比較,判定此時(shí)FIFO非空
還有一個(gè)有意思的地方
注意wr_ptr_gr
和wr_ptr_grr
,這兩個(gè)保持了兩個(gè)rd_clk時(shí)鐘周期,返回查看完整波形圖的話,這種情況只出現(xiàn)在寫指針的格雷碼
這tb文件中,我們?cè)O(shè)定的是,rd_clk比wr_clk快,打兩拍同步的方式,慢時(shí)鐘域同步到快時(shí)鐘域和快時(shí)鐘域同步到慢時(shí)鐘域處理方式是不同的,后面有時(shí)間再做介紹。
有興趣的小伙伴,可以試下如果rd_clk比wr_clk慢,這種情況會(huì)出現(xiàn)在rd_ptr_gr
和rd_ptr_grr
5 一個(gè)我在面試中被問到的問題
我在面試的時(shí)候被問到,如果跨時(shí)鐘域同步時(shí),同步過來的是一個(gè)亞穩(wěn)態(tài)的數(shù)據(jù),那么FIFO還能否正常工作?
在空滿判斷部分說過,讀空或?qū)憹M可以理解為:
- 讀空時(shí):可以理解為是讀指針在追寫指針
- 寫滿時(shí):可以理解為是寫指針在追讀指針
為了方便理解,我們將其抽象成一個(gè)追逐運(yùn)動(dòng),以讀空為例
先確定讀空的判斷, 讀空判斷是在rd_clk下,讀指針的格雷碼與寫指針同步過來的格雷碼進(jìn)行比較 ,實(shí)際上,讀指針追的是wr_ptr_grr
而wr_ptr_grr
又是寫指針留在原地的殘影(寫指針經(jīng)過兩個(gè)讀時(shí)鐘周期后得來的),在兩個(gè)讀時(shí)鐘周期的這段時(shí)間,寫指針可能會(huì)原地不動(dòng),也可能繼續(xù)前行,這便是虛空的概念。
評(píng)論
查看更多