ARM M4每周期指令数(IPC)计数器

14

我想要计算在ARM Cortex-M4(或Cortex-M3)处理器上每个周期执行的指令数。

所需的是:我想要进行性能分析的代码在运行时执行的指令数以及代码执行所需的周期数

1 - 周期数

使用周期计数器非常简单明了。

volatile unsigned int *DWT_CYCCNT  ;
volatile unsigned int *DWT_CONTROL ;
volatile unsigned int *SCB_DEMCR   ;

void reset_timer(){
    DWT_CYCCNT   = (int *)0xE0001004; //address of the register
    DWT_CONTROL  = (int *)0xE0001000; //address of the register
    SCB_DEMCR    = (int *)0xE000EDFC; //address of the register
    *SCB_DEMCR   = *SCB_DEMCR | 0x01000000;
    *DWT_CYCCNT  = 0; // reset the counter
    *DWT_CONTROL = 0; 
}

void start_timer(){
    *DWT_CONTROL = *DWT_CONTROL | 1 ; // enable the counter
}

void stop_timer(){
    *DWT_CONTROL = *DWT_CONTROL | 0 ; // disable the counter    
}

unsigned int getCycles(){
    return *DWT_CYCCNT;
}

main(){
    ....
    reset_timer(); //reset timer
    start_timer(); //start timer
    //Code to profile
    ...
    myFunction();
    ...
    stop_timer(); //stop timer
    numCycles = getCycles(); //read number of cycles 
    ...
}

2 - 指令数量

我在网上找到了一些文档,用于计算Arm Cortex-M3和Cortex-M4执行的指令数量(链接):

  # instructions = CYCCNT - CPICNT - EXCCNT - SLEEPCNT - LSUCNT + FOLDCNT

他们提到的寄存器在这里有详细记录here(第11-13页),这些是访问它们的内存地址:
DWT_CYCCNT   = 0xE0001004
DWT_CONTROL  = 0xE0001000
SCB_DEMCR    = 0xE000EDFC
DWT_CPICNT   = 0xE0001008
DWT_EXCCNT   = 0xE000100C
DWT_SLEEPCNT = 0xE0001010
DWT_LSUCNT   = 0xE0001014
DWT_FOLDCNT  = 0xE0001018

DWT_CONTROL寄存器用于启用计数器,特别是循环计数器,如此处所述。

但是,当我试图将所有内容放在一起以计算每个周期执行的指令数量时,我没有成功。

这里有一个关于如何从gdb中使用它们的简短指南。

不容易的是,一些寄存器是8位寄存器(DWT_CPICNT,DWT_EXCCNT,DWT_SLEEPCNT,DWT_LSUCNT,DWT_FOLDCNT),当它们溢出时会触发事件。我没有找到收集该事件的方法。没有代码片段来解释如何做到这一点,也没有适用于此的中断例程。

此外,似乎使用gdb的监视点在这些寄存器的地址上无法正常工作。gdb无法在寄存器更改值时停止。例如,在DWT_LSUCNT上:

(gdb) watch *0xE0001014

更新:我在GitHub上找到了这个项目,解释了如何使用DWT、ITM和ETM单位。但是我没有检查它是否有效!我会发布更新。
有关如何使用它们的任何想法吗?
谢谢!

1
也许太明显了,但在执行任何其他函数之前,您总是调用reset_timer(),对吗?您能发布调用代码作为最小示例吗? - Lundin
4
建议将寄存器声明为#define DWT_CYCCNT (*(volatile uint32_t*)0xE0001004ul) - Lundin
这些事件不是会触发调试监视器异常的调试事件吗? - Notlikethat
我不知道会生成什么类型的事件。在 ARM 文档 链接 中,他们谈到了事件,但我没有找到任何收集它们的方法。 - FrankPak
*DWT_CONTROL = *DWT_CONTROL | 0 ; // disable the counter 是无操作指令。 - markrages
3个回答

5
您提供的代码示例在清除启用位时存在问题。您应该使用“AND”而不是“OR”来清除该位:
*DWT_CONTROL = *DWT_CONTROL & 0xFFFFFFFE ; // disable the counter by clearing the enable bit

2

我认为如果你想测量准确的循环次数,使用调试器是一个不错的选择。Keil-MDK可以累加状态寄存器并且不会溢出。在调试器中得到的结果与使用DWT得到的结果相同。

如果你想测量其他值,比如FOLDCNT,可以在Keil-MDK中使用跟踪功能 -> 调试 -> 设置 -> 跟踪 -> 启用跟踪。

这样,在调试时,在跟踪窗口选择跟踪事件,Keil就可以收集并将那8位寄存器的值相加。

看起来有点愚蠢,但我不知道如何收集溢出事件,我认为这个事件只能被发送到ITM,因为DWT或ITM都是程序之外的独立组件。如果我们想在客户程序中收集事件,那么收集动作必须会影响结果的准确性。

ITM?ETM?CoreSight?DWT?AHB?


你找到如何收集程序执行中的计数器溢出事件而不影响它们了吗? - Sil

0

我不知道如何按照你想要的方式使用寄存器。但是,这是我处理测量周期的方法。

确保在SysTick控制和状态寄存器中启用计数器。通过适当的头文件,您应该可以将SysTick寄存器作为结构体访问。

测量计数器函数所需的周期数。稍后将从任何测量值中减去此数。

  SysTick->VAL = 0; // set 0
  // Measure delay on measurement  
  __disable_irq();
  a = (uint32_t) SysTick->VAL;
  //... measuring zero instructions
  b = (uint32_t) SysTick->VAL;
  __enable_irq();
  measure_delay = a - b;

现在测量一个函数。

SysTick->VAL = 0;
__disable_irq();
a = (uint32_t) SysTick->VAL;

//Assuming this function doesn't require interruptions

// INSERT CODE TO BE PROFILED
function_to_be_examined();

b = (uint32_t) SysTick->VAL;
__enable_irq();
cycles_profiled_code = a - b - measure_delay;

希望它有所帮助。


请注意,这是使用非常粗略的粒度进行测量的,因为SysTick“通常”设置为每1毫秒溢出。问题中的代码测量确切的周期计数。 - AVH
好的,已经注意到了。正确的。大致上,如果处理器速度为100MHz,1毫秒可以测量约100,000个周期。这种方法对于需要几万个周期的函数非常精确。 - Toani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接