今天要談一下, 在 SoC(System-on-Chip) debug 的藝術, 首先我先界定一下何謂 SoC, Wikipedia 對於 "System on a chip" 的解釋是 "A system on a chip or system on chip (SoC or SOC) is an integrated circuit (IC) that integrates all components of a computer or other electronic system into a single chip." 對於在 SoC 上面寫程式與 debug, 我認為最大的特點是:
- 任何地方都有可能會錯
讀者可能會想, 我在任何地方寫程式的特性也是這樣啊, 但是 SoC 還有一個很致命的缺點, 即便是處理器已經驗證過, 都仍然有可能會發生指令集的錯誤, 尤其是組合語言程式設計師每個人的寫法都不一樣, 很有可能會踩到之前沒有驗證過的指令集組合而發生錯誤!
是的, 在 SoC 上寫程式, 驗證工具不管做得再好, 都只是減少錯誤發生的機率而已, 實際上遇到的問題, 還是要有這樣的心態來面對會比較正確。因為在一般的情況下, 我們會把 CPU/DSP 當做已經驗證沒有問題來寫程式, 但是在 SoC 上面, CPU/DSP 非常有可能會因為在新的設計上, 某個或某些組合的 CPU/DSP instruction 在某一級 pipeline 發生問題, 而導致錯誤的結果。即便是大廠如 AMD, 經過了很多的驗證程序之後, 還是有可能出現問題, 如: AMD processor incorrectly updates the stack pointer
在這樣的 SoC 上面寫程式與 debug, 需要有一些藝術與技巧, 筆者就來分享一下我的經驗。筆者分享的是筆者在通訊 SoC 晶片上發展的經驗, 但我想很多部分都可以類推到其他系統晶片上。一般說來, 在這樣的 SoC 上面會出現的錯誤來源有幾個:
- 自己的程式
- CPU/DSP instruction 的邏輯錯誤
- CPU/DSP instruction 的時序錯誤
- IC 硬體區塊的錯誤
- IC 內部 Bus 傳輸錯誤
- IC 與 IC 之間外部 Bus 傳輸錯誤
- 別家晶片上對於通訊訊號或協定上的錯誤
由於通訊基頻系統大部分都含有一顆或多顆 DSP 負責基頻訊號處理, 所以 DSP programmer 常常身負驗證各個 functional block 的責任, 或是在使用的過程中由奇特的現象來發現驗證團隊之前沒有發現到的錯誤。不管錯誤來源有多少, 解錯誤的首要, 就是先把整個系統以及演算法的原理搞清楚, 清楚之後, 就要依據錯誤出現的現象, 猜測問題出現的點, 然後設計實驗來確認錯誤發生的地方。猜測問題發生的點, 依靠的是對於這個系統的直覺, 而這個直覺是透過你對系統的了解與經驗累積而來的。另外, 就是需要重建錯誤發生時所發生的事情。如果有 simulator 可以使用, 那麼常常在 simulator 上就可以觀察到錯誤的前後發生的事情, 但是像是通訊訊號或協定上的錯誤, 則常常需要有時序上的事件紀錄來重建錯誤發生當時的原貌, 或是有時候沒有現成的分析工具, 就要自己寫一些程式來記錄與觀察變數的變化與事件的流程; 而選擇觀察的物件, 也反映了你對於系統的了解程度。
第一個就是要確定自己的程式沒有問題, 程式可能出錯的來源有兩種: 一種是程式設計師自己寫錯, 另一個是 CPU/DSP instructions 出錯導致程式有錯。前者有很多的程式設計相關書籍都有提到如何避免, 以筆者的經驗, 在寫任何 DSP 程式, 特別是組合語言之前, 一定要先想好怎麼測試自己所寫的程式, 以及怎麼樣算是測試成功及失敗。這部分的觀念, 筆者後來發現軟體業界早已經講了很多, 讀者有興趣的話可以參考 TDD(Test Driven Development) 的觀念。TDD 的導入也能夠幫助我們找到第二、三個 CPU/DSP 指令集上的錯誤。
程式出錯另一個來源就是 CPU/DSP 指令集上的錯誤, 可以略分成兩種: 一種是指令集邏輯上的錯誤, 另一種是指令集時序上的錯誤。一般來說如果 CPU/DSP 有 test pattern 的話, 前者邏輯上的錯誤應該都已經抓完, 剩下的時序上的錯誤, 也就是指令前後組合的問題, 由於樣本數太大, 一般都只能隨機測試, 所以後者時序上的錯誤是 DSP programmer 比較容易遇到的地雷。如果 DSP programmer 是用組合語言撰寫程式, 而不是 C 語言, 那麼踩到地雷的機會更大。如果有編譯器的話, 通常編譯器就可以當作是一個測試的程式, 可以抓到某些編譯器 codegen 愛用的組合語言時序組合的 bug, 但是組合語言程式設計師每個人愛用的組合語言寫法都不同, 就很容易在某個人的寫法上就踩到地雷。而時序上的錯誤其實也是比較難抓的問題, 通常只能依靠 DSP programmer 在使用上的時候, 發現某些奇特的現象, 把搜尋範圍縮小之後才能夠找到。上一節講的 TDD 就能夠幫助 programmer 找到那些奇特的現象在哪裡。另外, 一般來說, 通訊 SoC 的計畫時程短則三年, 長則五年以上, 要求把整本 DSP 架構讀得滾瓜爛熟不也是很合理的嗎? 對於 CPU/DSP 架構的熟悉程度, 也會幫助你可以很快的找到指令時序上的錯誤。
前三項都確定沒問題之後, 接下來要排除的錯誤才是困難的開始, 在此, 筆者建議大家一定要維持寫實驗記錄或是工作記錄的習慣, 筆者常常遇到一解就是一兩週的 bug, 常常解到後來需要與一兩週之前的結果一起比較才能判斷錯誤在哪裡, 因此工作記錄非常重要, 而且有了工作記錄之後, 每天回家才不會心裡一直掛念這件事。要排除這邊的錯誤, 通常是已經有一個錯誤發生了, 這個錯誤也許要做很多次才能重複, 但是一定要確定如何能夠重複這個錯誤, 這點很重要, 因為接下來你會需要一直讓這個錯誤發生才能夠 debug. 讓錯誤發生是一個「點」, 要找出錯誤發生真正的原因(root cause), 常常需要其他的「點」, 讓你連成數條線才能夠把搜尋範圍變小找到真正發生的原因。
找 root cause 的過程, 我個人覺得就像是 binary search 一樣, 不斷的找一個點做判斷, 決定要往左跑, 還是往右跑。當然, 有的時候還是會判斷錯誤, 跑錯方向, 所以就要回溯到當初的切割點, 知道當時的實驗資料是什麼, 為什麼做這樣的判斷, 才能對整個問題有更全面性的了解。這時候仰賴的還是當初的實驗記錄, 所以實驗記錄很重要吧!
除了解單一錯誤以外, 有的時候會需要解多重錯誤。筆者碰觸到的大部分是通訊或訊號處理的問題, 穩定度很重要, 多重錯誤常常會表現在穩定度上, 所以我們常常要記錄錯誤率, 才能知道自己是否已經解完所有的錯誤, 或是剛剛解掉的那個錯誤是否佔的比例最大。另外, 我覺得在這樣的系統上 debug, 還要「大膽的假設,小心的求證」, 有的時候, 我們需要處理的是通訊 physical layer 或是 protocol layer 的問題, 需要猜測對方怎麼做, 這時候就需要大膽的假設, 然後小心的求證。有的時候, 錯誤的可能是另一家公司的晶片, 因此「大膽的假設,小心的求證」是必要的。在這個情況下, 考驗的就是你對整個通訊系統以及原理的掌握程度了。另外, 晶片內部的錯誤, 也是需要「大膽的假設,小心的求證」, 因為這時候, 能夠觀察的工具變少了, 有時候需要猜測。還有, 對已知事物的掌握程度, 也會影響到猜測的精準度, 比如說各個硬體的規格, 使用到的演算法的原理, 通訊 physical layer or protocl layer 的原理, 通訊系統的規格, CPU/DSP instruction set, 等等, 都能夠提昇猜測的效率。對已知事物掌握程度高, 才能夠專心的面對未知的事物。
在此, 筆者試圖將我的經驗整理成幾點原則:
- 先想清楚 test plan
- 熟悉 CPU/DSP 架構
- 熟悉待測物的功能、規格、演算法
- 確保知道如何重複產生 bug
- 知道 bug 發生點之後, 嘗試往前一級測試問題
- 紀錄所有測試數據以及判斷的依據, 以便回頭仔細檢視
身為一個使用組合語言的通訊 DSP 程式設計師, 除了通訊演算法以外, debug 大概就佔掉絕大多數的時間, 真正寫 code 反而是這些工作裡面佔比例最少的。因此, debug 的效率與正確性, 對於程式甚至是晶片的品質, 有著決定性的影響。每個人都有每個人的方法, 筆者在這一篇文章裡, 試圖分享我個人的方法與經驗, 希望能對即將跨入或剛剛跨入這一個領域的工程師有點幫助, 而在這個領域很有經驗的工程師也歡迎大家一起交流心得。
--
No comments:
Post a Comment