在ARM Cortex M4(或M3)上的循环计数器?

18
我正在尝试对一个C函数进行性能分析(该函数是从中断调用的,但我可以将其提取出来并在其他地方进行分析),这个函数运行在Cortex M4上。
有哪些可能性可以计算这个函数通常使用的周期数? 函数应该在大约4000个周期内运行,所以RTC不是一个选择,我猜测从反汇编中手动计算周期可能会很痛苦 - 而且只有在平均化的情况下才有用,因为我想在典型的流程和典型的闪存/内存使用模式下进行性能分析。
我听说过周期计数器寄存器和MRC指令,但它们似乎只适用于A8/11。我在Cortex-Mx微控制器中没有看到这样的指令。

1
大多数微控制器都有定时器,Cortex-M3在核心中有一个(如果我没记错的话,M4没有或M0没有其中之一)。http://github.com/dwelch67 我有许多示例,所有示例都从闪烁LED开始逐步向使用不同的定时器等方面发展。mbed和stm32f4d是Cortex-M示例(还有其他示例)。 - old_timer
5个回答

25

请看定义在这里的DWT_CYCCNT寄存器。请注意,该寄存器是与实现相关的。芯片供应商是谁?我知道STM32实现提供了这组寄存器。

这篇帖子提供了使用DWT Cycle Counter Register进行计时的说明。(请查看2019年12月11日下午6:29发布的帖子)

这篇Stack Overflow帖子也是一个使用DWT_CYCCNT的示例。


我看到它很快,但认为它只是一个比较器,用于计数器,每次生成给定值的中断。因此,我只会得到一个不精确的计数 - 每500个周期中断一次,或者对性能产生很大影响,总是中断代码?如何访问其值或使用它?(它确实是一个STM32F4芯片) - makapuf
@makapuf:请查看编辑后的帖子。您应该能够使用此寄存器获得精确的时间。 - Throwback1986
谢谢,听起来不错!我会尽快尝试。我会发布结果。 - makapuf
1
请在答案中包含链接中的内容,以防它们再次失效。 - BenCr
1
作为后人的跟进,这个链接非常好:https://dev59.com/O2Yr5IYBdhLWcg3wy9SA - Throwback1986
1
此帖子的链接已失效。 - bunkerdive

3
如果您的部件包含CoreSight嵌入式跟踪宏单元,并且您拥有适当的跟踪能力调试器硬件和软件,则可以直接对代码进行分析。当然,具备跟踪能力的调试硬件更加昂贵,并且您的板子需要设计使跟踪端口引脚可用于调试头上。由于这些引脚通常被多路复用到其他功能,因此可能不总是可行或实用。
否则,如果您的工具链包括一个周期精确的模拟器(例如Keil uVision中提供的),则可以使用它来分析代码的时间。模拟器提供了调试、跟踪和分析功能,通常比芯片上可用的功能更强大和灵活,因此即使您拥有跟踪硬件,模拟器仍可能是更容易的解决方案。

我在Linux上使用GNU工具链,因此使用gcc/gdb。 - makapuf
也许有一个稍微复杂的解决方案,那就是使用Windows机器或在VirtualBox中运行的Windows虚拟机,然后使用Keil uVision的评估版本和Codesourcery的GNU ARM工具链。 评估限制只针对ARM RealView编译器/链接器而非IDE,我不确定调试器/模拟器是否受到限制,但即使它们受到限制,代码大小限制为32k,因此您可能可以测试该函数,如果不能测试整个应用程序。详情请见:http://www.keil.com/appnotes/docs/apnt_199.asp。但这可能会带来太多麻烦。 - Clifford
谢谢,但这只是一个模拟,基于一个完美的内存模型(可以作为第一次近似值很好,但是在内存总线争用的情况下,我更信任真实的交易(我也会大量使用DMA传输...))。 - makapuf
@makapuf:我没有使用过ARMulator,但是如果您的代码没有外围硬件依赖,我建议它可能会有用。但是它是否支持Cortex-M呢?例如,Keil的模拟器的一个优点是,它为所有直接支持的基于ARM的芯片模拟了芯片上的外设。自从Cortex之前,甚至中断控制器都不是核心的一部分,这非常有用。还要考虑一下OVPsim,它支持Cortex-M,而且可以在Linux上运行。 - Clifford
我会尽快尝试使用Keil。 - makapuf
显示剩余2条评论

2
这只是更加简单:

[代码]

#define start_timer()    *((volatile uint32_t*)0xE0001000) = 0x40000001  // Enable CYCCNT register
#define stop_timer()   *((volatile uint32_t*)0xE0001000) = 0x40000000  // Disable CYCCNT register
#define get_timer()   *((volatile uint32_t*)0xE0001004)               // Get value from CYCCNT register

/***********
* How to use:
*       uint32_t it1, it2;      // start and stop flag                                             

        start_timer();          // start the timer.
        it1 = get_timer();      // store current cycle-count in a local

        // do something

        it2 = get_timer() - it1;    // Derive the cycle-count difference
        stop_timer();               // If timer is not needed any more, stop

print_int(it2);                 // Display the difference
****/

适用于Cortex M4:在CJMCU板上的STM32F407VGT,并且只计算所需的周期数。


适用于MK22FN512xxx12。 - Marc

2

扩展前面的答案,提供一个DWT_CYCCNT示例(STM32)在main中(类似于我其他帖子)。

注意:我也添加了一个延迟方法。您可以通过调用STOPWATCH_START,运行stopwatch_delay(ticks),然后调用STOPWATCH_STOP并使用CalcNanosecondsFromStopwatch(m_nStart, m_nStop)验证 stopwatch_delay 。根据需要调整ticks

uint32_t m_nStart;               //DEBUG Stopwatch start cycle counter value
uint32_t m_nStop;                //DEBUG Stopwatch stop cycle counter value

#define DEMCR_TRCENA    0x01000000

/* Core Debug registers */
#define DEMCR           (*((volatile uint32_t *)0xE000EDFC))
#define DWT_CTRL        (*(volatile uint32_t *)0xe0001000)
#define CYCCNTENA       (1<<0)
#define DWT_CYCCNT      ((volatile uint32_t *)0xE0001004)
#define CPU_CYCLES      *DWT_CYCCNT
#define CLK_SPEED         168000000 // EXAMPLE for CortexM4, EDIT as needed

#define STOPWATCH_START { m_nStart = *((volatile unsigned int *)0xE0001004);}
#define STOPWATCH_STOP  { m_nStop = *((volatile unsigned int *)0xE0001004);}


static inline void stopwatch_reset(void)
{
    /* Enable DWT */
    DEMCR |= DEMCR_TRCENA; 
    *DWT_CYCCNT = 0;             
    /* Enable CPU cycle counter */
    DWT_CTRL |= CYCCNTENA;
}

static inline uint32_t stopwatch_getticks()
{
    return CPU_CYCLES;
}

static inline void stopwatch_delay(uint32_t ticks)
{
    uint32_t end_ticks = ticks + stopwatch_getticks();
    while(1)
    {
            if (stopwatch_getticks() >= end_ticks)
                    break;
    }
}

uint32_t CalcNanosecondsFromStopwatch(uint32_t nStart, uint32_t nStop)
{
    uint32_t nDiffTicks;
    uint32_t nSystemCoreTicksPerMicrosec;

    // Convert (clk speed per sec) to (clk speed per microsec)
    nSystemCoreTicksPerMicrosec = CLK_SPEED / 1000000;

    // Elapsed ticks
    nDiffTicks = nStop - nStart;

    // Elapsed nanosec = 1000 * (ticks-elapsed / clock-ticks in a microsec)
    return 1000 * nDiffTicks / nSystemCoreTicksPerMicrosec;
} 

void main(void)
{
    int timeDiff = 0;
    stopwatch_reset();

    // =============================================
    // Example: use a delay, and measure how long it took
    STOPWATCH_START;
    stopwatch_delay(168000); // 168k ticks is 1ms for 168MHz core
    STOPWATCH_STOP;

    timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
    printf("My delay measured to be %d nanoseconds\n", timeDiff);

    // =============================================
    // Example: measure function duration in nanosec
    STOPWATCH_START;
    // run_my_function() => do something here
    STOPWATCH_STOP;

    timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
    printf("My function took %d nanoseconds\n", timeDiff);
}

1

这取决于您的ARM实现。

我在stm32F4核心上使用了SysTick->VAL寄存器。这是循环精确的。

解释结果时,请注意:

  • 考虑到包装。
  • 它是倒数计数,而不是正数计数。

限制: 这仅适用于大于单个SysTick的时间间隔。


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