一起学RISC-V汇编第7讲之自定义指令
一起学RISC-V汇编第7讲之自定义指令
为了方便用户对RISC-V进行扩展,RISC-V支持自定义指令。
自定义指令通常需要硬件与软件协同开发。
1 硬件实现
确定了自定义指令的功能后,需要设计指令编码,然后硬件逻辑实现。
1.1 确定opcode
RV32指令架构中定义了4种custom指令类型,opcode需使用表格custom-0/custom-1/custom-2/custom-3中的一种:
1.2 确定指令类型
需要结合指令的功能来进行选择,有6种指令指令格式,分别为R/I/S/B/U/J类型,第4讲已经讲过,这里摘抄过来:
- R型用于寄存器-寄存器间的操作
- I型用于短立即数和访存(Load)操作
- S型用于访存Store操作
- B型用于条件跳转
- U型用于长立即数
- J型用于无条件跳转
1.3 确定指令编码
根据opcode以及指令类型,还需要确定其它字段的编码,比如R-type中,需要确定func3/func7字段的编码。指令编码确定后,便可以由硬件实现逻辑。
1.4 实现硬件逻辑
在硬件层面上实现自定义指令的逻辑功能。涉及到修改处理器的微架构,添加相应的电路和逻辑来执行自定义指令的操作。
2 编译器支持
自定义指令,一个前提条件是硬件支持这条指令;二是编译器的支持,可以编译成可执行文件;三是软件怎么用这条指令,可以使用汇编或者内嵌汇编,关于汇编和内嵌汇编的内容后续几讲会专门讲到。
编译器支持自定义指令有两种方式:
- 利用
Kito Cheng
提供的.insn
模板进行开发 - 修改
binutils
的方法
下面分别进行讲述,内容主要见参考1:
2.1 使用.insn模板
例如自定义一条计算立方的指令cube,功能是算术运算,所以指令类型为可以选择R-type或I-type
R-type格式如下:
所以.insn
模板为:
.insn r opcode, func3, func7, rd, rs1, rs2
opcode需使用上述RISC-V base opcode map表格custom-0/custom-1/custom-2/custom-3中的一种,func3/func7字段可以自定义,注意不要超过了位宽限制。
硬件实现了这条自定义指令后,接下来就是软件上使用了。
汇编语句:
.insn r 0x7b, 6, 6, a0, a1, x0
内嵌汇编:
asm volatile(".insn r 0x7b, 6, 6, %0, %1, x0" : "=r"(res) : "r"(var));
这两种写法是等价的,都是计算cube,编译得到机器码后交给实际的硬件进行解析,实现自定义的指令功能。
测试:
static int custom_cube(int var)
{
int res;
asm volatile (
".insn r 0x7b, 6, 6, %0, %1, x0"
:"=r"(res)
:"r"(var)
);
return res;
}
反汇编得到:
a0002c74 <custom_cube>:
a0002c74: 7179 addi sp,sp,-48
a0002c76: d622 sw s0,44(sp)
a0002c78: 1800 addi s0,sp,48
a0002c7a: fca42e23 sw a0,-36(s0)
a0002c7e: fdc42783 lw a5,-36(s0)
a0002c82: 0c07e7fb 0xc07e7fb
a0002c86: fef42623 sw a5,-20(s0)
a0002c8a: fec42783 lw a5,-20(s0)
a0002c8e: 853e mv a0,a5
a0002c90: 5432 lw s0,44(sp)
a0002c92: 6145 addi sp,sp,48
a0002c94: 8082 ret
其中0xc07e7fb机器码,交给硬件解析,即可完成所需功能。
2.2 修改binutils的方法
使用.insn
模板优点是不用修改编译器,比较简单,但阅读起来不友好,临时用还OK,如果是长期维护就会很头疼,为了解决这个问题,还可以通过修改binutils来解决。
-
下载riscv-opcodes
$ git clone https://github.com/riscv/riscv-opcodes
-
新建一个custom_opcode文件,添加如下内容,这些内容定义了指令的样式以及各字段的编码
cube rd rs1 rs2 31..25=0x0c 14..12=0x6 6..2=0x1e 1..0=3
-
使用官方脚本生成指令模版,按照git仓库的README操作,或者如下操作(注意:parse.py要写成完整路径)
python3 /path/to/parse.py -c custom_opcode
可见生成了encoding.out.h文件,文件中有自定义指令相关内容:
#define MATCH_CUBE 0x1800607b #define MASK_CUBE 0xfe00707f DECLARE_INSN(cube, MATCH_CUBE, MASK_CUBE)
-
修改binutils
在
riscv-gnu-toolchain/riscv-binutils
中,修改include/opcode/riscv-opc.h
上述
riscv-opcodes
生成的三条宏定义放到该文件中,然后修改opcodes/riscv-opc.c
中的指令定义。{"cube", 0, INSN_CLASS_I, "d,s,t", MATCH_CUBE, MASK_CUBE, match_opcode, 0 },
修改完成后,这样就添加完成了。
-
编译binutils工具链,最终得到支持自定义指令的工具链。
使用编译得到的工具链测试:
static int custom_cube(int var)
{
int res;
asm volatile (
"cube %0, %1, x0"
:"=r"(res)
:"r"(var)
);
return res;
}
反汇编为:
a0002c74 <custom_cube>:
a0002c74: 7179 addi sp,sp,-48
a0002c76: d622 sw s0,44(sp)
a0002c78: 1800 addi s0,sp,48
a0002c7a: fca42e23 sw a0,-36(s0)
a0002c7e: fdc42783 lw a5,-36(s0)
a0002c82: 1807e7fb cube a5,a5,zero
a0002c86: fef42623 sw a5,-20(s0)
a0002c8a: fec42783 lw a5,-20(s0)
a0002c8e: 853e mv a0,a5
a0002c90: 5432 lw s0,44(sp)
a0002c92: 6145 addi sp,sp,48
a0002c94: 8082 ret
可见gcc已经可以识别到cube指令了。
参考: