arm cortexr8 获取cpu cycle clock的方法是什么?
在 ARM Cortex-R 系列里,可以用 ARM Performance Monitor Unit (PMU) 或 Generic Timer 来获取周期计数。
方法 1:使用 PMU Cycle Counter (CCNT)
Cortex-R8 内置 PMU (Performance Monitor Unit),其中有一个 Cycle Counter Register (PMCCNTR),它能直接计数 CPU cycle。
主要步骤:
启用 PMU
在 PMCR (Performance Monitor Control Register) 里设置 E 位,打开 PMU。
在 PMCNTENSET 里设置对应位,启用 cycle counter。
清零计数器(可选)
写 PMCCNTR 为 0。
读计数器
通过 MRC/MRS 指令读 PMCCNTR,得到 cycle 数。
代码示例(ARMv7-R/ARMv8-R 通用,需在特权模式):
static inline void enable_pmu(void) {
uint32_t value;
// Enable user-mode access to PMU (optional, if running in EL0)
asm volatile("MRC p15, 0, %0, c9, c14, 0" : "=r"(value));
value |= 1;
asm volatile("MCR p15, 0, %0, c9, c14, 0" :: "r"(value));
// Enable all counters
asm volatile("MRC p15, 0, %0, c9, c12, 0" : "=r"(value));
value |= 1; // enable all
asm volatile("MCR p15, 0, %0, c9, c12, 0" :: "r"(value));
// Enable cycle counter
value = 1 << 31;
asm volatile("MCR p15, 0, %0, c9, c12, 1" :: "r"(value));
}
static inline uint32_t get_cyclecount(void) {
uint32_t value;
asm volatile(“MRC p15, 0, %0, c9, c13, 0” : “=r”(value));
return value;
}
调用:
enable_pmu();
uint32_t start = get_cyclecount();
// … code to measure …
uint32_t end = get_cyclecount();
uint32_t cycles = end - start;
方法 2:使用 Generic Timer (ARMv8-R 特性)
Cortex-R8 属于 ARMv8-R 架构,支持 Generic Timer,其中有一个 64-bit CNTVCT (virtual count register),通常以固定频率(不是 CPU clock,而是 CNTFRQ 指定的时钟)递增。
读取方法:
static inline uint64_t get_cntvct(void) {
uint64_t cnt;
asm volatile(“MRS %0, CNTVCT_EL0” : “=r”(cnt));
return cnt;
}
static inline uint32_t get_cntfrq(void) {
uint32_t frq;
asm volatile(“MRS %0, CNTFRQ_EL0” : “=r”(frq));
return frq; // Hz
}
注意:CNTVCT 并不是严格的 CPU cycle counter,而是固定频率计数器。但它常用于计时,因为不受 CPU 变频影响。
✅ 结论:
如果你要精确获取 CPU 周期 (cycle clock) → 用 PMU Cycle Counter (PMCCNTR)。
如果你只要一个稳定的 时间基准 → 用 Generic Timer CNTVCT_EL0。
在 Cortex-R8 上,PMU cycle counter (PMCCNTR) 获取到的 CPU cycle 是基于 CPU core clock (CPU pipeline 的输入时钟),也就是 处理器运行指令的时钟频率。
细节说明:
PMCCNTR 的递增速率
默认情况下,它每经过 一个 CPU 时钟周期 递增 1。
如果 PMCR.D 位(Divide by 64)被置位,那么计数器会以 CPU_clk / 64 的速率递增。
所以正常情况下就是 CPU 周期数,除非你启用了除64分频。
CPU core clock 与外部时钟
Cortex-R8 的 CPU clock 由 SoC 内部时钟树生成,通常来自 PLL。
这个频率不一定等于外设时钟,也可能和 CNTFRQ(Generic Timer 的基准频率)不同。
所以:
PMCCNTR → 跟随 CPU pipeline 时钟 (CPU frequency)。
CNTVCT (Generic Timer) → 跟随固定 reference clock (CNTFRQ),独立于 CPU。
验证方法
如果你不确定 CPU 的实际运行频率,可以:
先读 PMCCNTR,
再延时一段已知的时间(例如用 CNTVCT 计时 1 ms),
再次读 PMCCNTR,差值除以时间就是 CPU cycle 频率(Hz)。
✅ 总结:
PMCCNTR 反映 CPU 核心时钟 (core clock) 的周期数。
如果你的 SoC CPU 频率是 600 MHz,那么 PMCCNTR 每秒会递增 600M。
如果打开了 Divide by 64,则变为 600M / 64 ≈ 9.375M 每秒。
如何用 PMCCNTR + CNTVCT 自动测出 Cortex-R8 当前的 CPU 频率
ChatGPT 说:
可以的 👍。我们可以利用:
PMCCNTR:记录 CPU cycle 数。
CNTVCT + CNTFRQ:提供稳定的时间基准。
思路:
读 CNTFRQ → 得到 Generic Timer 的频率(Hz)。
读 CNTVCT(起始时间)、PMCCNTR(起始 cycles)。
等待一段时间(比如 CNTVCT 增加一段固定值)。
再读 CNTVCT(结束时间)、PMCCNTR(结束 cycles)。
根据 ΔPMCCNTR / Δ时间 = CPU 频率。
示例代码(ARMv8-R / Cortex-R8)
#include <stdint.h>
static inline void enable_pmu(void) {
uint32_t value;
// 允许 PMU 使用
asm volatile("MRC p15, 0, %0, c9, c12, 0" : "=r"(value));
value |= 1; // E = 1, 使能 PMU
asm volatile("MCR p15, 0, %0, c9, c12, 0" :: "r"(value));
// 使能 cycle counter
value = 1 << 31;
asm volatile("MCR p15, 0, %0, c9, c12, 1" :: "r"(value));
// 清零 cycle counter
asm volatile("MCR p15, 0, %0, c9, c13, 0" :: "r"(0));
}
static inline uint32_t get_cyclecount(void) {
uint32_t value;
asm volatile(“MRC p15, 0, %0, c9, c13, 0” : “=r”(value));
return value;
}
static inline uint64_t get_cntvct(void) {
uint64_t cnt;
asm volatile(“MRS %0, CNTVCT_EL0” : “=r”(cnt));
return cnt;
}
static inline uint32_t get_cntfrq(void) {
uint32_t frq;
asm volatile(“MRS %0, CNTFRQ_EL0” : “=r”(frq));
return frq;
}
// 计算 CPU 频率 (Hz)
uint64_t measure_cpu_freq(void) {
enable_pmu();
uint32_t freq_ref = get_cntfrq(); // Generic Timer 频率 (Hz)
uint64_t t1 = get_cntvct();
uint32_t c1 = get_cyclecount();
// 等待一段参考时间,比如 1/100 秒
uint64_t wait_ticks = freq_ref / 100; // ~10ms
uint64_t target = t1 + wait_ticks;
while (get_cntvct() < target);
uint64_t t2 = get_cntvct();
uint32_t c2 = get_cyclecount();
uint64_t delta_t = t2 - t1; // Generic Timer ticks
uint64_t delta_c = (uint32_t)(c2 - c1); // CPU cycles
// 换算 CPU Hz
uint64_t cpu_hz = delta_c * freq_ref / delta_t;
return cpu_hz;
}
使用示例
uint64_t cpu_hz = measure_cpu_freq();
printf(“CPU Frequency = %llu Hz\n”, cpu_hz);
⚠️ 注意:
PMCCNTR 只有 32-bit,如果 CPU >1GHz,长时间测量可能溢出,所以等待时间别太长。
如果你的 SoC 打开了 PMCR.D (divide by 64),记得再乘 64 才是实际 CPU cycle。
大部分 Cortex-M 系列 MCU 没有像 Cortex-A/R 那样的 PMU(Performance Monitor Unit,带 cycle counter 的寄存器组)。
但是,Cortex-M 内核一般都提供了一个叫做 DWT (Data Watchpoint and Trace Unit) 的调试功能,其中包含了 CYCCNT 寄存器,它能实现类似 PMU cycle counter 的功能。
SysTick Timer(替代方案)
所有 Cortex-M 内核都有 SysTick 定时器。
它基于 CPU clock 运行,可以当作一个“简易周期计数器”,不过是 递减计数器,而且通常是 24-bit。
精度比 DWT_CYCCNT 差一些,但在没有 DWT 的内核上可用。
总结
Cortex-M3/M4/M7/M33 等中高端 MCU → 有 DWT_CYCCNT,等价于 cycle counter。
Cortex-M0/M0+ 等低端 MCU → 没有 DWT,只能用 SysTick 来近似计时。
Cortex-M 系列没有完整的 PMU 事件统计功能(比如 cache miss 计数、branch 预测统计等),只能用 CYCCNT 来做 CPU cycle 计数。
u32value = __MRC(15, 0, 9, 13, 0); 和asm volatile(“MRC p15, 0, %0, c9, c13, 0” : “=r”(value)); 写法有何区别
ChatGPT 说:
好问题 👌,你提到的两种写法:
u32value = __MRC(15, 0, 9, 13, 0);
和
asm volatile(“MRC p15, 0, %0, c9, c13, 0” : “=r”(value));
实际上都在执行同一个 ARM CP15 协处理器寄存器访问指令:
MRC p15, 0, Rt, c9, c13, 0
只是写法层面有所不同:
- __MRC() 写法
这是 编译器内置 intrinsic(内建函数)。
通常由 ARMCC(ARM Compiler)、Keil、IAR 等编译器提供。
它会被编译器展开成对应的 MRC 指令。
优点:
可读性更高,直接表达“读 CP15 的寄存器”。
编译器能更好地做语义检查,移植性较好(只要支持 ARM intrinsic)。
缺点:
依赖编译器,某些 GCC 环境可能没有 __MRC。
- asm volatile(“MRC …”) 写法
这是 GCC 内联汇编(或 clang 内联汇编)。
直接告诉编译器发出 MRC 指令,并把结果放到 C 变量里。
优点:
完全可控,和编译器无关。
在 GCC/clang 等工具链里通用。
缺点:
可读性稍差,需要写寄存器名。
编译器对语义不了解,可能不如 intrinsic 优化友好。
- 生成的汇编比较
两种方式编译后生成的指令是一样的(都是 MRC p15, 0, rX, c9, c13, 0)。
区别只在 代码可移植性 & 可读性:
如果你写的是 Keil/ARMCC 工程 → 推荐用 __MRC。
如果你写的是 GCC 工程(如裸机、Linux 内核) → 推荐 asm volatile(“MRC …”)。
✅ 总结
本质一样:两者都会生成相同的 MRC 指令。
差异:__MRC 是编译器内置函数,更高层;asm volatile 是手写汇编,更通用
ARM CP15 协处理器是什么?位置在哪里?作用是什么?为什么设计这个协处理器
ChatGPT 说:
好问题 👍,这涉及 ARM 经典架构里一个很重要的东西。
- CP15 协处理器是什么?
CP15,全称 System Control Coprocessor(系统控制协处理器)。
它是 ARMv4/v5/v6/v7(特别是 Cortex-A/R)架构里一个 专门用于系统控制和配置的协处理器。
在汇编里你常看到的 MRC p15,…、MCR p15,… 就是对 CP15 的访问。
- 位置在哪里?
CP15 不是外部芯片,而是 ARM CPU 内核里的一部分硬件单元。
它和主流水线、MMU、Cache 控制逻辑等紧密结合。
程序员不能直接通过地址总线访问 CP15(它不在 memory map 中),只能通过专门的指令(MRC/MCR)读写。
- 作用是什么?
CP15 主要提供系统级控制和状态寄存器,常见功能包括:
MMU(Memory Management Unit)配置
页表基地址(TTBR)
域访问控制
内存属性(cacheable/bufferable 等)
Cache/TLB 管理
开启/关闭 I-Cache / D-Cache
清空 / 失效 TLB / Cache line
CPU 系统控制
启用/禁用 MMU、Cache、Branch Prediction
异常向量表基地址
性能监控 (PMU)
Cycle counter
Event counters
安全与调试功能
控制 TrustZone
调试寄存器
总之,CP15 是 ARM 处理器的大管家,负责系统层面的控制,而不是普通的通用寄存器运算。
- 为什么设计成“协处理器”?
历史原因 + 架构扩展性:
历史背景:
ARM 早期架构支持最多 16 个“协处理器”(CP0~CP15),CPU 主核通过 MCR/MRC 指令和协处理器交互。
CP0:通常留作浮点协处理器 (FPU)。
CP14:调试相关。
CP15:固定分配为系统控制协处理器。
优势:
指令集简单化:不用为系统寄存器设计新的 ARM 指令,而是用通用的 MCR/MRC 接口。
扩展性强:厂商可以在 CP10、CP11 挂载 FPU/NEON,在 CP14 挂调试逻辑,不影响主核指令集。
权限控制:访问 CP15 通常需要特权模式(Supervisor/Hypervisor),保证用户态程序无法随意改 MMU/Cache。
- 现状
在 ARMv8-A (AArch64) 里,协处理器机制被弱化,CP15 寄存器统一并入系统寄存器空间,通过 MRS/MSR 指令访问,而不再用 MRC/MCR。
但是在 ARMv7-A/R 和之前的 32-bit ARM 中,CP15 依然是系统控制的核心。
✅ 一句话总结:
CP15 = ARM CPU 内部的“系统控制协处理器”,负责 MMU/Cache/异常向量/性能监控等全局控制。设计成协处理器是为了扩展性和架构简洁性。
在这里插入图片描述

828

被折叠的 条评论
为什么被折叠?



