手臂装配中的意外时间

问题描述

我需要一个非常精确的时间,所以我写了一些汇编代码(用于ARM M0 +)。 但是,时间安排不是我在示波器上进行测量所期望的。

#define LOOP_INSTRS_CNT                 4 // subs: 1,cmp: 1,bne: 2 (when branching)
#define FREQ_MHZ                        (BOARD_BOOTCLOCKRUN_CORE_CLOCK / 1000000)
#define DELAY_US_TO_CYCLES(t_us)        ((t_us * FREQ_MHZ + LOOP_INSTRS_CNT / 2) / LOOP_INSTRS_CNT)

static inline __attribute__((always_inline)) void timing_delayCycles(uint32_t loopCnt)
{
  // note: not all instructions take one cycle,so in total we have 4 cycles in the loop,except for the last iteration.
   __asm volatile(
    ".Syntax unified \t\n"  /* we need unified to use subs (not for sub,though) */
    "0: \t\n"
    "subs %[cyc],#1 \t\n"  /* assume cycles > 0 */
    "cmp %[cyc],#0 \t\n"
    "bne.n 0b\t\n"          /* this instruction costs 2 cycles when branching! */
    : [cyc]"+r" (loopCnt)   /* actually input,but we need a temporary register,so we use a dummy output so we can also write to the input register */
    :                       /* input specified in output */
    :                       /* no clobbers */
  );
}

// delay test
#define WAIT_TEST_US 100
gpio_clear(PIN1);
timing_delayCycles(DELAY_US_TO_CYCLES(WAIT_TEST_US));
gpio_set(PIN1);

非常基本的东西。但是,延迟(通过将GPIO引脚设置为低电平,循环然后再次设置为高电平来测量)时序始终比预期高50%。我尝试了较低的值(1个我们给1.56我们),最长为500毫秒给750毫秒。

我尝试单步执行,而循环实际上仅执行3个步骤:subs(1),cmp(1),branch(2)。括号是预期的时钟周期数。

任何人都可以了解这里发生的事情吗?

解决方法

经过一些好的建议,我发现该问题可以通过两种方法解决:

  1. 以相同的频率运行核心时钟和闪存时钟(如果代码从闪存运行)
  2. 将代码放在SRAM中,以避免闪存访问等待状态。

注意:如果有人复制了上面的代码,请注意,由于subs设置了s标志,因此可以删除cmp。如果这样做,请记住将指令计数设置为3而不是4。这样可以为您提供更好的时间分辨率。

,

您不能像使用PIC那样使用这些处理器,计时不能那样工作。我已经在这里演示了很多次,您可以环顾四周,也许可以在这里再次进行,但是现在还不能。

首先将它们进行流水线化,所以您的平均性能是一回事,但是一旦循环出现,诸如缓存和分支预测学习以及其他因素之类的问题就解决了,那么对于该实现,您可以获得一致的性能。现在,无视流水线处理器中与每条指令的时钟有关的任何文档都变得很浅,这是理解为什么时序无法按预期工作的第一个问题。

对齐发挥作用,人们讨厌我击败这支鼓,但是我已经证明了很多次。您可以在cortex-m0 TRM中搜索获取,并且您应该立即看到这将影响基于对齐的性能。如果芯片供应商仅将内核编译为16位,那么这将是可预测的或更可预测的(忽略其他因素)。但是,如果它们已经编译了其他功能,并且如前所述发生了预取,则循环在地址空间中的放置可能会通过对正向或负向取回的影响,从而影响完成循环的总时间,从而影响循环。或没有范围。

分支预测并没有出现在分支文档中,但是芯片供应商完全可以这样做。

缓存。如果是cortex-m0 +(如果它是STM32或其他品牌),则存在或可能无法关闭缓存。对于闪存来说,闪存速度是处理器速度的一半并不罕见,因此闪存等待状态设置是零,但零等待状态通常意味着额外的零,并且需要两个时钟来完成一次获取,或者至少可以测量到闪存执行速度是处理器的一半。在ram中的执行速度与所有其他设置相同(系统时钟速度等)。意法半导体(ST)有一个很好的预取/缓存解决方案,带有一些商标名称,也许还有一个知道的专利。而且您很少能关闭它或将其取消,因此第一次或进入循环的时间会看到延迟,从技术上讲,预取器会减慢循环的速度(请参见对齐)。

如前所述,取决于芯片供应商和器件的寿命,闪存通常是内核速度的一半。然后,根据您的时钟速率,当您在芯片文档中了解闪存设置时,它会显示与系统时钟速度相关的所需等待状态,这是闪存技术的关键性能指标,以及是否应该确实将系统时钟调高得太高,闪光灯没有速度限制,它的速度没有限制,根据我的经验可以保持稳定,到目前为止,我没有看到它们处于等待状态,但是闪光灯曾经是两次或部件支持的时钟速度范围内的三个设置,较新的部件,闪光灯趋向于覆盖速度较慢的内核(如m0 +)的整个范围,但m7等不断获得更高的时钟速率,因此您仍然可以期待供应商需要等待状态。

中断/异常。您是在rtos上运行此程序吗,是否正在发生中断,您是否正在增加和/或保证中断时间更长?

外设时序,不希望外设响应负载或在单个时钟中存储它们可能需要的时间,并且取决于内部或购买的时钟系统和芯片供应商IP,外设可能不会以处理器时钟速率运行,并以分频速率运行,从而使速度变慢。毫无疑问,您的代码会延迟调用此函数,然后在此定时循环之外,您会摆动gpio引脚以查看示波器上的某些内容,从而导致您如何进行基准测试以及基于上述因素以及该基准的其他问题

还有我要记住的其他因素。

像x86,全尺寸ARM等高端处理器一样,处理器不再决定性能。芯片和主板可以/可以。您基本上不能不断地给管道喂食,到处都是摊位。 Dram速度很慢,因此尝试进行缓存的缓存层很多,但是缓存有时会有所帮助,并且会伤害他人,分支预测变量会尽可能地帮助他们。依此类推,但它在很大程度上取决于处理器内核之外的系统,从而决定了内核的馈送能力,然后就管道及其自身的获取策略而言,进入了内核的属性。理想情况下,使用总线的宽度而不是指令的大小会增加事务开销,因此总线的多个宽度甚至比一个宽度等更为理想。

当在不同的对齐方式下使用相同的机器代码时,在任何内核上都会造成这样的紧密循环,从而产生抖动的运动,或者在时序上不一致。现在考虑到大小/功率/等,m0 +有一个很小的管道,但是它仍然应该显示出它的影响。这些不是图片,avrs或msp430,没有理由期望时序循环是一致的。充其量来说,您可以对诸如spi和i2c位敲打之类的东西使用定时循环,在这些情况下,您需要大于或等于某个时间值,但是如果您需要精确或在一定范围内,则从技术上讲,如果您实现控制许多因素,但是通常不值得付出努力,您现在就遇到了这个维护问题,或者代码的可读性或可理解性。

因此,最重要的是没有理由期望一致的时间安排。如果您碰巧获得一致/线性的时序,那就太好了。您要做的第一件事是检查更改并重新构建代码时是否对循环使用其他值,该值不会影响此循环的对齐。

您显示这样的循环

loop:
   subs r0,#1
   cmp r0,#0
   bne loop

切线为何选择cmp,为什么不

loop:
   subs r0,#1
   bne loop

但是第二,您然后声称要在一个示波器上进行测量,这很好,因为您如何测量事物会影响基准的质量,而基准经常因测量的标准而变化,因为标准不是问题所在测量,或者两者都有问题,那么测量就更加不一致了。是否使用了systick或其他工具来进行测量,具体取决于测量本身会导致变化的方式,即使您使用gpio来切换可能也可能会影响此结果的引脚。所有其他内容保持不变,只需根据立即数更改循环计数即可,使用的值可能使您在thumb和thumb2指令之间切换,从而更改某些循环的对齐方式。

所显示的内容意味着您有一个可能受许多系统问题影响的定时循环,然后将其包装,同时影响其他一些循环本身,以及可能会影响到gpio库函数的调用从性能角度考虑这些因素。使用内联汇编和编写此函数的风格表示您已经暴露了自己,并且可以轻松地看到运行相同代码甚至实际上被测代码相同的各种性能差异。机器代码。

除非这是微芯片PIC,而不是PIC32,否则是其他特定品牌和系列芯片的非常短的清单。忽略每条指令的周期计数,假设它们是错误的,除非您控制这些因素,否则不要尝试准确的计时。

使用硬件,例如,如果您尝试使用ws8212 / neopixel led,并且您的时序窗口很狭窄,那么使用指令时序将不会成功或成功将受到限制。在这种特定情况下,有时您可以不使用部件中的spi控制器或计时器来生成准确的计时(远远超过了管理位敲打或其他方式的软件计时器)。借助PIC,我可以使用定时循环和点动产生具有载波频率,开和关的电视红外信号,以生成高精度信号。我重复说过,对于这些可编程领导事物中的一种,使用长线性指令列表并依靠执行性能在cortex-m上将它们中的一小部分用于工作,但是它的工作效率很高,但由于其编译时间,快速而肮脏,因此受到极大限制。与SPI控制器相比,SPI控制器是一件痛苦的事情,但在SPI控制器的另一个晚上,它可以发送任意长度的高度精确定时的信号。

您需要将精力转移到使用计时器和/或非常规方式使用uart,spi,i2c等芯片外设来生成您要生成的任何信号。对于大于或等于的情况,而不是在一定时间范围内,请使定时循环或什至是基于计时器的循环被其他循环包裹。如果不能用一个芯片做到这一点,那么在制造产品时经常要四处看看,您必须在各个供应商之间购买组件。等等。推挤推挤使用CPLD或PAL或GAL之类的东西获得高度精确但自定义的计时根据您的工作以及更大的系统画面,带有mpsse的ftdi usb芯片具有通用状态机,您可以对其进行编程以生成信号数组,并使用此通用可编程控制器执行i2c,spi,jtag,swd等系统。但是,如果您没有USB主机,那将无法正常工作。

您没有指定芯片,我手头上有很多不同的芯片/板,但是只有一小部分可用,所以如果我想做一个演示,那可能不值得,如果我的有用一种方式编译内核我可能无法得到它的证明,而使用另一种方式在另一块芯片上编译ARM相同的内核可能很容易。我怀疑您的许多变化首先是因为您在一个更大的循环中进行调用,调用延迟调用以更改gpio的状态,然后将其重新编译用于实验。或者,如您的问题中所示,更糟糕的是,如果您正在执行一次通行证而不是围绕呼叫进行循环,那么这将使不一致最大化。