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 = 187 的 1 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 的二進位為 10111011,1 bits 個數為 6,與程式輸出的結果一致。同樣的程式,我們使用 QEMU 來執行:
可以看到,目前 QEMU 尚未支援
pcnt 指令,因此當執行到 pcnt 指令時,便會噴 Illegal instruction 的錯誤訊息。在 QEMU 中新增 pcnt 指令
根據前述 B Extension spec. 所列的
pcnt 指令格式,參考目前 QEMU RISC-V 現有的 Decodetree:target/riscv/insn32.decode,pcnt 的 Pattern 可以搭配 @r2 的 Format (只有 rs1 及 rd 這兩個 Fields),其完整定義如下:Field
Format
Pattern
我們可以定義
pcnt 的 Pattern 如下:所會產生的 decoder 如下:
由於
@r2 Format 並沒有參考任何的 Argument Set,因此 Decodetree 會自動根據 Format 所參考到的 Fields (rs1、rd) 動態產生 argument set struct: arg_decode_insn3213。此外,由
pcnt Pattern 所產生的 decode function 會呼叫 decode_insn32_extract_r2() 這個 extract function 來解析指令中 rs2 及 rd 欄位的值,並更新所傳入 arg_decode_insn3213 對應的欄位,而後再呼叫 trans_pcnt() 來執行 pcnt 指令 (產生對應的 TCG ops)。因此,我們還必須定義 trans_pcnt() 來實作 pcnt 的指令行為。參考 B Extension spec. 中,
pcnt 指令的實作:及 Spike 中,
pcnt 指令的實作:實作很簡單,每次迴圈 right shift
rs1 i 個 bits 並與 1 做 AND,若為 true 就將 count 加 1,最後回傳的 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)。新增:
trans_rvb.inc.c 來定義 B Extension 指令的實作 (當然,目前只有 pcnt 指令):由於對
x0 (zero register) 的寫入都會被忽略,因此首先判斷 rd 是否為 0,若為 0 則不做任何的事情。再來宣告一 TCG variable:
t0,並透過 gen_get_gpr() 將 rs1 暫存器的值 (如 pcnt_example 中 pcnt 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:vsetvl 及 vsetvli,比起本文所介紹之 B Extension 的 pcnt 指令要來得複雜得多,patches 仍在被 reviewed 中,也可以做為參考。本文所對 QEMU 做的修正,可以參考此 commit。