在《時鐘與復位》一文中已經解釋了亞穩態的含義以及亞穩態存在的危害。在單時鐘系統中,亞穩態出現的概率非常低,采用同步設計基本可以規避風險。但在實際應用中,一個系統往往包含多個時鐘,且許多時鐘之間沒有固定的相位關系,即所謂的異步時鐘域,這就給設計帶來很大的挑戰。
01跨時鐘域類型
跨時鐘域傳輸可分為以下幾種情況:
- 同頻零相位差時鐘
- 同頻恒定相位差時鐘
- 非同頻時鐘
- 整數倍頻時鐘
- 非整數倍頻時鐘
同頻同相時鐘可視為等效的時鐘,在不考慮時鐘抖動(Jitter)影響的情況下,兩個時鐘域之間的傳輸可視作同步的。而同頻恒定相位差的時鐘由于時鐘有效沿之間有一定的相位差,因此對建立/保持時間裕量有一定的影響。設計時需要確保組合邏輯的延時能滿足采樣處的建立/保持時間要求,一般可通過在發射沿或捕獲沿加入適當的偏移來完成時序的調整。
而對于非同頻的時鐘,二者可以是同步關系,也可以是異步關系,這里再次強調一下同步與異步的界定標準:有固定相位關系的稱為同步時鐘,沒有固定相位關系的稱為異步時鐘。
那么什么叫固定的相位關系?即兩個時鐘的有效沿之間的偏移量始終保持恒定,也即所謂的相位差固定。
非同頻時鐘之間的關系可以分為整數倍頻和非整數倍頻。整數倍頻的時鐘往往是同源時鐘,相位關系也往往是固定的。但是存在源時鐘域和目標時鐘域的快慢關系。假設源時鐘是20MHz,而目標時鐘域只有5MHz,那么就是快時鐘域到慢時鐘域的傳輸。
“慢采快”會導致數據丟失的問題,為了避免這個問題,要求源時鐘域的數據至少保持一個目標時鐘域的周期的穩定狀態。對于單比特控制信號的“快→慢”傳輸,可以采用如圖所示的脈沖同步器來檢測快時鐘域的脈沖信號。
一旦data1有脈沖拉高,經過mux的反饋,第一級DFF的輸出就會反轉并鎖住,直到下一個data1脈沖的到來才會再次反轉。中間兩級DFF就是用來打兩拍消除亞穩態的,最后一級DFF輸出的異或用來檢測同步過來的信號是否發生反轉,由于data1每次脈沖都會導致clk1時鐘域的輸出發生反轉,于是該脈沖信號就在clk2時鐘域被記錄下來并保持一個周期的穩定狀態。
那么假如源時鐘比目標時鐘要慢呢?在“快采慢”的情況下,需要注意快時鐘域觸發器不要對慢時鐘域的輸出信號進行重復采樣,在慢時鐘域維持一個周期的信號在慢時鐘域可能維持好幾個周期,這顯然會導致邏輯功能的錯誤。因此需要確保同步過來的信號在快時鐘域也只維持一個周期,因此采樣如圖所示的邊沿(上升沿)同步電路,就能將慢時鐘域的脈沖同步到快時鐘域。
(此法只用于慢時鐘域的單周期脈沖信號的同步,若源信號存在維持幾個周期的高電平,在快時鐘域也只會存在一個周期的脈沖信號,此時用基本的電平同步電路就可以了)
非整數倍頻的時鐘的之間相位關系總是在變化,這讓時序分析變得很復雜,除了亞穩態的問題,還要考慮相位差的變化是否會導致丟數據,即便能滿足時序,數據傳輸不連貫的問題也始終存在。
此時,已經標準化的技術,如FIFO和握手,解決了絕大多數情況下的跨時鐘域數據傳輸。
02FIFO
關于握手機制,筆者在《解剖AXI(一)—— 從握手開始》一文中已有說明,本文不再過多贅述。在實際設計中,使用握手機制來傳輸單比特信號產生的延遲要遠大于使用FIFO進行傳輸,因此本文重點介紹FIFO傳輸的機制,以及同步FIFO和異步FIFO的架構。
FIFO,即First In First Out,先進先出的數據緩存器??深惐葹榕抨?,先進隊伍的先服務,先寫入FIFO的數據先讀出。所以FIFO沒有外部地址線,只能靠回滾遞增的內部讀寫指針來指示下一個要讀/寫的地址。根據讀寫控制信號的時鐘關系,又可分為同步FIFO和異步FIFO。
2.1 同步FIFO
同步FIFO的寫時鐘和讀時鐘為同步時鐘,一般情況下為同一個時鐘。同步FIFO內的所有邏輯都是同步邏輯。一個最簡單的同步FIFO框圖如下所示。
產生FIFO空滿信號的方法有兩種:
一是在FIFO內部設置一個計數器來指示當前FIFO內部緩存了多少個數據,但這會引入新的寄存器造成額外的資源開銷;
二是通過拓寬讀寫指針的位數,然后根據讀寫指針的關系來判斷空滿。
當讀指針等于寫指針時,標明FIFO處于空狀態,如圖所示,假設FIFO深度為8,那么讀寫地址的位寬取3,讀寫指針在此基礎上拓寬一位:
當FIFO內有數據寫入時,寫指針增加,此時讀指針的低N-1位不等于寫指針的低N-1位(N為指針拓寬一位之后的位寬),如圖所示:
如果此時再讀出兩個數據,FIFO又將回到空狀態,此時讀指針與寫指針又相等:
假如此時,一口氣寫入8個數據,寫指針+8,變成4'b1010,FIFO處于滿狀態,此時讀寫指針的最高位不同,但低N-1位相同:
于是可以總結得到FIFO空滿信號的生成條件:
- fifo_empty_o:
assign fifo_empty_o = (wr_ptr == rd_ptr);
- fifo_full_o:
assign fifo_full_o = ((wr_ptr[$clog2(DEPTH)] != rd_ptr[$clog2(DEPTH)]) && (wr_addr == rd_addr));
2.2 異步FIFO
同步FIFO可用于在同步時鐘域傳輸多比特信號,但若要實現兩個異步時鐘域之間的數據傳輸,就要使用異步FIFO。
異步FIFO相較于同步FIFO最大的不同在于讀/寫時鐘域是異步的,也就是說讀寫控制信號是由不同的時鐘驅動的,這就導致讀指針需要同步到寫時鐘域,才能判斷FIFO是否滿;寫指針也需要同步到讀時鐘域,才能判斷FIFO是否空。
異步FIFO的框圖如下:
但是對于多比特信號的跨時鐘域同步,存在一個嚴重的問題。如果像同步FIFO那樣使用二進制的讀寫指針,指針每次加一都可能使好幾位發生翻轉,以三位指針為例,假設當前指針為3'b111下一次操作后指針加1變為3'b000.
我們已經知道,單比特信號打兩拍同步之后的結果可能恢復成0或1的其中一個穩態,假如該單比特信號是從0→1,最終哪怕同步結果是0,也不影響整個電路的功能,只當是沒有發生這次操作,在后續的運行過程中再把正確信號同步過來也是ok的。
但是對于多比特信號而言,多位發生翻轉,其中的每一位都可能同步為不同的結果,也就是說3'b111→3'b000可能變成3'b111→3'b010或者3'b111→3'b110、3'b111→3'b001、3'b111→3'b101之類的任意值,這就明顯違背了指針的正常邏輯,破壞了電路的功能。
我們希求的效果是3'b111→3'b000,哪怕同步結果不是3'b000,也得是3’b111保持不變。因此要求指針遞增每次只變化一位。
剛好,格雷碼的編碼方式就是相鄰兩個數值只有一位不同。因此在異步FIFO設計中,跨時鐘域同步讀寫指針之前,要將二進制指針先轉換成格雷碼編碼的指針,再進行同步。
二進制和格雷碼的互相轉化邏輯,這里不再贅述,只展示Verilog代碼以供參考:
bin2gray:
module bin2gray
#(
parameter WIDTH = 8
)
(
input [WIDTH-1:0] bin,
output [WIDTH-1:0] gray
);
assign gray = bin ^ {1'b0,bin[WIDTH-1:1]};
endmodule
gray2bin:
module gray2bin
#(
parameter WIDTH = 8
)
(
input [WIDTH-1:0] gray,
output [WIDTH-1:0] bin
);
genvar i;
generate
for(i=0;i< WIDTH;i=i+1)begin
assign bin[i] = ^gray[WIDTH-1:i];
end
endgenerate
endmodule
相應的,用格雷碼編碼的指針來判斷異步FIFO的空滿狀態:
- fifo_empty_o:
assign empty = (rd_ptr_gray == wr_ptr_gray_sync_r2);
- fifo_full_o:
assign full = ({~wr_ptr_gray[ADDR_WIDTH:ADDR_WIDTH-1],wr_ptr_gray[ADDR_WIDTH-2:0]} == rd_ptr_gray_sync_r2);
-
fifo
+關注
關注
3文章
389瀏覽量
43771 -
時鐘
+關注
關注
11文章
1740瀏覽量
131632 -
時鐘域
+關注
關注
0文章
52瀏覽量
9545 -
異步FIFO
+關注
關注
0文章
20瀏覽量
8392 -
同步FIFO
+關注
關注
0文章
5瀏覽量
5377
發布評論請先 登錄
相關推薦
評論