环境

本文假设用户使用基于 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 工具链中进行如下修改:

  1. 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"};
    
  2. reg_class (在 riscv-binutils/gas/config/tc-riscv.c)中添加这个客制寄存器集的“名称”:

    enum reg_class
    {
      RCLASS_GPR,
      RCLASS_FPR,
    + RCLASS_MPR,
      RCLASS_MAX,
      RCLASS_CSR
    };
    
  3. 在同一文件下负责汇编器初始化的 md_begin 函数中注册这个客制寄存器集:

    hash_reg_names(RCLASS_MPR, riscv_mat_names_numeric, 8);
    
  4. 在同一文件下检查 RISC-V 指令格式的 validate_riscv_insn 函数中个的最大的那个 switch 中添加对这个寄存器参数的检查。

  5. 在同一文件下负责进行实际汇编到二进制代码的 riscv_ip 函数中添加生成二进制代码的代码。

到此你的编译器已经能正常生成二进制代码了,但是如果你还想要用 objdump 之类的反编译工具:

  1. riscv-binutils/opcodes/riscv-dis.c 中的 print_insn_args 中也添加相关解析代码。

匹配和掩码

设有一条二进制指令 instruction, 若:

instruction & 掩码 == 匹配

则认为 instruction 是这一类的指令。

重新编译

重新编译工具链需要 make clean,删空安装位置(如 /opt/riscv),然后重新 make4

使用

然后就能在汇编里拿 asm 用你刚刚加的指令了。

如果你不是很懂内联汇编5,可以看看我的另一篇文章

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 里的关于 tohostfromhost 重复定义的错误,可以采用这里提到的方法修复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,比如乘除法,原子操作等等。

1

因为 debian 可以在 su 下面做所有的事,sudo 反而要自己装,docker 一打开就是 su,为了复制粘贴方便下面都不会写明要 sudo 的地方,如果有必要的话自己加……

2

这是真的坑。还好正常的 Linux 系统安装器默认的文件系统都是区分大小写的。但用 Docker 挂其他系统的 Volume 就不一定了……

3

没文档就 nm 离谱,有些字母用的也挺离谱。

4

这群开发者写的什么鬼 Makefile 啦。

5

讲道理我也不懂 😭

6

官方维护在干什么东西啊.jpg

7

现在我懂为啥 C++ 大型项目为啥要把依赖也搞到项目的 ./third_parties 里面一起编译了,否则这个链接、 abi 什么的是真的麻烦。