了解 Cortex M4 上的周期计数
Understanding cycle counts on Cortex M4
我正在玩弄带有 Cortex M4 的 STM32F407,我正在通过在调用我在汇编中实现的函数(在 C 中)之前和之后直接读取 DWT_CYCCNT
来测量函数的循环计数.我想了解我得到的结果。
08000610 <my_function>:
8000610: f04f 20ff mov.w r0, #4278255360 ; 0xff00ff00
8000614: f04f 11ff mov.w r1, #16711935 ; 0xff00ff
8000618: ea81 0100 eor.w r1, r1, r0
800061c: ea81 0100 eor.w r1, r1, r0
8000620: ea81 0100 eor.w r1, r1, r0
8000624: ea81 0100 eor.w r1, r1, r0
8000628: 4770 bx lr
800062a: bf00 nop
执行上述(包括函数调用)需要21个周期。当我添加一条 eor
指令时:
08000610 <my_function>:
8000610: f04f 20ff mov.w r0, #4278255360 ; 0xff00ff00
8000614: f04f 11ff mov.w r1, #16711935 ; 0xff00ff
8000618: ea81 0100 eor.w r1, r1, r0
800061c: ea81 0100 eor.w r1, r1, r0
8000620: ea81 0100 eor.w r1, r1, r0
8000624: ea81 0100 eor.w r1, r1, r0
8000628: ea81 0100 eor.w r1, r1, r0
800062c: 4770 bx lr
800062e: bf00 nop
这突然变成了28个周期。
添加另一个 eor
不会改变循环计数(仍然是 28)。如预期的那样,再添加一个会使循环计数器增加 1(所以 29)。
为什么?
- 根据 ARM,
eor
应该总是 1 个周期。
- 我不明白 3 阶段管道如何解释这种行为。
- 指令都是字对齐的,所以没有问题。
- 我怀疑它与闪存访问有关,虽然我尝试将此代码放入 IWRAM 并从那里执行,但这并没有改变任何东西。
- 查看我的二进制文件的 objdump,我可以确认我的测量没有任何问题。
- 最后,我尝试了一些强制使用 16 位 Thumb 编码的方法,但这并没有帮助我理解发生了什么。
有什么想法吗? :)
(这道题有点类似于#18960524,但是没有mul
和加载指令可能会搞砸。)
核心没有缓存*,但是系统肯定有- 即ST的"ART Accelerator".
如 the TRM 的第 3.5.2 节所述,这个东西位于总线路径中,使 full-width(128 位)从闪存中获取数据,然后将这些指令提供给内核的 ICode界面,因为它要求他们。
第 3.5.1 节记录了闪存等待状态的数量与时钟速度和电压配置的关系,对于 STM32F407 这意味着最多 7 个周期的最坏情况。我将从问题的性质猜测您可能没有启用加速器的预取或指令缓存功能,这意味着每 16-bytes-worth 条指令您将暂停 n 循环等待下一个块从闪存中拖入时的等待状态。
数学比我现在想尝试计算的要复杂得多,但足以说明 21 个周期是至少 7 个执行周期、2 个管道重新填充(每个 1-3 个周期)的某种重叠组合对于调用和 return) 和至少 2*n 等待状态以从闪存中获取至少 2 个块。
现在,需要注意的一个重要事项是第一个函数的长度为 28 字节,而第二个函数的长度为 32 - 即恰好是两个 16 字节的块。第二个值得注意的事实:M4 的 ICode 接口只执行 32 位读取,然后它从中馈入管道的获取阶段(我假设它只是在管道只消耗第一个半字时转动它的拇指一个周期)。我非常有信心你在第二个例子中看到的是两者之间令人不快的互动——根据一些有根据的猜测,我想象这组情况:
- 由于流水线的取指阶段在 0x800062c 处拉入指令,ICode 接口在 0x8000630 处开始对下一个字的总线请求。
- 当流水线解码
bx lr
并从同一指令字中获取 0x800062e 时,ICode 接口稍作休息,但加速器现在正在等待闪存传送 0x8000630-0x8000640 的读取。
- 分支执行,ICode 接口现在必须等待 n-1 个周期,以便加速器完成读取,现在将是在它可以请求持有的任何地址
lr
之前被丢弃(然后再等待另一个 n 周期以实际获取它)。
看起来 FLASH_ACR 应该可以更清楚地了解您的配置是什么,如果您真的想尝试对每个周期进行核算 - 除非您将整个事情计时到 zero-wait-state ARM 的核心时序采用的配置 (note the first paragraph),您将不得不考虑的不仅仅是核心。更一般地说,我建议 "programming a microcontroller without thoroughly studying the vendor's documentation" 就在 "simply walking into Mordor" ;)
* Cortex-M7 是 ARM M-class 内核中第一个真正拥有自己内部缓存的内核。
我正在玩弄带有 Cortex M4 的 STM32F407,我正在通过在调用我在汇编中实现的函数(在 C 中)之前和之后直接读取 DWT_CYCCNT
来测量函数的循环计数.我想了解我得到的结果。
08000610 <my_function>:
8000610: f04f 20ff mov.w r0, #4278255360 ; 0xff00ff00
8000614: f04f 11ff mov.w r1, #16711935 ; 0xff00ff
8000618: ea81 0100 eor.w r1, r1, r0
800061c: ea81 0100 eor.w r1, r1, r0
8000620: ea81 0100 eor.w r1, r1, r0
8000624: ea81 0100 eor.w r1, r1, r0
8000628: 4770 bx lr
800062a: bf00 nop
执行上述(包括函数调用)需要21个周期。当我添加一条 eor
指令时:
08000610 <my_function>:
8000610: f04f 20ff mov.w r0, #4278255360 ; 0xff00ff00
8000614: f04f 11ff mov.w r1, #16711935 ; 0xff00ff
8000618: ea81 0100 eor.w r1, r1, r0
800061c: ea81 0100 eor.w r1, r1, r0
8000620: ea81 0100 eor.w r1, r1, r0
8000624: ea81 0100 eor.w r1, r1, r0
8000628: ea81 0100 eor.w r1, r1, r0
800062c: 4770 bx lr
800062e: bf00 nop
这突然变成了28个周期。
添加另一个 eor
不会改变循环计数(仍然是 28)。如预期的那样,再添加一个会使循环计数器增加 1(所以 29)。
为什么?
- 根据 ARM,
eor
应该总是 1 个周期。 - 我不明白 3 阶段管道如何解释这种行为。
- 指令都是字对齐的,所以没有问题。
- 我怀疑它与闪存访问有关,虽然我尝试将此代码放入 IWRAM 并从那里执行,但这并没有改变任何东西。
- 查看我的二进制文件的 objdump,我可以确认我的测量没有任何问题。
- 最后,我尝试了一些强制使用 16 位 Thumb 编码的方法,但这并没有帮助我理解发生了什么。
有什么想法吗? :)
(这道题有点类似于#18960524,但是没有mul
和加载指令可能会搞砸。)
核心没有缓存*,但是系统肯定有- 即ST的"ART Accelerator".
如 the TRM 的第 3.5.2 节所述,这个东西位于总线路径中,使 full-width(128 位)从闪存中获取数据,然后将这些指令提供给内核的 ICode界面,因为它要求他们。
第 3.5.1 节记录了闪存等待状态的数量与时钟速度和电压配置的关系,对于 STM32F407 这意味着最多 7 个周期的最坏情况。我将从问题的性质猜测您可能没有启用加速器的预取或指令缓存功能,这意味着每 16-bytes-worth 条指令您将暂停 n 循环等待下一个块从闪存中拖入时的等待状态。
数学比我现在想尝试计算的要复杂得多,但足以说明 21 个周期是至少 7 个执行周期、2 个管道重新填充(每个 1-3 个周期)的某种重叠组合对于调用和 return) 和至少 2*n 等待状态以从闪存中获取至少 2 个块。
现在,需要注意的一个重要事项是第一个函数的长度为 28 字节,而第二个函数的长度为 32 - 即恰好是两个 16 字节的块。第二个值得注意的事实:M4 的 ICode 接口只执行 32 位读取,然后它从中馈入管道的获取阶段(我假设它只是在管道只消耗第一个半字时转动它的拇指一个周期)。我非常有信心你在第二个例子中看到的是两者之间令人不快的互动——根据一些有根据的猜测,我想象这组情况:
- 由于流水线的取指阶段在 0x800062c 处拉入指令,ICode 接口在 0x8000630 处开始对下一个字的总线请求。
- 当流水线解码
bx lr
并从同一指令字中获取 0x800062e 时,ICode 接口稍作休息,但加速器现在正在等待闪存传送 0x8000630-0x8000640 的读取。 - 分支执行,ICode 接口现在必须等待 n-1 个周期,以便加速器完成读取,现在将是在它可以请求持有的任何地址
lr
之前被丢弃(然后再等待另一个 n 周期以实际获取它)。
看起来 FLASH_ACR 应该可以更清楚地了解您的配置是什么,如果您真的想尝试对每个周期进行核算 - 除非您将整个事情计时到 zero-wait-state ARM 的核心时序采用的配置 (note the first paragraph),您将不得不考虑的不仅仅是核心。更一般地说,我建议 "programming a microcontroller without thoroughly studying the vendor's documentation" 就在 "simply walking into Mordor" ;)
* Cortex-M7 是 ARM M-class 内核中第一个真正拥有自己内部缓存的内核。