修改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

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Back to Top