环境
本文假设用户使用基于 debian 的 Linux 系统,有 su
或者 sudo
的能力1,并且用的文件系统是区分大小写2的。
然后请事先安装 git
以及任意文本编辑器。
GNU 编译工具链
clone 代码
git clone https://github.com/riscv/riscv-gnu-toolchain
然后进入 clone 到的文件夹中。
装依赖
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
准备要安装到的位置
比如要把工具链装到 /opt/riscv
下的话:
mkdir /opt/riscv
chmod 777 /opt/riscv # just use 777, who cares about bad guys?
configure
./configure --prefix=/opt/riscv --enable-multilib
make
make
One eternity later ...
make linux
Two eternities later ...
注意每次 make
的时候都会下载对应的 submodule
,请保证有正常的网络环境。
然后 /opt/riscv
里就有编译器、lib 等等了。
添加自定义指令
编辑 riscv-binutils/opcodes/riscv-opc.c
,在 riscv_opcodes
中加入新的指令:
{
"<指令名称>", 0, <指令类型>, "<操作数>",
<匹配>, <掩码>, match_opcode, 0
},
代表使用时形如:
<指令名称> <操作数>
的一条汇编指令。
指令类型
建议无脑给 INSN_CLASS_I
, 因为 I
在所有情况下都是支持的,无需在编译时手动开启各个指令集。
暂时不清楚是否支持在 include/opcode/riscv.h
中的 riscv_insn_class
中添加自己的指令集名称,然后在这里使用。
操作数
这我一直没找到一个说明的文档,只有根据已经写好的部分和代码(代码在 riscv-binutils/gas/config/tc-riscv.c
)推了3:
-
d
代表目标寄存器 -
s
代表第一个寄存器操作数 -
t
代表第二个寄存器操作数 -
j
代表一个立即数 -
o(s)
代表用于读取出来的一个地址,格式为寄存器 + 偏移量 -
q(s)
代表用于写入进去的一个地址,格式为寄存器 + 偏移量 -
<
和>
代表移位的位数
客制寄存器
这里假设你希望在内联汇编里手写寄存器而不是让 gcc 为你分配你的客制寄存器,比如你希望添加一个读取矩阵的指令:
{"matload", 0, INSN_CLASS_I, "Md,o(s)", 0x100b, 0x707f, match_opcode, INSN_DREF},
这里的 Md
代表一个矩阵寄存器。
那么你需要在 gnu 工具链中进行如下修改:
-
在
riscv-binutils/opcodes/riscv-opc.c
中的riscv_fpr_names_abi
下面,添加一个新的数组,保存你的各个寄存器的名字,并在riscv-binutils/include/opcode/riscv.h
添加相应的声明,例如:// riscv.h extern const char *const riscv_mat_names_numeric[8];
// riscv-opc.c const char * const riscv_mat_names_numeric[8] ={"mat0", "mat1", "mat2", "mat3", "mat4", "mat5", "mat6", "mat7"};
-
在
reg_class
(在riscv-binutils/gas/config/tc-riscv.c
)中添加这个客制寄存器集的“名称”:enum reg_class { RCLASS_GPR, RCLASS_FPR, + RCLASS_MPR, RCLASS_MAX, RCLASS_CSR };
-
在同一文件下负责汇编器初始化的
md_begin
函数中注册这个客制寄存器集:hash_reg_names(RCLASS_MPR, riscv_mat_names_numeric, 8);
-
在同一文件下检查 RISC-V 指令格式的
validate_riscv_insn
函数中个的最大的那个switch
中添加对这个寄存器参数的检查。 -
在同一文件下负责进行实际汇编到二进制代码的
riscv_ip
函数中添加生成二进制代码的代码。
到此你的编译器已经能正常生成二进制代码了,但是如果你还想要用 objdump
之类的反编译工具:
- 在
riscv-binutils/opcodes/riscv-dis.c
中的print_insn_args
中也添加相关解析代码。
匹配和掩码
设有一条二进制指令 instruction
, 若:
instruction & 掩码 == 匹配
则认为 instruction
是这一类的指令。
重新编译
重新编译工具链需要 make clean
,删空安装位置(如 /opt/riscv
),然后重新 make
4。
使用
然后就能在汇编里拿 asm
用你刚刚加的指令了。
RISC-V tools
我们主要使用 RISC-V tools 中的 RISC-V 行为级模拟器 —— spike。
clone 代码
git clone https://github.com/riscv/riscv-tools.git
然后进入 clone 到的文件夹中。
与 GNU 工具链在 make
的时候会自动拿 submodule 不同,tools 这边要手动:
git submodule update --init --recursive
此外建议把我们重点要修改的 riscv-isa-sim
手动更新到 origin master 版本:
cd riscv-isa-sim
git pull origin master
指定安装地址
export RISCV=<安装地址>
build
如果你按照官方说明直接在 clone 到的文件夹中:
./build.sh
那你大概率会碰到 riscv-test submodule 里的关于 tohost
和 fromhost
重复定义的错误,可以采用这里提到的方法修复6:
修改 ./riscv-tests/isa/Makefile
中的编译选项:
-RISCV_GCC_OPTS ?= -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles
+RISCV_GCC_OPTS ?= -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -fcommon
添加自定义指令集
在 Spike 模拟器中添加客制指令集非常方便,只需要在 ./riscv-isa-sim/customext/
中添加一个实现扩展指令集行为的文件,
其中有一个类实现了 extension_t
,然后用 REGISTER_EXTENSION
宏将其注册到模拟器即可。
extension_t
要求一个 name
,一个拿所有指令的 get_instructions
,一个拿所有反汇编指令的 get_disasms
。
gem5 模拟器
clone 代码
git clone https://gem5.googlesource.com/public/gem5
装依赖
sudo apt install build-essential git m4 scons zlib1g zlib1g-dev \
libprotobuf-dev protobuf-compiler libprotoc-dev libgoogle-perftools-dev \
python3-dev python3-six python libboost-all-dev pkg-config
build
在 clone 到的文件夹中:
/usr/bin/env python3 $(which scons) build/RISCV/gem5.opt -j <CPU核数>
如果你想要在你的程序中加 checkpoint
、重置统计数据等,需要构建 libm5
:
/usr/bin/env python3 $(which scons) -C util/m5 build/riscv/out/m5
使用
如果你编写的程序中使用了 libm5
中的功能,需要包含 gem5/m5ops.h
并在链接时链接 libm5.a
:
riscv64-linux-gnu-gcc -static -I/home/longfangsong/workspace/gem5/include/ -I <gem5 位置>/gem5/include/ -static <其他源文件> <gem5 位置>/gem5/util/m5/build/riscv/out/libm5.a
Trick on profiling unspported ISA set
gem5
只支持使用 riscv64-linux-gnu-gcc
生成的二进制文件,而且假定了用户有硬浮点支持,这在想要比较不同指令集时会带来一些不便。
解决方案非常呆但很有效,就是先用 riscv64-unknown-elf-gcc
拿到某个指令集和 abi(如果要用和最终编译到二进制文件时不同的 abi 的话建议通过全局变量传参数和返回值) 下的汇编,然后用 riscv64-linux-gnu-gcc
编译汇编来生成最终的二进制文件以供 gem5
执行。
可能会用到软乘法、软浮点等等的libgcc 中的 polyfill 代码,把这些也拉过来编译就是了7, 目前已知的几份 polyfill 代码的位置都位于 riscv-gnu-toolchain/libgcc
下,softfp
就是软浮点,config/riscv
就是其他一些东西的 polyfill,比如乘除法,原子操作等等。
因为 debian 可以在 su
下面做所有的事,sudo
反而要自己装,docker 一打开就是 su
,为了复制粘贴方便下面都不会写明要 sudo
的地方,如果有必要的话自己加……
这是真的坑。还好正常的 Linux 系统安装器默认的文件系统都是区分大小写的。但用 Docker 挂其他系统的 Volume 就不一定了……
没文档就 nm 离谱,有些字母用的也挺离谱。
这群开发者写的什么鬼 Makefile 啦。
讲道理我也不懂 😭
官方维护在干什么东西啊.jpg
现在我懂为啥 C++ 大型项目为啥要把依赖也搞到项目的 ./third_parties
里面一起编译了,否则这个链接、 abi 什么的是真的麻烦。