態(tài)到異步FIFO的工程實踐)
面試FPGA崗位不管面的是通信、圖像還是控制方向有一道題幾乎是躲不掉的跨時鐘域。我面過的崗位里十個面試官有九個會從CDCClock Domain Crossing切入剩下一個不直接問但會在你做項目描述的時候突然插一句“你這個模塊兩個時鐘之間傳數(shù)據(jù)怎么保證不出錯”。為什么這道題這么受偏愛因為CDC問題在真實項目里太容易踩坑了。異步FIFO、寄存器打拍、握手協(xié)議、脈沖同步——這些名詞背下來不難難的是面試官追問“為什么用這個方案”“這個方案有什么代價”“兩個時鐘頻率差一倍怎么辦”的時候你能不能把背后的原理和工程取舍講清楚。這篇內(nèi)容我就按我在實際項目里處理跨時鐘域的思路來拆從最底層的亞穩(wěn)態(tài)講起到單bit同步、多bit同步、異步FIFO、握手協(xié)議最后面到那些高頻追問點怎么回答。內(nèi)容既可以當面試資料背也可以直接指導(dǎo)你手里的實際設(shè)計。1. 面試官為什么揪著跨時鐘域不放亞穩(wěn)態(tài)的本源跨時鐘域這個問題的關(guān)鍵詞不是“時鐘域”而是“亞穩(wěn)態(tài)”。如果只是兩個時鐘域之間傳數(shù)據(jù)設(shè)計方案一大堆但所有方案本質(zhì)上都在解決同一個問題數(shù)據(jù)在跨越時鐘邊界的時候能不能穩(wěn)定地被采樣到。1.1 亞穩(wěn)態(tài)到底是怎么產(chǎn)生的觸發(fā)器的時序特性大家都知道建立時間setup time和保持時間hold time。數(shù)據(jù)在時鐘有效沿到來之前必須穩(wěn)定一段時間在有效沿之后也必須再穩(wěn)定一段時間這樣觸發(fā)器輸出的Q端才會是確定性的0或1。問題在于跨時鐘域的源寄存器是A時鐘驅(qū)動的目的寄存器是B時鐘驅(qū)動的。兩個時鐘的相位關(guān)系是未知的B時鐘的采樣沿什么時候到來和A時鐘的輸出什么時候變化兩者之間完全隨機。這就導(dǎo)致B時鐘域的那個觸發(fā)器可能正好在輸入數(shù)據(jù)變化窗口內(nèi)采到了數(shù)據(jù)。這個采樣窗口有多窄TSMC 28nm工藝下一個標準D觸發(fā)器的建立時間大約在幾十皮秒到上百皮秒的量級。也就是說B時鐘的采樣沿只要落在輸入數(shù)據(jù)變化沿附近這幾十皮秒的區(qū)間里觸發(fā)器輸出就會進入亞穩(wěn)態(tài)——輸出值既不是明確的0也不是明確的1可能振蕩在中間電平并且這個振蕩會持續(xù)一個不確定的時間通常要經(jīng)過幾個門延遲才能恢復(fù)到穩(wěn)定狀態(tài)。有些新人會問那我把時鐘頻率降下來采樣沿離數(shù)據(jù)變化沿遠一點是不是亞穩(wěn)態(tài)就沒了這種想法只在仿真里成立。實際芯片里兩個時鐘域之間沒有明確的相位關(guān)系你沒法保證B時鐘的采樣沿永遠避開數(shù)據(jù)變化窗口。溫度變了、電壓變了、工藝偏差了相位關(guān)系就全變了。所以跨時鐘域的可靠設(shè)計永遠不能寄希望于“時序剛好錯開”這種運氣。1.2 亞穩(wěn)態(tài)傳播的三類典型危害面試的時候不能只說出“亞穩(wěn)態(tài)會產(chǎn)生”這半句話還得說清楚亞穩(wěn)態(tài)到底會造成什么后果這才顯得你是真做過設(shè)計的。第一類是邏輯錯誤。亞穩(wěn)態(tài)的輸出被后面的組合邏輯繼續(xù)傳播可能讓后級多個觸發(fā)器采到不一致的值。比如數(shù)據(jù)為0和1之間振蕩的時候有的后級觸發(fā)器采到0有的采到1整個狀態(tài)機就跑飛了。第二類是數(shù)據(jù)錯誤但功能還能恢復(fù)。數(shù)據(jù)采錯了一個bit但這拍數(shù)據(jù)本來就允許偶發(fā)錯誤比如一些視頻數(shù)據(jù)流的控制信號錯了下一幀就恢復(fù)了。這種相對能容忍。第三類是最危險的——亞穩(wěn)態(tài)通過組合邏輯風(fēng)扇-out擴散導(dǎo)致大面積的時序邏輯狀態(tài)混亂。一個亞穩(wěn)態(tài)信號輸入到狀態(tài)機的組合邏輯里可能讓狀態(tài)機同時進入多個狀態(tài)復(fù)位都未必能拉回來。這種情況在芯片里就是功能失效嚴重的直接死機。我實際調(diào)試的時候遇到過SPI接口偶發(fā)數(shù)據(jù)錯亂最后定位就是CS信號跨時鐘域沒處理干凈把狀態(tài)機帶飛了。面試官想要的答案是亞穩(wěn)態(tài)的本質(zhì)是觸發(fā)器的時序約束被違反輸出進入不確定狀態(tài)且這個不確定狀態(tài)會向外傳播所以跨時鐘域處理的核心目標就是把進入亞穩(wěn)態(tài)的概率壓到工程可接受的水平并且阻斷亞穩(wěn)態(tài)向外傳播的路徑。2. 單bit跨時鐘兩級同步器背后的門道從最簡單的設(shè)計方案開始兩級觸發(fā)器同步器two-flop synchronizer。這也是面試官最愛讓你現(xiàn)場寫的一段代碼。// 兩級同步器用于單bit信號跨時鐘域 module sync_2ff #( parameter STAGES 2 )( input wire clk, // 目的時鐘域時鐘 input wire rst_n, // 目的時鐘域異步復(fù)位 input wire din, // 來自源時鐘域的異步信號 output wire dout // 同步后的信號 ); reg [STAGES-1:0] sync_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sync_reg b0; end else begin sync_reg {sync_reg[STAGES-2:0], din}; end end assign dout sync_reg[STAGES-1]; endmodule這段代碼基本上每個做FPGA的人都能默寫出來但面試官真正想考察的是下面幾個點。2.1 兩級打拍為什么能解決亞穩(wěn)態(tài)——概率攤薄而非消除這里必須糾正一個常見誤解兩級同步器不是把亞穩(wěn)態(tài)消除了而是把亞穩(wěn)態(tài)傳播到后級邏輯的概率攤薄到忽略不計。第一級觸發(fā)器采樣跨時鐘域信號時確實可能進入亞穩(wěn)態(tài)輸出端要花一段時間才能穩(wěn)定。在它穩(wěn)定的這段窗口里第二級觸發(fā)器的時鐘沿到來理論上仍然有可能采到第一級還沒穩(wěn)定的輸出從而再次進入亞穩(wěn)態(tài)。也就是說兩級同步器的輸出端依然存在進入亞穩(wěn)態(tài)的概率只是概率被急劇縮小了??s小了多少這就涉及到MTBFMean Time Between Failures。一個兩級同步器的MTBF估算公式可以簡化為[ MTBF \frac{e^{t_r/\tau}}{T_0 \cdot f_{clk} \cdot f_{data}} ]其中t_r是提供給第一級觸發(fā)器從亞穩(wěn)態(tài)恢復(fù)到穩(wěn)定態(tài)的時間等于兩級觸發(fā)器之間的時鐘周期減去第二級的setup時間τ和T_0是工藝相關(guān)的亞穩(wěn)態(tài)參數(shù)f_clk是采樣時鐘頻率f_data是異步信號變化頻率。我拿實際工藝參數(shù)舉例采樣時鐘100MHz異步信號變化頻率10MHz第一級到第二級的恢復(fù)時間按10ns算τ取50ps這個量級的話算出來的MTBF是以萬年為單位的。這個可靠性已經(jīng)足夠覆蓋絕大多數(shù)產(chǎn)品生命周期了。但注意如果不打這兩拍直接把異步信號進狀態(tài)機MTBF可能縮短到微秒量級——你的系統(tǒng)平均每微秒就崩一次這完全沒法用。所以兩級同步器的本質(zhì)是用兩個周期的延遲代價把“采樣失敗導(dǎo)致系統(tǒng)崩潰”的概率從“隨時可能發(fā)生”壓到“理論上幾乎不會發(fā)生”。2.2 幾級最適合以及什么時候必須加第三級標準答案是兩級。一級不夠因為亞穩(wěn)態(tài)還沒恢復(fù)就被后級直接用三級及以上沒必要因為兩級帶來的延遲已經(jīng)夠大再加級數(shù)對可靠性改善微乎其微只會白白增加延遲和資源。但也有例外。如果目的時鐘域頻率很低比如只有幾MHz而源時鐘域頻率很高異步信號變化又快那兩級觸發(fā)器之間的可用恢復(fù)時間會非常長——正常來說這反而是好事MTBF會更大。但如果你的目的時鐘域頻率高到幾百MHz兩級之間只有一個周期恢復(fù)時間被壓縮得很短MTBF可能跌破可接受范圍。這時候就需要考慮插三級或者壓根不要用這種方式跨改用下面的脈沖同步方案。還有一種必須加第三級的場景異步信號進入的是高扇出網(wǎng)絡(luò)。比如一個異步復(fù)位信號要控制幾百個觸發(fā)器你只打了兩拍就接出去第一級亞穩(wěn)態(tài)恢復(fù)后后面那一大堆觸發(fā)器的時鐘邊沿到達時間各不相同扇出大的話有的在信號還沒穩(wěn)定時就已經(jīng)采過了。項目里面有經(jīng)驗的做法是打兩拍后再接一段緩沖器樹或者至少在扇出路徑上再多插一兩級寄存器。面試時能說出這個點面試官基本會認為你踩過這個坑。2.3 快慢時鐘方向和電平保持兩級同步器適用于慢時鐘域到快時鐘域前提是信號的變化寬度要大于目的時鐘域一個以上時鐘周期這樣才能保證被至少采到一拍。如果源時鐘域信號只持續(xù)一個源時鐘周期而目的時鐘域比較慢信號可能整個被漏掉。舉個例子源時鐘100MHz信號只高1個周期10ns目的時鐘50MHz周期20ns。如果B時鐘的采樣沿恰好落在信號有效窗口之外這個脈沖就丟了。所以兩級同步器跨單bit這件事有一個隱性前提信號電平在目的時鐘域的至少一個采樣周期內(nèi)保持穩(wěn)定。面試官問你“快時鐘到慢時鐘怎么跨”答案一般不直接是兩級同步器而是先把快脈沖展寬成電平或者直接用握手、異步FIFO這屬于后面要講的多bit方案范疇。3. 多bit數(shù)據(jù)跨時鐘異步FIFO、格雷碼、握手協(xié)議單bit有單bit的玩法一旦數(shù)據(jù)寬度變成8bit、16bit甚至更寬問題就完全變了。你第一次認真做跨時鐘域數(shù)據(jù)通路的時候八成會被這個問題卡住。3.1 為什么多bit不能每個bit打兩拍這是面試里的經(jīng)典陷阱題。很多人在實際設(shè)計之前會覺得8bit數(shù)據(jù)每個bit用兩級同步器打兩拍不就同步過來了這樣做會出現(xiàn)一個致命問題數(shù)據(jù)一致性的破壞。八個bit變化的時候不可能保證同一時刻翻轉(zhuǎn)。比如數(shù)據(jù)從 01111111 變到 10000000八個bit全部翻轉(zhuǎn)但由于工藝偏差和布線長度不同B時鐘采樣時可能采到 01000000甚至 00100000——每個bit的到達時間不一樣目標寄存器會采到一個“一半新一半舊”的組合而這個組合可能根本不是任何一個合法數(shù)據(jù)。有人會想那我把數(shù)據(jù)在每個源時鐘沿都鎖存一下再發(fā)送沒有用。鎖存解決的是數(shù)據(jù)本身的問題解決不了采樣時刻不一致的問題。只要是多bit數(shù)據(jù)直接跨時鐘域就必然面臨“不同bit到達目的寄存器的時間不一致”這個物理現(xiàn)實。真正的解決方案有三條路異步FIFO、握手協(xié)議、格雷碼。三條路的本質(zhì)要么是讓多個bit變化的時間天然對齊格雷碼只有1bit變化要么是讓“數(shù)據(jù)的有效時刻”和“數(shù)據(jù)本身”分開傳輸通過握手確保接收方采到的數(shù)據(jù)一定是穩(wěn)定的。3.2 異步FIFO的格雷碼跨時鐘和空滿判斷異步FIFO是工程中用得最多的多bit跨時鐘域方案面試也最喜歡考。讓我先說說它的基本架構(gòu)寫時鐘域負責(zé)寫指針遞增和數(shù)據(jù)寫入讀時鐘域負責(zé)讀指針遞增和數(shù)據(jù)讀出指針本身跨時鐘域傳遞。如果直接把二進制指針跨過去多bit變化的問題又回來了所以指針必須用格雷碼編碼。格雷碼的特性是每次遞增只變化1bit。寫指針格雷碼跨到讀時鐘域打兩拍采樣讀指針格雷碼跨到寫時鐘域打兩拍采樣。因為是1bit變化兩級同步器不會出現(xiàn)多bit不一致采樣的風(fēng)險。空滿判斷是整個異步FIFO里最繞的部分。直接比較指針格雷碼最簡單讀指針等于寫指針就是空最高兩位和剩余的位全部相等就是滿。為什么是最高兩位因為格雷碼在最高位翻轉(zhuǎn)時表示指針繞了一圈回到同一區(qū)段最高兩位相等才說明讀寫指針真正重合。但面試官一般不止?jié)M足于你會判斷他更想聽你說清楚空滿信號的產(chǎn)生需要多長時間因為寫滿信號依賴于讀指針跨到寫時鐘域打兩拍這本身就是跨時鐘同步的延遲。判斷空滿的結(jié)果是保守的——滿信號可能延遲一拍到來導(dǎo)致FIFO實際已經(jīng)滿了但滿信號還沒產(chǎn)生這時候繼續(xù)寫就會覆蓋未讀數(shù)據(jù)空信號同理延遲到來可能讓讀端在FIFO已經(jīng)空了之后仍然嘗試讀讀到垃圾數(shù)據(jù)。工程上怎么解決保守判斷即可滿信號稍微延遲沒關(guān)系只要設(shè)置適當?shù)木幊虧M閾值programmable full threshold留出打拍延遲的空間空信號也一樣讀端配合valid信號使用不依賴空信號做精確的時序。面試能說出這些工程細節(jié)說明你真的用異步FIFO做過數(shù)據(jù)通路。3.3 握手協(xié)議的適用場景和實現(xiàn)異步FIFO適合數(shù)據(jù)連續(xù)流握手協(xié)議適合數(shù)據(jù)包式或低速控制信息的跨時鐘域傳輸。握手協(xié)議的基本思想是發(fā)送方把數(shù)據(jù)放到總線上然后拉高req信號接收方看到req同步過來后鎖存數(shù)據(jù)拉高ack信號發(fā)送方看到ack同步回來撤銷req和總線數(shù)據(jù)接收方看到req撤銷撤下ack。一輪完整的握手完成。手寫實現(xiàn)一個握手跨時鐘域代碼// 握手協(xié)議跨時鐘域傳輸示例為脈沖式鎖存數(shù)據(jù) module handshake_cdc #( parameter DATA_WIDTH 8 )( // 源時鐘域 input wire src_clk, input wire src_rst_n, input wire [DATA_WIDTH-1:0] src_data, input wire src_req, output wire src_ack, // 目的時鐘域 input wire dst_clk, input wire dst_rst_n, output reg [DATA_WIDTH-1:0] dst_data, output reg dst_req );握手方案在真正做的時候有不小的延遲開銷源端看到ack撤掉才能繼續(xù)發(fā)下一筆而ack要跨回源時鐘域所以每筆數(shù)據(jù)的吞吐被同步延遲限制住了。優(yōu)點是結(jié)構(gòu)簡單不需要異步FIFO那樣的存儲空間適合偶爾傳一筆控制命令的場景。在這里我想插一個項目經(jīng)驗握手信號本身也是個單bit跨時鐘域信號需要打兩拍同步。你如果直接拿未同步的req信號用組合邏輯判斷還是會有亞穩(wěn)態(tài)風(fēng)險。所以握手協(xié)議嚴格來說是“單bit同步方案 數(shù)據(jù)鎖存使能”的組合——數(shù)據(jù)總線本身不跨時鐘域打拍而是通過同步后的使能信號在目的時鐘域鎖存。4. 面試高頻追問脈沖同步、注意事項和回答模板這邊把面試中真正容易出現(xiàn)的高頻追問整理一下順便說清每個問題背后的考察意圖。4.1 單脈沖跨時鐘域的同步方案脈沖信號在跨時鐘域時比電平信號更容易丟。剛才已經(jīng)講過兩級同步器對脈沖寬度有要求寬度不足一個目的時鐘周期就漏采。工程上的標準做法是脈沖轉(zhuǎn)電平跨域后再電平轉(zhuǎn)脈沖。源時鐘域一旦檢測到脈沖到來就把一個toggle信號翻轉(zhuǎn)相當于一個標志位花邏輯翻轉(zhuǎn)跨時鐘域打兩拍后目的時鐘域檢測到toggle翻轉(zhuǎn)沿就恢復(fù)出一個目的時鐘域脈沖。為什么要翻轉(zhuǎn)而不是直接用脈沖電平因為翻轉(zhuǎn)沿本身就是“事件發(fā)生”的標志跨一個時鐘域后只要檢測兩次采樣值不同就知道有事件發(fā)生了不存在脈沖被漏掉的問題。這種toggle同步方案唯一的代價是它只能傳遞“有脈沖”這個信息不能傳遞多個脈沖之間的稀疏程度。如果兩個源時鐘脈沖間隔比同步延遲還短toggle連續(xù)翻轉(zhuǎn)兩次目的端可能只檢測到一次沿。面試官問到這你還能補充一句“需要對源脈沖間隔做約束保證兩次脈沖之間至少間隔足夠時間”就非常加分。4.2 跨時鐘域的常見注意事項綜合、約束、復(fù)位跨時鐘域代碼最怕的不是功能仿真沒過而是過了功能仿真但上了板子就隨機出錯。這里有幾個高頻檢查點。第一綜合工具默認認為兩個沒有約束關(guān)系的時鐘之間不存在時序路徑所以跨時鐘域路徑上的觸發(fā)器不會被布局布線工具去做時序收斂也不會報時序違規(guī)。這是好事但前提是你已經(jīng)正確聲明了CDC路徑——通常用set_clock_groups -asynchronous或set_false_path來約束。反過來如果忘了約束工具會在CDC路徑上對著錯的時鐘關(guān)系做時序優(yōu)化浪費大量布局布線資源。第二異步復(fù)位的處理??鐣r鐘域模塊里的異步復(fù)位信號必須做異步復(fù)位同步釋放否則復(fù)位釋放的沿如果恰好和目的時鐘域采樣沿沖突復(fù)位后的寄存器依然會進入亞穩(wěn)態(tài)。異步復(fù)位同步釋放電路本身也是一種CDC處理常見寫法還是兩級同步器只是把復(fù)位信號當成一個特殊的異步信號來對待。第三不要在跨時鐘域路徑上放任何組合邏輯。你看到一個正在做數(shù)據(jù)跨時鐘域的代碼里打著兩拍之后馬上接了加法器然后結(jié)果又打了一拍——這種結(jié)構(gòu)從數(shù)據(jù)正確性到亞穩(wěn)態(tài)傳播都不好??鐣r鐘域的每一個數(shù)據(jù)路徑都要保證源時鐘域鎖存好數(shù)據(jù)信號直接打到目的時鐘域的寄存器輸入中間不插組合邏輯。4.3 從面試到實戰(zhàn)我總結(jié)的幾條閉環(huán)心得最后從面試這個場景抽離出來說說實際項目里怎么把跨時鐘域這件事做干凈。第一先分類再動手。拿到跨時鐘域的需求先問三個問題傳的是單bit還是多bit方向是快到慢還是慢到快數(shù)據(jù)是連續(xù)性流還是偶發(fā)事件這三個問題直接決定方案選擇而不是上來就套異步FIFO。第二做代碼規(guī)范的時候強制貫徹。我在團隊里推的原則是每個模塊頂層的跨時鐘域信號必須集中在一個文件或一個區(qū)域里管理任何跨時鐘域路徑都要標記注釋CDC_SYNC_2FF、CDC_ASYNC_FIFO這樣的標簽方便review和后端工具處理。第三仿真驗證要專門建模亞穩(wěn)態(tài)。很多人功能仿真做完了就以為跨時鐘域沒問題但普通仿真器的行為級模型里根本沒有亞穩(wěn)態(tài)概念。成熟的驗證環(huán)境會做“異步時鐘相位隨機化”或者直接在RTL模型里注入X態(tài)這樣能提前暴露跨時鐘域邏輯的脆弱點。如果你沒有這個條件至少要在仿真時把兩個時鐘的相位偏移用#delay隨機化跑多輪回歸??鐣r鐘域這塊面試答得好不好往往就是面試官判斷你有沒有真正做過高速數(shù)據(jù)通路的一個分水嶺。能把亞穩(wěn)態(tài)講透、能把方案選型的代價講清、能說出幾個工程上的注意點基本就讓面試官心里有底了。