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