這篇是叫 Claude 從我的筆記整理出來,事情真的發生過,字是他打的,連語氣也是叫他學我的,我只負責瞄一下的喔。
板子不講話的時候,先確定你聽得到
先講這是什麼東西
Tang Console,Sipeed 出的一塊 FPGA 板子,上面是一顆 138K 的 Gowin 晶片,算蠻大的。前幾篇講的那台跑 Linux 的 terminal 就是在它上面。這篇是更前面的事,九月初剛拿到板子,第一步是在上面拼一顆小小的 RISC-V,叫 picorv32,讓它從 UART 印一行字出來。就這樣而已耶。
板子上有一顆 debugger 晶片,接 USB 之後電腦這邊就看到 JTAG 跟一個 serial port,燒 bitstream 跟看 UART 都走它。很方便,照理說。
症狀:DONE 亮了,就是不講話
bitstream 燒進去,DONE 燈亮,表示 FPGA 有收到設定。然後 serial port 開著,什麼都沒有。一個字都沒有。
這樣卡了好幾個禮拜。改設計、換 core、改 clock、改 UART 的 baud,每一個版本都一樣安靜。安靜的問題最討厭的地方是它沒有訊息:你分不出來是「設計壞了所以沒在講」還是「設計好好的在講,只是我們聽不到」。這兩個可能性每次都一起出現,每次都沒辦法排除其中一個。
後來才知道,是兩個 bug 疊在一起。一個是聽不到,一個是沒在講。所以不管修哪一邊,看起來都沒有動。
一顆一顆驗
既然猜不出來,那就每一樣東西都量一次:
- configuration 有進去:usercode register 讀回來是對的
- fabric 有在跑:拿一個 LED 閃給你看的設計,閃
- 50 MHz 的 clock 是對的:量過
- 從 flash 開機也可以
- UART 收的方向是好的:從電腦送字進去,FPGA 收得到
- U15 這根腳,就是接到 debugger 的 UART TX 那根,FPGA 真的有在推:從它自己的 iobuf 讀回來,有在動
然後 debugger 的韌體重刷過,hash 對過。USB 線換過,port 換過,電源換過,連電腦都換過一台。還是零。
一個 bitstream,兩根腳
最後是這樣抓到的。寫一個設計,同一個 register 的輸出同時接到兩根腳:一根是 U15,板上 debugger 那條;另一根是 pmod 排針上隨便一根,外面接一個 USB 轉 serial 的小板子。然後讓它一直送 0x55。
| 腳 | 收到 |
|---|---|
| U15,板上 debugger | 0 bytes |
| pmod 那根,外接 serial | 36046 bytes,7199 b/s,預期是 7200 |
同一個設計、同一個 clock、同一瞬間,一邊完美,一邊零。這就不是程式的問題了,除了那條線本身是斷的,好像想不到別的解釋。
debugger 的韌體查了一下,Sipeed 只發過一版,所以也沒有東西可以升級。那就是硬體了。板上的 UART 從此放棄,改用 pmod 那根加外接的 serial 小板,一路用到現在。
有了會講話的腳,SoC 還是不講話
這下聽得到了,picorv32 還是一個字都不印。不過這次不一樣了,不講話終於是有意義的:它真的沒在講。
Claude 去讀 program counter,發現 0x40 這個位址有抓進來,0x44 從來沒有,可是這兩個是連在一起的指令耶。picorv32 會先多抓一條,branch 跳走的時候再丟掉,所以 0x44 沒出現,代表它在 0x40 就跳走了,而它不應該跳。
原因是這樣。picorv32 讀 register file 是用組合邏輯的,意思是這個 cycle 讀,這個 cycle 就要拿到。可是 Gowin 的工具看到一個 32 個 32-bit 的陣列,就很貼心的幫你放進 block RAM。block RAM 讀出來要等下一個 cycle。所以每一次讀 register,拿到的都是上一拍的值。
所以它 fetch 是對的,store 是對的,從 bus 上讀到的資料也是對的,然後拿舊的 register 去算。表面上的症狀是 uart_putc 卡在一個 tx_busy 的 bit 上一直等,可是同一個 bus 上同一個 cycle 另外接一個 probe 去讀,那個 bit 明明是 0。它看到的是上一拍的 1。
修法是一個 attribute:
(* syn_ramstyle = "registers" *) reg [31:0] cpuregs [0:regfile_size-1];
叫工具不要用 block RAM,用 flip-flop。多了 1024 個 flip-flop,在 138K 的晶片上好像不算什麼。燒進去,selftest 過,讀 clock 剛好 50,000,000 Hz。第一行字終於出來了。
這段最有感的是,工具從頭到尾都說它是健康的喔。0 個 setup violation、0 個 hold violation,fmax 84 MHz 對 50 MHz 的要求綽綽有餘。timing 對不代表邏輯對,它們是兩件事,工具只管前面那件。
後來 U15 又活了一下
這個有點好笑。幾天後把板子上的 SoM 拆下來看了看、再裝回去,U15 居然送出 2582 個 bytes,有史以來第一次。然後再試 20 次,每次 2 秒,全零。同一個 register 接到 pmod 的那根,每次都是完美的 16384。
所以它不是斷掉的線,也不是少焊了什麼,是一個接觸不良的點,剛好碰到了一下。這也解釋了更早的一件事:第一次玩這塊板子的時候,有看過一顆 LED 跟著這條線閃 3 秒亮 3 秒暗,之後就再也沒看過。兩次都是真的,只是那個接點心情不一樣。
Sipeed 的 wiki 上有一條「板上 debugger 的 UART 不能用」,症狀是 baud 變四倍或是亂碼,跟我們這個一個字都沒有的不太一樣。總之板上的那條就讓它去了,pmod 很好用。
路上踩到的
- 6-pin 的轉接頭插在 8-pin 的 pmod 排針上,拔掉再插會差一格,然後看起來就像板子壞了。後來每根腳都改成 open drain,只拉低不推高,插錯一格也不會打架。
- pmod 的腳位不要用猜的,每根腳送一個不一樣的字,看哪個字到了。照「標準」猜了兩次,兩次都錯。
- openfpgaloader 對這顆 GW5A 完全沒有驗證,checksum 那段直接跳過,所以 DONE 燈是它唯一的判斷。真的要確認,讀 usercode register 回來看。
- 燒失敗的時候 FPGA 還留著上一次的設定,所以你在測的是上一個版本,而且它看起來很成功。
下次怎麼看
- 板子不講話,先把「沒在講」跟「聽不到」分開。最快的方法是同一個 register 推兩根腳,一根走原本的路,一根走別的路。
- 每一個版本都安靜,那是板子的證據,不是設計的。
- 工具說 0 個 violation,只代表 timing 過了,邏輯對不對它不管。
- core 用組合邏輯讀的陣列,小心被工具推成 block RAM,讀出來會慢一拍。
- 一條線曾經通過一次又死掉,那是接觸不良,不是設計的問題,可以不用再查設計了。
一根腳有字一根腳沒有,吵了好幾個禮拜的架,原來是兩個 bug 在輪流擋喔