畢業專題要加速 SLH-DSA。profiling 出來的結果毫無懸念:絕大部分時間都在 SHAKE256,再往下拆就是 Keccak-f[1600] 這個 permutation。
問題是,怎麼加速。
兩條路
A:獨立加速器。 做一顆 Keccak 專用的 IP,掛在匯流排上。CPU 把 state 寫過去,啟動,等它算完,再把結果讀回來。
B:擴充指令集。 在 CPU 核心裡面加暫存器與運算單元,用新的指令直接操作。
A 比較好做。IP 可以獨立驗證,介面單純,不用動到 CPU 的 datapath——動 CPU 的 datapath 意味著你要重新理解別人的核心,而 CV32E40P 不是一份小的 codebase。
我們選了 B。
理由是資料搬移,不是運算
關鍵在於呼叫的頻率。
SLH-DSA 一次簽章要呼叫 Keccak 幾萬次。每一次呼叫如果都要:
把 1600 bits 寫到加速器
啟動
輪詢或等中斷
把 1600 bits 讀回來
那麼真正在算的時間,會被匯流排往返的時間淹沒。加速器本身再快都沒用——你優化的是分母裡比較小的那一項。
指令集擴充讓 state 直接住在 CPU 的暫存器裡。沒有搬移,沒有握手,沒有輪詢。
我們加了什麼
- 320-bit 的 SIMD 暫存器與 ALU。這個寬度不是隨便挑的,它對應 Keccak state 的一個 lane plane(5 × 64 bits)。
xor3v:三輸入 XOR。theta 步驟大量出現連續的 XOR,把它折成一道指令能省下可觀的指令數。lv/sv:寬向量的載入與儲存。
數字
Keccak-f[1600] 本身拿到 ×34.76,SHAKE256 ×23.95。
但端到端的 SLH-DSA 簽章/驗證只有最高 ×13.95。
這個落差不是失敗,是 Amdahl’s Law。我們只加速了熱點,熱點以外的部分(雜湊樹的走訪、記憶體操作、控制流程)一點都沒動。當熱點被壓縮到夠小,剩下的部分就開始主導總時間。
如果要再往上推,下一步不是把 Keccak 做得更快——那已經沒什麼油水了——而是去看第二熱點是誰。
代價
誠實地說,B 這條路的代價是真的:
- 你必須讀懂別人的 CPU 核心,包括它的 pipeline、hazard 處理、以及 decoder 怎麼擴充。
- 工具鏈要跟著改。編譯器不認識你的新指令,一開始只能用 inline assembly。
- 驗證變複雜。你改的是核心本身,所以原本跑得過的所有東西都要重跑一次。
如果只是想要一個能動的 demo,A 更划算。但如果目標是「這個設計在真實的 IoT 場景裡有意義」,那資料搬移就是那個非解不可的問題。