修改binutils在RISC-V上添加汇编指令
1. 起源
最近给学长打工做的相关研究需要对RISC-V指令集进行扩展,因此需要魔改编译器添加指令。
而RISC-V架构上做到这一点其实非常容易,官方提供了一个riscv-opcodes工具,它可以用于生成编译器所需的opcode宏来添加所需的指令。
2. 准备环境
我的实验基于Debian Bullseye发行版进行。(写这篇文章之时Bullseye还处于Testing状态)
首先我们需要编译安装riscv-gnu-toolchain
.
sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
sudo mkdir /opt/riscv
sudo chown $USER:$USER /opt/riscv # $USER是自己的用户,这样后续操作可以省去需要root权限的麻烦
git clone git@github.com:riscv/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
./configure --prefix=/opt/riscv
make linux -j 16 # 16根据自己的CPU线程数修改
在安装完成后,最好将/opt/riscv/bin
添加到$PATH
环境变量中,以便直接使用。
echo "export PATH=/opt/riscv/bin:$PATH" >> ~/.zshrc # 如果你使用的是zsh,如果是默认的bash改成.bashrc即可
3. 使用riscv-opcodes生成编译器所需宏定义
首先,下载所需的工具
git clone git@github.com:riscv/riscv-opcodes.git
cd riscv-opcodes
code opcodes-custom # code指vscode,可以换成你喜欢的编辑器
这里需要读者对RISC-V的指令类型有一定的了解,实际上和MIPS非常相似,如果不了解可以先阅读这里。
这里它给了我们一些指令的模板如下所示:
@custom0 rd rs1 imm12 14..12=0 6..2=0x02 1..0=3
@custom0.rs1 rd rs1 imm12 14..12=2 6..2=0x02 1..0=3
@custom0.rs1.rs2 rd rs1 imm12 14..12=3 6..2=0x02 1..0=3
@custom0.rd rd rs1 imm12 14..12=4 6..2=0x02 1..0=3
@custom0.rd.rs1 rd rs1 imm12 14..12=6 6..2=0x02 1..0=3
@custom0.rd.rs1.rs2 rd rs1 imm12 14..12=7 6..2=0x02 1..0=3
而我们可以在此添加一条R-Type指令,如下:
max rd rs1 rs2 31..25=0 14..12=7 6..2=0x02 1..0=3
其中[14:12] funct3、[31:25] funct7部分都可以自己进行修改。
然后我们执行这个仓库中的parse_opcodes
脚本来生成编译器所需的宏定义,保存到def.h
中:
python3 parse_opcodes -c < opcodes-custom > def.h
然后我们打开def.h
,搜索我们刚刚添加的指令max
,找到以下三行:
#define MATCH_MAX 0x700b
#define MASK_MAX 0xfe00707f
// ...
DECLARE_INSN(max, MATCH_MAX, MASK_MAX)
这三行便是我们需要加入编译器的关键。
4. 修改编译器的binutils
我们找到之前的riscv-gnu-toolchain源代码目录
打开./riscv-binutils/include/opcode/riscv-opc.h
然后我们会惊奇地发现这个文件已经预留了一部分CUSTOM部分,我们把刚刚的两行#define接在其后,如下所示:
同样,我们也将刚刚定义的max指令的DECLARE_INSN
添加到其后,如下图所示:
到此只是定义了宏定义,我们还需要修改opcodes表。
打开./riscv-binutils/opcodes/riscv-opc.c
可以看到这里有一个结构体常量数组定义了所有opcodes:
const struct riscv_opcode riscv_opcodes[] =
{
/* name, xlen, isa, operands, match, mask, match_func, pinfo. */
我们仿照其它R-Type的指令格式编写我们的指令的该结构体下的定义:
{"max", 0, INSN_CLASS_I, "d,s,t", MATCH_MAX, MASK_MAX, match_opcode, 0 },
然后将它插入到/* Terminate the list. */
之前:
然后保存这两个文件的更改,重新编译工具链。
make clean
make linux -j 16
5. 编写程序测试:
这里我们编写以下代码进行测试:
#include <stdio.h>
int hw_max(int a,int b) {
int res;
__asm__ __volatile__(
"max %[d], %[a], %[b]\n\t"
: [d] "=r" (res)
: [a] "r" (a), [b] "r" (b)
);
return res;
}
int main() {
int a = 233;
int b = 666;
int c = hw_max(a,b);
printf("%d\n",c);
return 0;
}
效果如图:
6. 参考资料
Adding custom instruction to RISCV ISA and running it on gem5 and spike
Syntacore RISCV 工具链使用(五)gcc工具链的生成与添加自定义指令集
注:以上参考资料都直接使用了riscv-tools这个年久失修的仓库,其它组件编译的时候会遇到问题,但我们只需要使用riscv-opcodes
并不需要编译整个tools部分。
One Response