ARM Cortex-M7简单延迟循环汇编时序结果如何解释?
测试背景与现象
我了解ARM Cortex-M7的周期时序并未公开,因此在STM32H750-DK开发板上用DWT计数器测量简单延迟循环的周期数,得到以下现象:
- 无论使用16位指令(操作R5寄存器)还是32位指令(操作R8及以上寄存器),Cortex-M7每个周期可执行两条指令;
- 首次运行循环时周期数显著偏高,后续重复运行时,无论N取值多少,都会额外增加6个周期;
- 添加nop指令后,N=64000时首次运行周期为96030,后续稳定为96006。
测试代码及数据如下:
//-------------- 未测量代码 -------------------------- // ldr r5,=N // ------------- 周期测量范围内的代码 ------ // tloop: subs r5,r5,#1 // bne tloop // ------------- 代码结束 ------------------------ /* // 时序数据 - 通常取第二次及以后的重复运行结果 // (首次运行的周期数标注在括号内) ╔═══════╤════════════════╗ ║ N │ DWT_CYCCNT(首次)║ ╠═══════╪════════════════╣ ║ 50 │ 56 (78) ║ ╟───────┼────────────────╢ ║ 100 │ 106 (128) ║ ╟───────┼────────────────╢ ║ 200 │ 206 ║ ╟───────┼────────────────╢ ║ 500 │ 506 ║ ╟───────┼────────────────╢ ║ 1000 │ 1006 ║ ╟───────┼────────────────╢ ║ 64000 │ 64006 (64028) ║ ╚═══════╧════════════════╝ 注释:区别在于操作R5的是16位指令,操作R8及以上的是32位指令,但二者时序完全一致。 添加nop指令后,N=64000时,结果为96030(首次运行)和96006(后续运行)。 */
结果解读与流水线分析
你的分支预测猜测完全正确,结合Cortex-M7的流水线特性,可拆解为以下几点:
1. 双指令执行能力
Cortex-M7采用超标量流水线设计,具备双发射能力:在单个周期内可同时执行两条独立指令(只要不存在数据依赖、指令类型兼容等限制)。你的测试中,subs r5,r5,#1和bne tloop属于无数据依赖的指令对,因此能被双发射执行,每个循环仅消耗1个周期,这也解释了N个循环的基础周期数为N,加上固定的6个额外周期。
2. 首次运行的额外周期:分支预测未命中
Cortex-M7带有静态分支预测器,首次执行bne tloop时,预测器尚未学习到该分支的跳转规律,会出现分支预测未命中。分支未命中会导致流水线清空、重新取指,带来额外的周期开销——这就是首次运行时周期数偏高的原因(比如N=50时多了22个周期,N=64000时多了22个周期,符合分支未命中的固定开销特征)。后续重复运行时,预测器已掌握分支持续跳转的规律,预测命中率接近100%,因此周期数稳定。
3. 固定额外6个周期的来源
这6个周期是循环的初始化与收尾开销:包括循环进入前的寄存器准备、流水线启动延迟,以及分支指令的固定执行开销(即使预测命中,分支指令本身也会占用少量周期)。
4. 添加nop后的周期变化
添加nop后,循环内变为三条指令:subs、nop、bne。由于超标量双发射特性,每两个周期可执行三条指令(比如subs+nop占1周期,bne+subs占1周期,以此类推),因此N=64000时总基础周期数为64000*1.5=96000,加上固定的6个额外周期,正好匹配后续运行的96006;首次运行同样因为分支预测未命中,额外增加24个周期(96030-96006=24),符合预测未命中的开销规律。
补充Cortex-M7流水线关键信息
- Cortex-M7的流水线长度为6级,包含取指、译码、执行等阶段;
- 双发射仅支持特定指令组合:比如两个算术逻辑指令、一个算术指令+一个分支指令,不支持两个分支指令同时发射;
- 静态分支预测器默认对向后跳转(如循环分支)预测为跳转,向前跳转预测为不跳转,但首次执行时仍会有学习过程;
- 指令长度(16位/32位)不影响双发射能力,因为取指单元会自动处理Thumb/Thumb-2指令的解码与发射。
内容的提问来源于stack exchange,提问作者user2064070

