我想检测一个程序来访问硬件性能计数器。
我已经编译了一个基本的 Rocketchip ( freechips.rocketchip.system-DefaultConfig) 并riscv-pk用于运行二进制文件。我正在 Verilator 中运行内核的模拟,在 UCB Chipyard 项目中使用大部分默认值编译了它。
二进制文件的 C 如下:
#include <stdio.h>
#define CACHE_MISS 0x100
int loop(int n, int a) {
int b = 0;
for(int i=0; i<n; i++) {
b = a + b;
}
return b;
}
int main() {
int a = 1;
int n = 200;
int count = 0;
printf("Configuring event monitor ...\n");
/*
// initialize counters here
// should start tracking cache misses with 0x100
__asm__ volatile("csrw mhpmevent3, %0"
:
: "r"(CACHE_MISS)
);
*/
printf("Executing loop ...\n");
loop(n, a);
printf("Reading event counter ...\n");
// read counters here
__asm__ volatile("csrr %0, hpmcounter3"
: "=r"(count)
);
printf("Cache misses: %d\n", count);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这是一个基本代码,只是为了看看我是否可以计算缓存未命中数。这段代码给了我一个“非法指令”错误。
z 0000000000000000 ra 00000000000101e0 sp 000000000fee9b00 gp 000000000001e560
tp 0000000000000000 t0 0000000000000000 t1 000000000000000f t2 0000000000000000
s0 000000000fee9b20 s1 0000000000000000 a0 00000000000000c8 a1 0000000000000001
a2 0000000000000000 a3 0000000000000010 a4 00000000000000c8 a5 00000000000000c8
a6 000000000000001f a7 0000000000000000 s2 0000000000000000 s3 0000000000000000
s4 0000000000000000 s5 0000000000000000 s6 0000000000000000 s7 0000000000000000
s8 0000000000000000 s9 0000000000000000 sA 0000000000000000 sB 0000000000000000
t3 0000000000000000 t4 0000000000000000 t5 0000000000000000 t6 0000000000000000
pc 00000000000101e0 va 00000000b03027f3 insn b03027f3 sr 8000000200046020
An illegal instruction was executed!
Run Code Online (Sandbox Code Playgroud)
最初读取的行hpmcounter3正在读取mhpmcounter3并导致相同的错误。
与另一位开发人员交谈,一个可能的原因是riscv-pk在 U 模式下运行二进制文件,因此在调用m*汇编指令(在机器,M 模式下运行)之后会出现非法指令。因此,我更改了二进制文件以注释掉对 mphmevent3 的第一次写入,并且只是从 hpmcounter3 进行了裸读,但仍然收到非法指令错误。这个结果的理论是没有启用mcounteren和scounteren寄存器,所以我无法访问计数器。但是,在riscv-pk's内部查看minit.c,我发现:
// Enable user/supervisor use of perf counters
if (supports_extension('S'))
write_csr(scounteren, -1);
if (supports_extension('U'))
write_csr(mcounteren, -1);
Run Code Online (Sandbox Code Playgroud)
我不确定,但我觉得这可以启用计数器。
关于我应该如何访问硬件性能计数器的任何想法?
是的,pk 在用户模式下运行二进制文件,因此预计从用户空间访问机器级寄存器会产生非法指令异常:
\n\n\n\n\n尝试在没有适当权限级别的情况下访问 CSR 或写入只读寄存器也会引发非法指令异常。
\n
(RISC-V 特权规范 1.12-草案 2020-01-13,第 2.1 节 CSR 地址映射约定)
\n\n当且仅当它们在下一个更高模式中启用时,这些hpmcounter*寄存器才可以从用户模式访问(参见表 2.2 当前分配的 RISC-V 用户级 CSR 地址,RISC-V 特权规范):
\n\n\n如果实现了 S 模式,则在 U 模式下执行时,scounteren 寄存器中的相同位位置类似地控制对这些寄存器的访问。如果允许 S 模式访问计数器寄存器并且在 scounteren 中设置相应位,则也允许 U 模式访问该寄存器。
\n
使他们成为可能的 pk 片段看起来很不错。“S”和“U”misa位在 RISC-V 特权规范中定义:
\n\n\n如果分别支持用户和管理员模式,则将设置 \xe2\x80\x9cU\xe2\x80\x9d 和 \xe2\x80\x9cS\xe2\x80\x9d 位。
\n
但请注意,RISC-V 特权规范还指定了(强调我的):
\n\n\n\n\n寄存器 mcounteren 和 scounteren 是 WARL 寄存器,如果实现 U 模式\n 和 S 模式,则必须实现这些寄存器。任何位都可能包含硬连线值零,表示在低特权模式下执行时,读取相应的计数器将导致非法指令异常。
\n
也许您的 CPU 没有实现hpmcounter3并且已将其硬连线为零?
要进一步解决此问题,您可以在 pk 内添加一个断点,即在单步汇编指令之前添加断点write_csr(scounteren, -1),以查看写入是否实际发生。如果发生这种情况,您可以检查寄存器以检查是否hpmcounter3硬连线为零。