对于Cortex M0 M3 M4 的我知道可以在其《Technical Reference Manual 》> Programmers Model > Instruction set summary 里面查看
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0432c/CHDCICDF.html
但是到了M7, 他告诉我去ARM-V7-M的手册中找
“The processor implements the ARMv7-M instruction set and features provided by the ARMv7E-M architecture profile. For more information about the ARMv7-M instructions, see the ARM®v7-M Architecture Reference Manual.” -----《ARM Cortex-M7 Processor Technical Reference Manual》Programmers Model > Instruction set summary > Binary compatibility with other Cortex processors
但是在 V7-M 手册里,也没有找到具体的指令执行周期。请问应该去哪里找呢
这个问题很好,我请工程师帮忙看一下
这是个很有趣的问题,如果你仔细留意会发现理论上,Cortex-M7就无法提供准确的指令执行周期信息,因为Cortex-M7的流水线是超标量的(Super-scalar),对于前后满足条件的两条指令来说,Cortex-M7的流水线可以同时执行。在这种情况下,如何计算指令的平均周期呢?
要知道,程序员了解指令执行周期是为了能够精确的计算某一段机器码的执行时间。但是如果这些指令中存在可以同时执行的部分,那么单个指令在流水线中执行所需要的时间就变得没有意义了。
实际上,这个问题比想象的要略微复杂一点。因为前后两条指令还可能存在数据依赖(Data Dependency)或者是资源竞争(Structural Hazard)的情况。所以,指令执行周期的平均值也是根据前后指令的组合不同而变动的。
感谢回复,我对于M7的 dual-issue 不是特别理解。
对于能够同时执行的两个指令,如何才算是满足条件?
如果两条符合条件的指令同时执行,他的执行时间与一条指令单独执行相比如何?
另外不管怎么说,对于一个具体的指令,我从MIPS和Cortex M0过来的,觉得应该是有一个固定的执行顺序的,耗费的机器周期应该是固定的,或者说是可预测的。即便是可以同时与其他指令一起执行那也是可预测的。所以这种情况,一是为什么不出一个表,一个指令单独执行时候所耗机器周期如何,总归是希望能有个参考,能在编程和优化的时候给点参考。二是出一个2维表,一个指令与其他指令执行同时执行下机器周期如何。
我在英文论坛上也问了这个问题,目前的主流意见还是写完程序后自己手测。
两条指令之所以安排他们同时执行,就是因为两条指令同时的执行时间小于二者之和啊。应该说,和最长的那条指令执行时间一样长——你会发现,双发射的流水线连回答“他的执行时间与一条指令单独执行相比如何?”都是不容易做到的。
如果你从MIPS过来,应该更多接触过超标量,甚至是Out of Order的流水线。超标量,甚至是Out of Order的流水线理论上都无法精确的标注一条指令的执行周期,这种情况下,为了评估系统吞吐量,往往用另外一个概念,也就是CPI(Cycle per Instruction)或者IPC(Instruction per Cycle)。对于Cortex-M0这种顺序执行的流水线来说,CPI一定是 >= 1的,但是对于超标量以及OOO的流水线来说,CPI是可以做到 < 1的。
至于你说的参考表,如果你非要一个大体的参考,那么可以不那么严格的参照Cortex-M3/M4的数据。
至于你说的出一个二维表格……你有考虑过这个表格有多大么?从排列组合的角度来说,这是一个可怕的全排列啊……当你要考虑两个指令的可能性的时候,实际上要考虑三条指令的全排列……也就是说从所有支持的指令中任意找出3条指令,然后对他们进行全排列……
这是没有意义的。真正有意义的是,计算一条流水线的CPI最大值和最小值。
原来如此,那么CPI的计算是如何进行的呢?写完程序后手测还是有啥别的方法?另外dual-issue可不可以进行是CPU判断的还是编译器编译的时候就能进行判断?
对于什么样的指令可以同时执行,这其实主要是从以下三个角度来考虑:
- Data Hazard
也就是说,首先考虑,前后两条指令使用的寄存器是否有依赖关系;其次,目标流水线对与这种依赖关系是如何处理的——直接拒绝双发射,还是通过bypass(forwarding)的方法允许在合适的时间双发射——这也意味着,双发射不一定是成对发射的,也可以存在某条指令发射以后,当Data Hazard得到缓解时立即发射吓一跳指令的可能(也可能是后来的依赖的存在依赖的指令被撤回等待Data Hazard得到缓解后再发射的可能性)——具体怎么样,这属于Cortex-M7具体实现的问题,而具体实现往往是非公开的。所以这里你我都不知道如何评估。
- Control Hazard
异常来了咋办?出现阻塞了咋办?这些也都是处理器实现的细节,有多个策略,我们缺乏公开资料的情况下,是无法讨论的。
- Structural Hazard
当你只有一个ALU的时候,你永远无法同时发射两个数值运算指令就是这个道理。Cortex-M7拥有两个ALU,但是只有一个LSU(Load Store Unit),所以,很容易根据这个信息判断出哪些指令存在双发射的可能。
CPI 其实就是个统计的活……你跑一段Benchmark(找点知名的,比如CoreMark或者Dhrystone之类),然后用处理器自带的PMU(Performance Monitor Unit)计算下某段函数实际跑了多少指令周期,然后除以这段函数有多少条指令。这就计算出来了。原理很简单,操作起来可能有点费劲。
而且,一个程序不同位置的CPI是变化的,甚至同一段程序的CPI也有可能是变化的,这里面干扰因素主要来源于存储器的延时,prefetch,cache policy等等。
是否可以dual issue的判断是流水线硬件来完成的,对compiler是透明的。当然,ARM Compiler在明确知道目标处理器是Cortex-M7的情况下,甚至会交换某些指令的顺序(保证逻辑等效的情况下),以最大限度的保证更多的指令可以被dual-issue。
最后我要说一下,依赖compiler是不靠谱的……要想最大程度的利用dual-issue,还是要程序员写代码的时候有所注意。简单举个例子,比如C语言中全局变量或者寄存器,往往都有volatile修饰。这保证了逻辑的正确性,却阻断了compiler优化的可能。另外,volatile的使用,使得函数内,每一个对变量的访问都要实际进行Load/Store操作,这就导致大量的Load/Store操作密集存在于同一段代码中,这些密集存在的读写操作会导致大量的Structural Hazard,从而妨害dual issue的进行。另外对volatile变量的数值运算,也会由于运算指令和Load/Store指令存在依赖关系而无法dual issue。所以,针对这种情况我们要在函数需要优化的地方,通过局部变量人为的进行“读、改、写”操作——也就是先用局部变量保存volatile变量的值,然后后续运算全部针对这个局部变量,最后再把计算结果保存回去——这是一个例子。具体软件优化方法,取决于编译器、你掌握的目标处理器的信息以及C语言的各类技巧。内容太大,这里就不方便展开了。
CPI的计算就是个统计的过程。对一段已知代码来说,有多少条指令是知道的,然后就是统计这段代码执行用了多少个指令周期。Cortex-M7有PMU(Performance Monitor Unit),通过它可以方便的统计指令周期。
Dual-issue是否可以进行是流水线硬件进行判断的,对compiler透明。
make sense 了,另外 dual-issue的判断是由哪一个部分进行管理的,跟PFU,branch predictor是一起工作的吗? 找了半天感觉相关资料很少,是不是也属于非公开资料?
然后您说的
我不是特别理解具体如何判断。是指:只有一个LSU说明两个Data相关的指令无法同时进行,这个意思吗?最起码无法同时load/store。也就是说两个ADD指令,在M7里【1】,第一个stage取两个指令,然后用两个stage分别decode,经过两个stage(E1,E2)在ALU中计算完成,一个stage store到Register。,dual-issue是这样工作的吧?然后俩个LDR就无法dual-issue。那么LDR,LDR,ADD,STR这样的组合呢?
我用Keil-MDK debug STM32F746,是个M7核,trace一般的指令比如MOV,VLDR,STR,CMP都是12个clock cycle,这样测然后想加是不是无法反映实际运行的情况?
另外【1】中的这个2nd issue stage,看上去是不是,不管可不可以dual-issue都会被执行?
【1】community.arm.com/.../how-long-are-the-cortex-m7-pipeline-stages
不好意思,你问的这些问题已经属于非公开的内容了。我无法具体回答,我只能给你一些大体的信息:
- 两个无数据相关的ALU操作可以duel-issue
- 两个无数据依赖关系的ALU和LS操作可以dual-issue
- 某些Load/Store指令的组合可以dual-issue
另外,关于Cortex-M7 CPI的计算,系统有一个专门的系统外设DWT(Data Watchpoint and Trace Unit),这个外设提供了一个Clock Cycle Counter(CYCCNT),可以方便的用来计算CPI。具体细节,你自己看看资料吧(ARMv7-M Architecture Reference Manual)
真是非常感谢您,明白了很多。
您好,真不好意思又要叨扰了。这几天看了DWT,确实很好用。只是有个问题是DWT中除了CYCCNT外的counter,都只有8bit的register。所以如果一段程序稍长一点的话就溢出了,既然CPI是统计的话,一段一端的测程序好像会不太准的样子。这个overflow,v7-M 手册里说会触发counter overflow event,但是我没找到怎么收集这个event,是需要设置一个中断触发吗? 因为这个,我最后还是选择keil debug中的trace,用ITM来trace 这些event,因为里面可以累加counter的值,也能读到overflow事件。只是跟DWT printf出来的,有点不太一样。
另外我看keil debug的话,时钟周期的测量也是通过coresight来trace的,如果一段程序稍长一些的(不是by instruction),keil里显示的数据跟DWT CYCCNT出来的是一样的。
非常感谢。
DWT本身就是个低成本的东西,设计目的就是应对局部。大程序(运行时间长的)要用高精度的示波器(或者逻辑分析仪)配合IO口翻转来测量时间(在测量开始的时候拉低电平,在测量结束的时候拉高电平,最终计算的时候,由拉低电平引入的误差基本忽略不计)——我还是那句话,不要相信Debug,一切都要实测。
DWT也算是debug的一种?用示波器测io反转的话,只能获得该段程序运行的运行事件,具体的指令数量看来只能动手数了。非常感激大神的耐心解答。