功能模擬全綠,波形漂亮得像教科書。合成完,跑 post-synthesis 模擬——輸出開始出現不該有的值。
這篇是那兩週的紀錄。
症狀
模組是一個 FIFO arbiter,多個 requester 搶同一條輸出通道。行為層模擬(behavioral simulation)下,仲裁邏輯完全正確;換成後合成的 gate-level netlist,某些 cycle 的 grant 訊號會短暫地同時拉高兩條。
短暫。這個詞是關鍵。
我一開始找錯方向
前三天我都在讀 RTL。反覆確認 one-hot 編碼、確認 priority encoder 沒寫錯、確認 reset 有沒有漏接。全部都對。
因為我心裡的預設是:「模擬是對的,那 netlist 應該也是對的,所以錯的一定是我寫的邏輯。」
這個預設本身就是 bug。
真正的差異
行為層模擬裡,賦值幾乎是瞬間發生的;gate-level netlist 裡,每一個閘都有延遲,而且不同路徑的延遲不一樣。
我的組合邏輯有兩條路徑會收斂到同一個 grant 訊號。在行為層,兩條路徑同時更新,中間狀態不存在。在 gate level,快的那條先到,慢的那條晚幾百皮秒才到——中間那段時間,輸出處在一個我從來沒有想過會存在的狀態。
也就是 glitch。
它在功能上「不存在」,因為下一個時脈邊緣它就穩定了。但如果下游有東西是位準敏感(level-sensitive)而不是邊緣敏感(edge-sensitive)的,它就會被抓到。
修法
修法本身很無聊:把那段組合邏輯後面加一級暫存器,讓 grant 只在時脈邊緣改變。代價是一個 cycle 的延遲,換來不會亂跳的輸出。
我真正學到的
- 「模擬過了」不是一個等級,是好幾個等級。 Behavioral、post-synthesis、post-implementation(帶實際佈線延遲),三個層級測的是不同的東西。跳過中間層直接上板,等於自願放棄一個定位問題的機會。
- 當模擬與實機不合,先問「這兩者的假設差在哪」,而不是「我的邏輯哪裡錯」。 前者收斂得快得多。
- 組合邏輯的中間狀態是真的存在的。 寫 RTL 的時候很容易把它當成不存在。
畢業專題整合到 PYNQ-Z2 的時候,同樣的思路又救了我一次。那次是 BRAM 介面的時序,但問題的形狀一模一樣。