QEMU: 使用 Decodetree 新增 RISC-V 指令
2020-2-6
| 2025-1-12
本文字數 1915閱讀時長 5 分鐘
type
status
date
slug
summary
tags
category
icon
password
⚠️
本文所使用的 QEMU 版本為:v4.2.0
在之前的文章中 (Part 1, Part 2) 我們提到了如何使用 Decodetree 來定義指令的 decoder。本篇文章就實際使用 Decodetree 來定義一個 QEMU RISC-V 目前尚未支援的指令 - B(itmanip) Extension 中的 pcnt 指令,並實做其行為。

pcnt 指令

pcnt 指令的定義如下:
This instruction counts the number of 1 bits in a register. This operations is known as population count, popcount, sideways sum, bit summation, or Hamming weight.
其指令格式為:

安裝 toolchain

由於 B Extension 尚未正式定稿 (Draft),因此必須至 riscv-bitmanip repo 下載 toolchain,並依照該 repo 的指示安裝:
此安裝除了 toolchain 外,還會安裝支援 B Extension 的 Spike (riscv-isa-sim) 及 riscv-pk (P.S. 目前的 script 是寫死安裝路徑為:/opt/riscv64b)。

範例程式

在安裝好後,我們可以寫一個範例程式,並搭配 Spike 來做測試:
此範例程式做的事情很簡單,透過 inline assembly:pcnt 指令,將 int num = 1871 bits 個數給計算出來。
透過剛剛的 toolchain 編譯此程式:
  • march=rv64gb:指定 target ISA 為 RISC-V 64-bit + g (IMAFD base) + b (B Extension)
透過 objdump 觀看其反組譯碼:
可以看到 Line:15 呼叫了 pcnt 指令:pcnt s0, s0
透過 Spike 執行程式:
187 的二進位為 101110111 bits 個數為 6,與程式輸出的結果一致。
同樣的程式,我們使用 QEMU 來執行:
可以看到,目前 QEMU 尚未支援 pcnt 指令,因此當執行到 pcnt 指令時,便會噴 Illegal instruction 的錯誤訊息。

在 QEMU 中新增 pcnt 指令

根據前述 B Extension spec. 所列的 pcnt 指令格式,參考目前 QEMU RISC-V 現有的 Decodetree:target/riscv/insn32.decodepcntPattern 可以搭配 @r2Format (只有 rs1rd 這兩個 Fields),其完整定義如下:

Field

Format

Pattern

我們可以定義 pcntPattern 如下:
所會產生的 decoder 如下:
由於 @r2 Format 並沒有參考任何的 Argument Set,因此 Decodetree 會自動根據 Format 所參考到的 Fields (rs1rd) 動態產生 argument set struct: arg_decode_insn3213
此外,由 pcnt Pattern 所產生的 decode function 會呼叫 decode_insn32_extract_r2() 這個 extract function 來解析指令中 rs2rd 欄位的值,並更新所傳入 arg_decode_insn3213 對應的欄位,而後再呼叫 trans_pcnt() 來執行 pcnt 指令 (產生對應的 TCG ops)。因此,我們還必須定義 trans_pcnt() 來實作 pcnt 的指令行為。

參考 B Extension spec. 中,pcnt 指令的實作:
及 Spike 中,pcnt 指令的實作:
實作很簡單,每次迴圈 right shift rs1 i 個 bits 並與 1AND,若為 true 就將 count1,最後回傳的 count 就是 1 bits 個數。

trans_pcnt() 實作了 pcnt 指令對應的 TCG ops。QEMU 在執行時,會將 target instructions (e.g. RISC-V instructions) 轉譯成 TCG ops,而 TCG ops 則會再轉譯為 host instructions (e.g. x86 instruction)。
關於 TCG 的說明,可以參考 QEMU 的 documentations:Translator InternalsTCG README
新增:trans_rvb.inc.c 來定義 B Extension 指令的實作 (當然,目前只有 pcnt 指令):
由於對 x0 (zero register) 的寫入都會被忽略,因此首先判斷 rd 是否為 0,若為 0 則不做任何的事情。
再來宣告一 TCG variable:t0,並透過 gen_get_gpr()rs1 暫存器的值 (如 pcnt_examplepcnt s0, s0 指令,rs1 即為 s0,也就是 x8),載入到 t0
這邊還呼叫了我們所定義幫我們處理 pcnt 計算 1 bits 個數的 pcnt helper function:gen_helper_pcnt()。該 helper function 會在計算完後,將最後的結果存至 rd (i.e. cpu_gpr[a->rd]) 暫存器中。
最後別忘了要釋放之前所宣告的 TCG variable:t0
P.S. 其實這邊可以更簡單的直接將 cpu_gpr[a->rs1] 傳入,省略 TCG variable:t0 的宣告:
pcnt 的 helper function 定義如下:
基本上就是實作先前在 B Extension spec. 及 Spike 中所看到的 1 bits 個數計算方式。由於 pcnt helper function 只需接收 rs1 暫存器的值,並回傳最後 1 bits 個數的結果,因此,我們定義 pcnt 的 helper function 為接收一 target_ulong 型態的 rs1 並回傳 target_ulong 型態的 1 bits 個數結果。
最後別忘了將我們新增的 bitmanip_helper.o 加入 compile objects 列表:

重新編譯 QEMU,再次執行 pcnt_example
這次 QEMU 就可以正確的 decode 並執行 pcnt 指令了。

在 QEMU 中新增指令的流程大致就如同本文所介紹,不過由於 pcnt 指令只是單純的 bit operation 指令,沒有像 csr 相關指令會涉及 CPURISCVState 的更新,以及像 jal 指令會涉及 DisasContext 的判斷,因此實作起來相對簡單。若欲讓 QEMU 支援不論是 B Extension 或是 V Extension 的其他指令,就是得好好 K spec. 並一個一個新增了。
另外最近剛好 C-Sky Microsystems 的 LIU Zhiwei <[email protected]> 在實作 V Extension 的 configure instructions:vsetvlvsetvli,比起本文所介紹之 B Extensionpcnt 指令要來得複雜得多,patches 仍在被 reviewed 中,也可以做為參考。
本文所對 QEMU 做的修正,可以參考此 commit
  • QEMU
  • OP-TEE: InitializationQEMU Decodetree 語法介紹 (Part 2.)
    Loading...