使用现代 CPU(例如 ARM)进行周期计数

电器工程 微控制器 手臂 编程 皮质-m3
2022-01-18 06:52:17

在许多应用中,指令执行与预期输入刺激具有已知时序关系的 CPU 可以处理如果关系未知则需要更快 CPU 的任务。例如,在我使用 PSOC 生成视频的项目中,我使用代码每 16 个 CPU 时钟输出一个字节的视频数据。由于测试 SPI 设备是否准备就绪,如果没有,则 IIRC 分支需要 13 个时钟,而加载和存储到输出数据需要 11 个时钟,因此无法测试设备在字节之间是否准备就绪;相反,我只是安排让处理器在第一个字节之后为每个字节执行精确的 16 个周期的代码(我相信我使用了真正的索引加载、虚拟索引加载和存储)。每行的第一次 SPI 写入发生在视频开始之前,对于随后的每次写入,都有一个 16 周期的窗口,在该窗口中写入可能不会发生缓冲区溢出或欠载。分支循环产生了一个 13 周期的不确定性窗口,但可预测的 16 周期执行意味着所有后续字节的不确定性将适合相同的 13 周期窗口(这反过来又适合写入可以接受的 16 周期窗口)发生)。

对于较旧的 CPU,指令时序信息清晰、可用且不含糊。对于较新的 ARM,时序信息似乎更加模糊。我知道当代码从闪存执行时,缓存行为会使事情变得更难预测,所以我希望任何循环计数的代码都应该从 RAM 执行。但是,即使从 RAM 执行代码,规范似乎也有些模糊。使用循环计数代码仍然是个好主意吗?如果是这样,使其可靠工作的最佳技术是什么?在多大程度上可以安全地假设芯片供应商不会悄悄地溜进“新改进”芯片,在某些情况下,该芯片会缩短执行某些指令的周期?

假设以下循环从字边界开始,如何根据规范准确确定它需要多长时间(假设 Cortex-M3 具有零等待状态内存;对于本示例,系统的其他任何内容都不重要)。

循环:
  移动 r0,r0 ; 简短的简单指令以允许预取更多指令
  移动 r0,r0 ; 简短的简单指令以允许预取更多指令
  移动 r0,r0 ; 简短的简单指令以允许预取更多指令
  移动 r0,r0 ; 简短的简单指令以允许预取更多指令
  移动 r0,r0 ; 简短的简单指令以允许预取更多指令
  移动 r0,r0 ; 简短的简单指令以允许预取更多指令
  添加 r2,r1,#0x12000000 ; 2字指令
  ; 重复以下操作,可能使用不同的操作数
  ; 将继续添加值,直到发生进位
  国际贸易委员会
  添加cc r2,r2,#0x12000000; 2字指令,加上itcc的额外“字”
  国际贸易委员会
  添加cc r2,r2,#0x12000000; 2字指令,加上itcc的额外“字”
  国际贸易委员会
  添加cc r2,r2,#0x12000000; 2字指令,加上itcc的额外“字”
  国际贸易委员会
  添加cc r2,r2,#0x12000000; 2字指令,加上itcc的额外“字”
;...等,带有更多有条件的两字指令
  子 r8,r8,#1
  bpl myloop

在执行前六条指令期间,内核将有时间获取六个字,其中三个将被执行,因此最多可以预取三个。接下来的指令都是三个字,因此内核不可能像执行指令一样快地获取指令。我希望某些“it”指令需要一个周期,但我不知道如何预测哪些指令。

如果 ARM 可以指定“it”指令时序确定的某些条件(例如,如果没有等待状态或代码总线争用,并且前面的两条指令是 16 位寄存器指令等),那就太好了。但我还没有看到任何这样的规范。

示例应用程序

假设有人试图为 Atari 2600 设计一个子板,以生成 480P 的分量视频输出。2600 具有 3.579MHz 像素时钟和 1.19MHz CPU 时钟(点时钟/3)。对于 480P 分量视频,每行必须输出两次,即 7.158MHz 点时钟输出。由于 Atari 的视频芯片 (TIA) 使用 3 位亮度信号和分辨率约为 18ns 的相位信号输出 128 种颜色中的一种,因此仅通过查看输出很难准确地确定颜色。更好的方法是截取对颜色寄存器的写入,观察写入的值,并将对应于寄存器编号的 TIA 亮度值馈入每个寄存器。

所有这一切都可以用 FPGA 完成,但是一些非常快的 ARM 设备可以比具有足够 RAM 来处理必要缓冲的 FPGA 便宜得多(是的,我知道可能会产生这样的体积,成本是' t 一个真实的因素)。然而,要求 ARM 观察输入时钟信号会显着提高所需的 CPU 速度。可预测的周期计数可以使事情变得更清洁。

一种相对简单的设计方法是让 CPLD 监视 CPU 和 TIA 并生成 13 位 RGB+同步信号,然后让 ARM DMA 从一个端口获取 16 位值并以适当的时序将它们写入另一个端口。不过,看看便宜的 ARM 是否能做所有事情,这将是一个有趣的设计挑战。如果可以预测 DMA 对 CPU 周期计数的影响(特别是如果 DMA 周期可能发生在内存总线空闲的周期中),则 DMA 可能是多合一方法的一个有用方面,但在过程中的某个时刻ARM 必须执行其查表和总线监视功能。请注意,与许多在消隐间隔期间写入颜色寄存器的视频架构不同,Atari 2600 经常在帧的显示部分期间写入颜色寄存器,

也许最好的方法是使用几个离散逻辑芯片来识别颜色写入并将颜色寄存器的低位强制为正确的值,然后使用两个 DMA 通道对传入的 CPU 总线和 TIA 输出数据进行采样,并且第三个 DMA 通道生成输出数据。然后,CPU 可以自由地处理来自每个扫描线的两个源的所有数据,执行必要的转换,并将其缓冲以供输出。必须“实时”发生的适配器职责的唯一方面是覆盖写入 COLUxx 的数据,这可以使用两个通用逻辑芯片来处理。

4个回答

我投票给DMA。它在 Cortex-M3 及更高版本中非常灵活——你可以做各种疯狂的事情,比如自动从一个地方获取数据并以指定的速率或在某些事件中输出到另一个地方,而无需花费任何 CPU 周期。DMA 更可靠。

但细节上可能很难理解。

另一种选择是 FPGA 上的软核,通过硬件实现这些紧密的东西。

时间信息是可用的,但是,正如您所指出的,有时可能是模糊的。Cortex-M3技术参考手册的第 18.2 节和表 18.1 中有很多时序信息,例如(此处为pdf),此处摘录:

18.2节选

它给出了最大时间的条件列表。许多指令的时间取决于外部因素,其中一些确实会产生歧义。我已经强调了我在该部分的以下摘录中发现的每一个歧义:

[1] 分支需要一个周期的指令,然后流水线重新加载目标指令。未采用的分支总共为 1 个周期。立即采取的分支通常是1 个管道重载周期(总共 2 个周期)。带有寄存器操作数的分支通常是2 个流水线重载周期(总共 3 个周期)。除了访问较慢的内存之外,当分支到未对齐的 32 位指令时,管道重新加载时间会更长[多长时间?]。向代码总线发出一个分支提示,允许较慢的系统[慢多少?] 进行预加载。可以[这是可选的吗?]减少[减少多少?] 对较慢内存的分支目标惩罚,但绝不会低于此处所示。

[2]通常,加载-存储指令第一次访问需要两个周期,每次额外访问需要一个周期。具有立即偏移量的存储需要一个周期。

[3] UMULL/SMULL/UMLAL/SMLAL根据源值的大小使用提前终止[什么大小?]。这些是可中断的(放弃/重新启动),最坏情况下的延迟为一个周期。MLAL版本需要4 到 7 个周期,MULL 版本需要3 到 5 个周期对于 MLAL,签名版本比未签名版本长一个周期。

[4] IT说明书可折叠[什么时候?看评论。]

[5] DIV 时间取决于除数和除数[与 MUL 相同的问题] DIV 是可中断的(被放弃/重新启动),最坏情况下的延迟为一个周期。当被除数和除数在大小上相似[多相似?] 时,除数会很快终止。最短时间适用于除数大于被除数和除数为零的情况。零除数返回零(不是故障),尽管可以使用调试陷阱来捕获这种情况。[MUL 的范围是多少?]

[6] 休眠是指令的一个周期加上适当的休眠周期。WFE 仅在事件过去后使用一个周期。WFI 通常不止一个周期,除非在进入 WFI 时恰好有一个中断恰好挂起。

[7] ISB 需要一个周期(作为分支)。DMB 和 DSB 需要一个周期,除非数据在写缓冲区或 LSU 中未决。如果在屏障期间出现中断,则将其放弃/重新启动。

对于所有用例,它会比“这条指令是一个周期,这条指令是两个周期,这是一个周期......”在更简单、更慢、更旧的处理器中计算可能更复杂。对于某些用例,您不会遇到任何歧义。如果您确实遇到歧义,我建议:

  1. 联系您的供应商并询问他们针对您的用例的说明时间。
  2. 测试以指定模棱两可的行为
  3. 重新测试任何处理器版本,尤其是在经历供应商变更时。

这些要求可能会回答您的问题,“不,这不是一个好主意,除非遇到的困难值得付出代价”——但您已经知道这一点。

解决此问题的一种方法是使用具有确定性或可预测时序的设备,例如 Parallax Propeller 和 XMOS 芯片:

http://www.parallaxsemiconductor.com/multicoreconcept

http://www.xmos.com/

周期计数与 Propeller 配合得非常好(必须使用汇编语言),而 XMOS 设备有一个非常强大的软件实用程序,即 XMOS 时序分析器,它与用 XC 编程语言编写的应用程序一起工作:

https://www.xmos.com/download/public/XMOS-Timing-Analyzer-Whitepaper%281%29.pdf

当您远离低级微控制器并进入更通用的计算处理器时,周期计数会变得更加困难。第一个通常确实有明确的说明时间,部分原因是您的站点。这也是因为它们的架构相当简单,所以指令时间是固定的并且是可知的。

大多数 Microchip PIC 就是一个很好的例子。10、12、16 和 18 系列具有很好的文档记录和可预测的指令时序。在这些芯片所针对的小型控制应用中,这可能是一个有用的功能。

随着您远离超低成本,设计人员因此可以花费更多的芯片面积从更奇特的架构中获得更高的速度,您也远离可预测性。看看现代 x86 变体就是一个极端的例子。有多个级别的高速缓存、内存虚拟化、预读、流水线等,这使得计算指令周期几乎是不可能的。在这个应用中,这并不重要,因为客户对高速感兴趣,而不是指令时序可预测性。

您甚至可以在更高级别的 Microchip 模型中看到这种效果。24 位内核(24、30 和 33 系列)在很大程度上具有可预测的指令时序,但存在寄存器总线争用时的少数例外情况除外。例如,在某些情况下,当下一条指令使用具有某些间接寻址模式的寄存器(其值在前一条指令中更改)时,机器会插入停顿。这种停顿在 dsPIC 上是不常见的,大多数情况下您可以忽略它,但它显示了由于设计人员试图为您提供更快、更强大的处理器,这些事情是如何蔓延的。

所以基本的答案是,当你选择处理器时,这是权衡的一部分。对于小型控制应用,您可以选择小型、便宜、低功耗且具有可预测指令时序的产品。当您需要更多的处理能力时,架构会发生变化,因此您必须放弃可预测的指令时序。幸运的是,当您使用更多计算密集型和通用用途的应用程序时,这不是一个问题,所以我认为权衡取舍相当不错。