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。