如何使用riscv-tests进行rv指令集测试
riscv-tests
riscv-tests是 Github 上由 Riscv 社区维护的一个 riscv32/64 指令集的测试系统. 可以用来测试模拟器/处理器的指令实现情况, 支持大部分指令的测试.
构建
请确保你的系统已经安装了riscv-gnu-toolchain, 并且已经将riscv-gnu-toolchain的bin目录添加到了环境变量中.
1 |
|
现在你可以在./tests目录下找到所有的测试程序(elf). 将elf文件喂给你的模拟器/处理器, 并查看程序退出后a0的值即可.
测试根据以下几个标准被分成几种TVM(Test Vector Module)类型:
- 可以使用的寄存器和指令集。
- 可以访问内存的哪些部分。
- 测试程序开始和结束执行的方式。
- 测试数据的输入方式。
- 测试结果的输出方式。
TVM Name | Description |
---|---|
rv32ui | user-level, integer only |
rv32si | supervisor-level, integer only |
rv64ui | user-level, integer only |
rv64uf | user-level, integer and floating-point |
rv64uv | user-level, integer, floating-point, and vector |
rv64si | supervisor-level, integer only |
rv64sv | supervisor-level, integer and vector |
其中user-level代表指令主要用于用户态,supervisor-level代表指令可能会用于内核态, 因此supervisor-level的指令中包含着大量的特权指令, 同时要求TVM支持特权指令.
同时, 对于每种TVM对象, 还有不同的目标环境, 不同的目标环境主要由virtual memory是否开启来区分, 以下是目标环境的描述:
Target Environment Name | Description |
---|---|
p | virtual memory is disabled, only core 0 boots up |
pm | virtual memory is disabled, all cores boot up |
pt | virtual memory is disabled, timer interrupt fires every 100 cycles |
v | virtual memory is enabled |
上面两个特性规定了每个测试环境的基本特性, 例如rv32ui-p代表着一个只有一个核心的32位无虚拟内存的用户态测试环境. 相对应的, rv32ui-p-addi代表着一个只有一个核心的32位无虚拟内存的用户态测试环境, 这个测试环境主要测试addi指令. 然而, 每个测试用例除了要测试的指令外, 还会有一些额外的指令来辅助我们发现运行到哪个test出现了问题.
源码
riscv-tests官方的README并不完整, 大部分的内容还是要自己去看源码. riscv-tests的源码中最重要的两个部分是/isa和/env. 其中/isa目录下存放着所有的测试用例, /env目录下存放着所有的测试环境.
以/isa/rv64ui/rv64ui-p-addi.S为例, 这个测试用例的源码如下:
1 |
|
其中包括了一些宏定义, 这些宏定义在riscv_test.h和test_macro.h中找到. 最重要的宏定义是TEST_PASSFAIL. 我们可以在/env/p/riscv_test.h中找到. 其中/p代表环境类型, 不同的环境类型判断pass/fail的宏定义也就不同, 这点需要注意.
1 |
|
简而言之, 每一个测试文件包含着若干个测试用例TEST_1 ~ TEST_N, 在每个测试用例开始时会将当前测试的编号存入gp寄存器, 在测试结束时调用RVTEST_PASS或RVTEST_FAIL.
测试是否通过的判定依据是a0寄存器是否为0, 如果为0则调用RVTEST_PASS, 否则调用RVTEST_FAIL.
如果测试不通过, 则会将测试编号左移一位, 然后加1, 重新调用RVTEST_FAIL.
因此, 如果a0的数字大于0, 意味着TEST_{a0-1/2}测试失败.
然而, 如果环境是v, 那么RVTEST_PASS的判断条件就是a0是否为1:
riscv-test-env
riscv-tests-env写的比较自由, 有些地方的宏定义可能会有一些不同, 因此需要注意.
fence的作用是保证所有的指令都已经执行完毕, 并且所有的寄存器都已经写回内存. ecall的作用是调用系统调用, 具体的功能是由a7寄存器决定的. 93代表着退出程序.
// TODO: 补充各个指令集主要测试的指令