00回顧
前面的文章我們講了亞穩(wěn)態(tài)的產(chǎn)生,是由于觸發(fā)器在工作過程中存在數(shù)據(jù)的建立時間和保持時間。在上升沿觸發(fā)電路中,建立時間就是在時鐘上升沿到來之前,觸發(fā)器數(shù)據(jù)保持穩(wěn)定的最小時間;而保持時間就是在時鐘上升沿到來之后,觸發(fā)器數(shù)據(jù)端數(shù)據(jù)還應(yīng)該保持的最小時間。如果數(shù)據(jù)在時鐘上升沿前后的這個窗口內(nèi)發(fā)生改變,即違反了建立保持時間,會使觸發(fā)器工作在一個不穩(wěn)定的狀態(tài),影響下一級觸發(fā)器,發(fā)生連鎖反應(yīng)使整個系統(tǒng)工作失常。
異步FIFO是一種重要的異步時鐘域的數(shù)據(jù)同步手段,在實際應(yīng)用中非常常見,面試時也作為??碱}型出現(xiàn)在視野中。但是如何設(shè)計一個高可靠性、高速的異步FIFO也是一個難點。
UART項目內(nèi)部雖然只使用了同步FIFO實現(xiàn)數(shù)據(jù)的緩存,但咱們趁熱打鐵,將異步FIFO的原理和設(shè)計中需要關(guān)注的地方一并講透,可以說干貨滿滿,建議收藏~
01異步FIFO結(jié)構(gòu)
異步FIFO架構(gòu)
異步FIFO主要是由 雙端口存儲器 、 寫指針產(chǎn)生邏輯 、讀指針產(chǎn)生邏輯及空滿標志產(chǎn)生邏輯4部分組成。讀寫操作是由兩個完全不同時鐘域的時鐘所控制。在寫時鐘域部分,由寫指針產(chǎn)生邏輯生成寫端口所需要的寫地址和寫控制信號;在讀時鐘域部分,由讀指針產(chǎn)生邏輯生成讀端口所需要的讀地址和讀控制信號;在空滿標志產(chǎn)生部分,通常是把寫指針與讀指針相互比較產(chǎn)生空滿標志。讀寫時鐘屬于不同的時鐘域,如何同步異步信號,使觸發(fā)器不產(chǎn)生亞穩(wěn)態(tài)及如何正確地設(shè)計空、滿信號的控制電路,使FIFO不會溢出,造成數(shù)據(jù)丟失是異步FIFO設(shè)計的兩個難點。
指針實現(xiàn)方式
- 使用二進制方式實現(xiàn)指針
以寫指針為例。在寫請求有效時,寫指針在時鐘上升沿來時遞增。同樣在讀請求有效時,讀指針在讀時鐘沿來時遞增。在產(chǎn)生空滿信號時,需要比較讀寫指針,由于兩個指針分屬于不同的時鐘域,彼此之間異步,在使用二進制計數(shù)器實現(xiàn)指針時, 可能會導(dǎo)致用于比較的指針采樣錯誤 。
比如,二進制的值會從1111變?yōu)?000,這時所有位都會發(fā)生改變。雖然同步以后可以有效避免亞穩(wěn)態(tài),但是仍然可能得到錯誤的采樣值。
從1111到0000轉(zhuǎn)換可能的中間值:
1111 —> 0000
1111 —> 0001
1111 —> 0010
1111 —> 0011
1111 —> 0100
1111 —> 0101
1111 —> 0110
1111 —> 0111
1111 —> 1000
1111 —> 1001
1111 —> 1010
1111 —> 1011
1111 —> 1100
1111 —> 1101
1111 —> 1110
1111 —> 1111
如果同步時鐘沿在1111向0000轉(zhuǎn)換的中間某個位置到來,就可能將四位2進制的任何值采樣同步到新的時鐘域中。而FIFO空滿信號產(chǎn)生使用錯誤的指針將產(chǎn)生誤標志,從而使FIFO滿時沒有正確產(chǎn)生滿標志,導(dǎo)致數(shù)據(jù)丟失;FIFO空時沒有正確產(chǎn)生空標志,導(dǎo)致讀出垃圾數(shù)據(jù)。
所以采用二進制方式實現(xiàn)指針不是最終的方案,建議盡量避免使用二進制計數(shù)產(chǎn)生指針。
** 使用格雷碼方式實現(xiàn)指針
格雷碼(gray)相對于二進制的優(yōu)勢在于:格雷碼 從一個數(shù)變?yōu)橄乱粋€數(shù)時只有一位發(fā)生變化 。
所以格雷碼在轉(zhuǎn)換時最多只會出現(xiàn)一位錯誤。比如從1000變?yōu)?000時,兩級同步器采樣要么為1000(舊值),要么為0000(新值),而不會出現(xiàn)其他的值。這樣就可以避免產(chǎn)生錯誤的空滿標志。
在格雷碼實現(xiàn)FIFO指針時需要注意, 對于寫邏輯部分,產(chǎn)生寫滿信號需要對讀指針(格雷碼)進行同步;對于讀邏輯部分,產(chǎn)生讀空信號需要對寫指針(格雷碼)進行同步 。
有人要問了,使用兩級同步器對指針同步時,不可避免的會產(chǎn)生兩個時鐘的延遲,這個延遲會不會對讀寫數(shù)據(jù)產(chǎn)生?別急,我會掰開來講清楚的。我們先講講二進制和格雷碼的相互轉(zhuǎn)換。
- 兩種格雷碼計數(shù)器
上面我們已經(jīng)講了格雷碼這種編碼方式可以有效的避免絕大部分錯誤。那怎么產(chǎn)生格雷碼編碼方式的讀寫指針呢?別看格雷碼計數(shù)器看起來復(fù)雜,實現(xiàn)起來其實很簡單。這里有兩種方案產(chǎn)生讀寫指針。
方案A:
方案A
步驟1:將格雷碼轉(zhuǎn)換為二進制值;
步驟2:根據(jù)條件遞增二進制值;
步驟3:將二進制值轉(zhuǎn)換為格雷碼;
步驟4:將計數(shù)器的最終格雷碼值保存到寄存器中。方案B:
方案B
步驟1:根據(jù)條件遞增二進制值;
步驟2:將二進制值保存到寄存器中,同時將二進制值轉(zhuǎn)換為格雷碼;
步驟3:將計數(shù)器的最終格雷碼值保存到寄存器中。這兩種方案有什么區(qū)別?
可以發(fā)現(xiàn),方案A的讀寫指針產(chǎn)生必須經(jīng)過相對復(fù)雜的二進制和格雷碼的相互轉(zhuǎn)換邏輯和遞增邏輯,這條組合邏輯極有可能 限制FIFO的最高工作頻率 ,成為關(guān)鍵路徑。
而方案B將二進制值的結(jié)果保存在一組額外的寄存器中,雖然增加了電路的面積(這點面積基本沒有影響),但避免了復(fù)雜的組合邏輯,可以 提高系統(tǒng)工作頻率 。
格雷碼和二進制相互轉(zhuǎn)換
- 格雷碼到二進制的轉(zhuǎn)換
格雷碼轉(zhuǎn)換為二進制的公式為:
對于n位計數(shù)器來說,i
例如當n=4時,對應(yīng)的格雷碼到二進制的轉(zhuǎn)換為:
bin[0] = gray[0] ^ gray[1] ^ gray[2] ^ gray[3]
bin[1] = gray[1] ^ gray[2] ^ gray[3]
bin[2] = gray[2] ^ gray[3]
bin[3] = gray[3]
可以看出,bin[3]是通過將格雷值右移3位得到;bin[2]是通過將格雷值右移2位得到;bin[1]是將格雷值右移1位得到;bin[0]是將格雷值右移0位得到。(右移后需按位異或)
下面是格雷碼到二進制轉(zhuǎn)換的Verilog代碼。
module gray_to_bin(bin,gray);
parameter SIZE=4;
input [SIZE-1:0] gray;
output [SIZE-1:0] bin;
reg [SIZE-1:0] bin;
integer i;
always @(*) begin
for(i=0;i<=SIZE;i=i+1)
bin = ^(gray >>i);
end
endmodule
- 二進制到格雷碼的轉(zhuǎn)換
二進制到格雷碼的轉(zhuǎn)換公式為:
同樣,對于n位計數(shù)器來說,i
例如當n=4時,對應(yīng)的二進制到格雷碼的轉(zhuǎn)換為:
gray[0] = bin[0] ^ bin[1]
gray[1] = bin[1] ^ bin[2]
gray[2] = bin[2] ^ bin[3]
gray[3] = bin[3]
可以看出,可以通過逐位異或,或者將二進制碼右移后與自身異或的操作,計算出對應(yīng)的格雷碼。
下面是二進制到格雷碼轉(zhuǎn)換的Verilog代碼:
module bin_to_gray(bin,gray);
parameter SIZE=4;
input [SIZE-1:0] bin;
output [SIZE-1:0] gray;
assign gray = (bin >>1) ^ bin;
endmodule
格雷碼計數(shù)器實現(xiàn)
本格雷碼計數(shù)器采用方案B實現(xiàn),可有效提高FIFO的最大操作頻率。所以不會涉及到格雷碼到二進制的轉(zhuǎn)換。
根據(jù)上文中格雷碼和二進制碼的特點,細心的同學(xué)可以發(fā)現(xiàn),格雷碼和二進制碼的最高位是相同的,所以只需對低三位進行格雷碼的轉(zhuǎn)換。以寫指針為例,具體實現(xiàn)方式如下:
parameter SIZE=4;
reg [SIZE:0] wbin,wbnext;
reg [SIZE:0] wptr,wgnext;
always @(posedge wclk ornegedge rst_n) begin
if(!rst_n) begin
wbin <= 'h0;
wptr[SIZE-1:0] <= 'h0;
end
elsebegin
wbin <= wbnext;
wptr[SIZE-1:0] <= wgnext[SIZE-1:0];
end
end
always @(*) wptr[SIZE] = wbin[SIZE];
assign wbnext = !wfull ? wbin+winc : wbin;
assign wgnext[SIZE-1:0] = (wbnext[SIZE-1:0] >>1) ^ wbnext[SIZE-1:0];
指針同步后的影響
在FIFO中,F(xiàn)IFO寫滿時不應(yīng)該再向FIFO中寫數(shù)據(jù),避免造成FIFO溢出,導(dǎo)致數(shù)據(jù)丟失。所以在寫時鐘域,需要將寫指針和同步后的讀指針進行比較,產(chǎn)生寫滿標志。下面我將舉例說明將讀指針同步到寫時鐘域后對寫滿標志和寫數(shù)據(jù)的影響。
在最初的t0時刻,讀寫指針都為0。隨著后續(xù)數(shù)據(jù)寫入FIFO,寫指針遞增。當?shù)竭_t5時刻時,F(xiàn)IFO寫滿,讀寫指針相等,寫滿信號wfull由0變?yōu)?。
如果在t6時刻發(fā)生了讀操作,由于兩級同步器包含兩個觸發(fā)器,將讀指針同步到寫時鐘域會導(dǎo)致讀指針在兩個寫時鐘后出現(xiàn),寫滿信號wfull在t6和t7時刻都為1,t7時刻后變?yōu)?。這雖然增加了阻止數(shù)據(jù)寫入的周期(2 wclk cycle),但是對數(shù)據(jù)的準確性無任何影響。
空滿邏輯同步后效果
類似的,在FIFO空時也會阻止FIFO的讀操作。
FIFO的讀空信號產(chǎn)生是把寫指針同步到讀時鐘域和讀指針進行比較。
在t10時刻,寫指針等于讀指針,此時FIFO為空。若t11時刻時開始向FIFO寫入數(shù)據(jù),t11,t12時刻雖然FIFO不為空,但是由于兩級同步器的延時,此時讀空信號rempty仍然為1,這會阻止FIFO的讀操作,但這是無害的。在t12時刻后,rempty釋放,此時FIFO可以開始讀操作。
在將滿時通知寫的一邊FIFO已滿,在將空時通知讀的一邊FIFO已空都是可以的,即使同步后的指針存在延時,阻止寫/讀的影響會使FIFO掛起一段時間,但不會導(dǎo)致任何錯誤。
空滿信號產(chǎn)生
空滿標志的產(chǎn)生是異步FIFO設(shè)計的核心。如何正確設(shè)計此部分的邏輯,會直接影響到FIFO的性能??諠M標志的產(chǎn)生原則是: 寫滿不溢出,讀空不多讀 。
最直接的做法是采用讀寫指針相比較來產(chǎn)生空滿標志。當讀寫指針的差值等于一個預(yù)設(shè)值時,空滿信號被置位。這種實現(xiàn)方式邏輯簡單,但是它的減法器形成了一個比較大的組合邏輯。限制了FIFO的速度。
所以一般采用相等或是不相等的比較邏輯,避免使用減法器。采用直接比較的方法必須區(qū)分當讀寫地址相等的時候是空還是滿,所以必須增加額外的1位控制信號來區(qū)分空滿標志。
以16x16異步FIFO為例,寫指針wptr[4:0],讀指針rptr[4:0],其中低三位為真正的讀寫地址,最高位用來區(qū)分空滿。
若讀寫指針完全相等,表示讀地址追上了寫地址,此時FIFO為空;若最高位相反但其余位相同,表示寫地址領(lǐng)先讀地址一個周期,此時FIFO為滿。
讀寫指針比較產(chǎn)生空滿標志rempty和wfull,將寫指針同步到讀時鐘域,產(chǎn)生讀空信號rempty控制異步FIFO的讀操作;將讀指針同步到寫時鐘域,產(chǎn)生寫滿信號wfull控制異步FIFO的寫操作。
空滿信號的Verilog實現(xiàn)如下:
wire wfull;
wire rempty;
reg [SIZE:0] wptr_reg1;
reg [SIZE:0] wptr_reg2;
reg [SIZE:0] rptr_reg1;
reg [SIZE:0] rptr_reg2;
// 寫指針在讀時鐘域的兩級同步
always @(posedge rclk ornegedge rst_n) begin
if(!rst_n) begin
wptr_reg1 <= 'h0;
wptr_reg2 <= 'h0;
end
elsebegin
wptr_reg1 <= wptr;
wptr_reg2 <= wptr_reg1;
end
end
// 讀指針在寫時鐘域的兩級同步
always @(posedge wclk ornegedge rst_n) begin
if(!rst_n) begin
rptr_reg1 <= 'h0;
rptr_reg2 <= 'h0;
end
elsebegin
rptr_reg1 <= rptr;
rptr_reg2 <= rptr_reg1;
end
end
//空滿標志產(chǎn)生
assign wfull = ({!wptr[SIZE],wptr[SIZE-1:0]}==rptr_reg2)? 1'b1 : 1'b0;
assign rempty = (wptr_reg2==rptr)? 1'b1:1'b0;
好了,異步FIFO到這里講完了,大家可以試試自己寫一個異步FIFO,調(diào)整讀寫時鐘的頻率和比例,進行前后仿真看看性能怎樣呢~
-
存儲器
+關(guān)注
關(guān)注
38文章
7525瀏覽量
164158 -
控制電路
+關(guān)注
關(guān)注
82文章
1718瀏覽量
136012 -
觸發(fā)器
+關(guān)注
關(guān)注
14文章
2002瀏覽量
61284 -
FIFO存儲
+關(guān)注
關(guān)注
0文章
103瀏覽量
6024 -
異步時鐘
+關(guān)注
關(guān)注
0文章
17瀏覽量
9418
發(fā)布評論請先 登錄
相關(guān)推薦
評論