riscv支持自定义指令,但是自定义指令,在gcc工具链中,是不支持的,因此需要自己修改gcc工具链,以支持自己定义的指令。
gcc工具链中,gcc工具负责将高级语言,转换为汇编程序。binutils中的as工具,负责将汇编程序,转化为二进制程序。
我们增加的是汇编指令,因此只需要修改binutils代码,在其中,加入我们定义的指令。
Contents
一、需要加的指令
我们需要加的指令,如下图所示:
这条指令,比较特殊的是,最后7个bit为全1。
如果按照riscv-spec的规定,那么这条指令,是属于80bit指令。
但是,这里,只是借用了80bit指令的编码部分,指令还是32bit。这就需要在binutils中,做一些处理。
二、下载binutils
从github上,下载riscv-binutils的源代码。
git clone https://github.com/riscv/riscv-binutils-gdb.git
下载完毕后,在源代码根目录,创建build目录,用于之后编译binutils。
我们首先编译binutils工具。
cd build ../configure -with-arch=rv64imafdc --with-abi=lp64d --target=riscv64-unkown-elf --prefix=/home/lujun/tools/riscv-gcc2 make make install
安装完毕后,得到如下一些工具:指定将来工具,安装在 /home/lujun/tool/riscv-gcc2, 这样就不需要root权限。
我们在这里,主要修改的是as工具和objdump工具。
三、生成encoding.h
riscv的相关工具,比如binutils,spike等,均依赖于encoding.h这个头文件,而这个头文件,由riscv-opcodes这个工具来生成。
从github上,下载riscv-opcodes开源项目:
git clone https://github.com/riscv/riscv-opcodes.git
创建,opcodes-own文件,加入如下内容:
p.add16 rd rs1 rs2 31..25=0x20 14..12=0 6..2=0x1f 1..0=3 |
最前面一列,是指令的助记符,后续跟指令的参数。
接着是各个bit的描述,这个比较重要。要注意描述,从高bit描述到低bit。
修改Makefile,增加如下代码:
gen: cat opcodes-own | python3 ./parse-opcodes -c > encoding.h
执行make gen,生成新的 encoding.h文件。在这个文件中,我们关注如下信息:
#define MATCH_P_ADD16 0x4000007f #define MASK_P_ADD16 0xfe00707f DECLARE_INSN(p_add16, MATCH_P_ADD16, MASK_P_ADD16)
定义该条指令的MASK和MATCH,并且声明一条该指令。
四、修改riscv-opc.h文件
在binutils源代码根目录下的include/opcode/riscv-opc.h 文件,其实就来源于上面生成的encoding.h文件。
该文件,对riscv的指令和架构进行了描述。不过不能直接从上一步生成的encoding.h文件,拷贝覆盖该文件,否则编译会报错。
将上一步得到的信息,拷贝到该文件中。
加入MASK和MATCH。
加入DECLARE_INSN。
五、修改riscv-opc.c文件
binutils源代码根目录下,opcdes/riscv-opc.c 文件,定义了riscv的指令集。
结构体数组,riscv_opodes,保存了riscv的指令。因此要在这个数组中,加入我们自定义的指令,这样才能够实现编译。
在数组的最后,加入如下内容:
{"p.add16", 0, {"I", 0}, "d,s,t", MATCH_P_ADD16, MASK_P_ADD16, match_opcode, 0}, |
如下图所示:
第一个是汇编指令的助记符
第二个是指令依赖的XLEN,0表示不指定
第三个是指令所属的指令集,这里暂定为I指令集
第四个是指令的参数,d表示rd,s表示rs1,t表示rs2。
第五个是指令的MATCH参数,在enconding.h文件中生成
第六个是指令的MASK参数,在encoding.h文件中生成
第七个是指令的match函数,直接使用默认的match_opcode函数
第八个填0即可。
以上的格式,可以参考目前已经有的指令,进行修改。可以考虑找一条和当前指令很相似的指令,然后参考修改。
六、修改riscv.h
在binutils源代码根目录,include/opcode/riscv.h中,有riscv_insn_length函数,该函数,用来判断指令的位宽。
在代码中,加入80bit指令的判断。如果是80bit指令,当做是32bit指令,返回4。
static inline unsigned int riscv_insn_length (insn_t insn) { if ((insn & 0x3) != 0x3) /* RVC. */ return 2; if ((insn & 0x1f) != 0x1f) /* Base ISA and extensions in 32-bit space. */ return 4; if ((insn & 0x3f) == 0x1f) /* 48-bit extensions. */ return 6; if ((insn & 0x7f) == 0x3f) /* 64-bit extensions. */ return 8; if ((insn & 0x7f) == 0x7f) /* 80bit, reserved as 32bit */ return 4; /* Longer instructions not supported at the moment. */ return 2; }
七、编译binutils
修改完毕后,需要对binutils重新编译。
cd build make -j8 make install
八、测试
编写如下汇编程序,命名为 a.s
.text p.add16 x5, x1, x2 p.add16 x2, x2, x3 p.add16 x6, x5, x8 p.add16 x4, x7, x9
使用刚刚编译得到的 riscv64-unknown-elf-as 工具,编译该程序,得到 a.out。说明,as工具,可以编译我们自定义的指令。
使用 riscv64-unknow-elf-objdump -d a.out, 查看反汇编:
从反汇编可以看出,我们自定义的指令,objdump工具也能认识,能做反汇编。