Spike 是 RISC-V ISA 模拟器,实现了一个或多个 RISC-V hart 的功能模型。它的名字来源于用于庆祝美国横贯大陆铁路竣工的金色道钉。
Spike 支持以下 RISC-V ISA 特性:
项目进行版本管理主要是为了标明 API 何时被扩展或变得不兼容。本着这一精神,Spike 力求遵循 SemVer 版本管理方案,其中当做出向后不兼容的 API 变更时递增主版本号;当添加新 API 时递增次版本号;当以向后兼容的方式修复缺陷时递增补丁版本号。
Spike 的主要公共 API 是 RISC-V ISA。Spike 内部实现所用的 C++ 接口目前不被视为公共 API,对该接口的向后不兼容变更_将_在不递增主版本号的情况下进行。
我们假设 RISCV 环境变量已设置为 RISC-V 工具的安装路径。
$ git submodule update --init --recursive
$ apt-get install device-tree-compiler
$ apt-get install libboost-regex-dev libboost-system-dev
$ mkdir build
$ cd build
$ ../configure --prefix=$RISCV
$ make
$ [sudo] make install
如果你的系统使用 yum 包管理器,可以将第一步替换为 yum install dtc。
如果你不需要在交互式调试模式下通过 socket 使用命令 I/O,可以跳过第二步。
安装 bash、gmake、dtc,并使用 clang。
$ git submodule update --init --recursive
$ pkg_add bash gmake dtc
$ exec bash
$ export CC=cc; export CXX=c++
$ mkdir build
$ cd build
$ ../configure --prefix=$RISCV
$ gmake
$ [doas] make install
安装 spike(参见构建步骤)、riscv-gnu-toolchain 和 riscv-pk。
编写一个简短的 C 程序并将其命名为 hello.c。然后,将其编译为名为 hello 的 RISC-V ELF 二进制文件:
$ riscv64-unknown-elf-gcc -o hello hello.c
现在你可以在代理内核之上模拟该程序:
$ spike pk hello
向模拟器添加一条指令需要两个步骤:
在文件 riscv/insns/<new_instruction_name>.h 中描述该指令的功能行为。查看该目录中的其他指令作为起点。
将操作码和操作码掩码添加到 riscv/opcodes.h。或者,将其添加到 riscv-opcodes 包中,它会为你完成此操作:
$ cd ../riscv-opcodes
$ vi opcodes // 为新指令添加一行
$ make install
将指令添加到 riscv/riscv.mk.in。否则,该指令将不会包含在构建中,并会被视为非法指令。
重新构建模拟器。
要调用交互式调试模式,请使用 -d 启动 spike:
$ spike -d pk hello
要查看整数寄存器的内容(0 表示核心 0):
: reg 0 a0
要查看浮点寄存器的内容:
: fregs 0 ft0
或:
: fregd 0 ft0
取决于你希望以单精度还是双精度打印该寄存器。
要查看内存位置的内容(十六进制物理地址):
: mem 2020
要查看具有虚拟地址的内存内容(0 表示核心 0):
: mem 0 2020
你可以按回车键前进一条指令。你也可以执行直到达到所需的相等条件:
: until pc 0 2020 (当 pc=2020 时停止)
: until reg 0 mie a (当寄存器 mie=0xa 时停止)
: until mem 2020 50a9907311096993 (当 mem[2020]=50a9907311096993 时停止)
或者,你可以在相等条件为真时持续执行:
: while mem 2020 50a9907311096993
你可以通过以下方式无限期地继续执行:
: r
在执行过程中的任何时刻(即使没有 -d),你都可以使用 <control>-<c> 进入交互式调试模式。
要从调试提示符结束模拟,请按 <control>-<c> 或:
: q
交互式调试模式的替代方案是使用 gdb 附加。由于 spike 试图模拟真实硬件,你还需要 OpenOCD 来实现这一点。 我们将使用以下测试程序:
$ cat rot13.c
#include <stdio.h>
char text[] = "Vafgehpgvba frgf jnag gb or serr!";
// Don't use the stack, because sp isn't set up.
volatile int wait = 1;
int main()
{
int i = 0;
while (text[i]) {
char lower = text[i] | 32;
if (lower >= 'a' && lower <= 'm')
text[i] += 13;
else if (lower > 'm' && lower <= 'z')
text[i] -= 13;
i++;
}
done:
printf("decoded text: %s\n", text);
}
$ riscv64-unknown-elf-gcc -g -Og --specs=semihost.specs -o rot13 rot13.c
要调试此程序,首先运行 spike 并让它监听 OpenOCD:
$ spike --rbb-port=9824 -m0x10000:0x20000 rot13
Listening for remote bitbang connection on port 9824.
...
在另一个 shell 中,使用相应的配置文件运行 OpenOCD:
$ cat spike.cfg
adapter driver remote_bitbang
remote_bitbang host localhost
remote_bitbang port 9824
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xdeadbeef
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
gdb report_data_abort enable
init
arm semihosting enable
halt
$ openocd -f spike.cfg
Open On-Chip Debugger 0.12.0
...
Info : starting gdb server for riscv.cpu on 3333
Info : Listening on port 3333 for gdb connections
riscv.cpu halted due to debug-request. Semihosting is active.
...
riscv.cpu: target state: halted
在另一个 shell 中,启动你的 gdb 调试会话:
$ riscv64-unknown-elf-gdb rot13
...
Reading symbols from rot13...
(gdb) target extended-remote localhost:3333
...
(gdb) load
...
(gdb) set $sp=0x2fff0
(gdb) b main
Breakpoint 1 at 0x10202: file rot13.c, line 5.
(gdb) c
Continuing.
Disabling abstract command writes to CSRs.
Breakpoint 1, main () at rot13.c:5
5 {
(gdb) print text
$1 = "Vafgehpgvba frgf jnag gb or serr!"
(gdb) until done
[riscv.cpu] Found 4 triggers
main () at rot13.c:16
16 printf("decoded text: %s\n", text);
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00019ff8 in _exit ()
(gdb)
...
在 OpenOCD 终端上,你将看到:
...
decoded text: Instruction sets want to be free!
semihosting: *** application exited with 0 ***
riscv.cpu halted due to breakpoint. Semihosting is active.