访问 RISC-V 中的硬件性能计数器

Accessing Hardware Performance Counters in RISC-V

我想检测一个程序来访问硬件性能计数器。

我编译了一个基本的 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;
}

这是一个基本代码,只是为了看看我是否可以计算缓存未命中数。此代码给我一个 "illegal instruction" 错误。

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!

最初从 hpmcounter3 读取的行是从 mhpmcounter3 读取的,结果出现了同样的错误。

与另一位开发人员交谈,一个可能的原因是 riscv-pk 运行s 二进制文件处于 U 模式,因此调用 m* 程序集后的非法指令指令(运行 在机器中,M 模式)是预期的。所以我更改了二进制文件以注释掉对 mphmevent3 的第一次写入,并且只是从 hpmcounter3 中进行了裸读,但仍然出现非法指令错误。这个结果的理论是 mcounterenscounteren 寄存器没有启用,所以我无法访问计数器。但是,查看 riscv-pkminit.c 内部,我发现:

  // Enable user/supervisor use of perf counters
  if (supports_extension('S'))
    write_csr(scounteren, -1);
  if (supports_extension('U'))
    write_csr(mcounteren, -1);

我不确定,但我觉得这样可以启用计数器。

关于我应该如何访问硬件性能计数器有什么想法吗?

是的,pk 在用户模式下运行二进制文件,因此预计从用户 space 访问机器级寄存器会产生非法指令异常:

Attempts to access a CSR without appropriate privilege level or to write a read-only register also raise illegal instruction exceptions.

(RISC-V Privileged spec 1.12-draft 2020-01-13,第 2.1 节 CSR 地址映射约定)

hpmcounter* 寄存器可从用户模式访问(参见 Table 2.2 当前分配的 RISC-V 用户级 CSR 地址,RISC-V 特权规范),当且仅当它们在下一个更高模式下启用:

If S-mode is implemented, the same bit positions in the scounteren register analogously control access to these registers while executing in U-mode. If S-mode is permitted to access a counter register and the corresponding bit is set in scounteren, then U-mode is also permitted to access that register.

启用它们的 pk 片段看起来不错。 'S' 和 'U' misa 位在 RISC-V 特权规范中定义:

The “U” and “S” bits will be set if there is support for user and supervisor modes respectively.

但请注意,RISC-V 特权规范还指定(强调我的):

Registers mcounteren and scounteren are WARL registers that must be implemented if U-mode and S-mode are implemented. Any of the bits may contain a hardwired value of zero, indicating reads to the corresponding counter will cause an illegal instruction exception when executing in a less-privileged mode.

也许您的 CPU 没有实现 hpmcounter3 并且它硬接线为零?

要进一步解决此问题,您可以在 pk 内添加一个断点,即就在 write_csr(scounteren, -1) 和单步汇编程序指令之前,以查看写入是否实际发生。如果它们发生了,您可以检查寄存器以检查 hpmcounter3 是否硬接线为零。