2

我需要一个非常精确的时序,所以我写了一些汇编代码(用于 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 us 给 1.56 us),最高 500 ms 给 750 ms。

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

任何人都可以阐明这里发生了什么吗?

4

2 回答 2

1

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

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

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

于 2020-10-07T09:43:21.380 回答
1

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

首先,这些是流水线的,因此您的平均性能是一回事,但是一旦进入循环并且诸如缓存和分支预测学习之类的事情以及其他因素已经解决,那么您可以获得一致的性能,用于该实现。忽略与流水线处理器的每条指令的时钟相关的任何文档现在不管多么浅,这是理解为什么时序不能按预期工作的第一个问题。

对齐起着重要作用,人们厌倦了我敲这个鼓,但我已经证明了很多次。您可以在 cortex-m0 TRM 中搜索 fetch,您应该会立即看到这会影响基于对齐的性能。如果芯片供应商仅将内核编译为 16 位,那么这将是可预测的或更可预测的(忽略其他因素)。但是,如果他们已经在其他功能中进行了编译,并且如果按照描述进行预取,那么在地址空间中循环的放置可能会影响循环,增加或减去影响完成循环的总时间的取指,这可以用或没有范围。

分支预测,它没有在 arm 文档中显示为 arm 正在执行,但芯片供应商完全可以自由地执行此操作。

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

闪存,正如所提到的,取决于芯片供应商和部件的使用年限,闪存的速度是内核的一半是很常见的。然后根据您的时钟频率,当您阅读芯片文档中的闪存设置时,它会显示所需的等待状态与系统时钟速度的关系,这是闪存技术的关键性能指标,您是否应该这样做确实将系统时钟提高得太高了,闪光灯没有变得更快它有速度限制,根据我的经验,sram 可以跟上,到目前为止我没有看到它们处于等待状态,但闪光灯曾经是两个或该部件支持的时钟速度范围内的三个设置,

中断/异常。您是否在 rtos 上运行此程序,您是否正在增加和/或保证这会以更长的延迟中断?

外设时序,外设预计不会在单个时钟中响应加载或存储,它们可以根据需要花费任意时间,并且取决于内部或购买的时钟系统和芯片供应商 IP,外设可能不会在处理器时钟频率并以分频运行使事情变慢。毫无疑问,您的代码会延迟调用此函数,然后在此时序循环之外,您正在摆动 gpio 引脚以查看示波器上的某些内容,这会导致您如何执行基准测试以及基于上述因素和这一因素的其他问题.

以及我必须记住的其他因素。

像 x86、全尺寸 ARM 等高端处理器一样,处理器不再决定性能。芯片和主板可以/可以。你基本上不能一直喂管道,到处都是摊位。Dram 很慢,因此缓存层试图处理它,但缓存有时会有所帮助并伤害其他人,分支预测器的伤害与它们的帮助一样多。依此类推,但它在很大程度上由处理器内核之外的系统驱动,你可以如何为内核供电,然后你就可以了解内核关于管道的属性和它自己的获取策略。理想情况下使用总线的宽度而不是指令的大小,事务开销因此总线的多个宽度比一个宽度等更理想。

当相同的机器代码用于不同的对齐方式时,在任何核心上导致这样的紧密循环具有不平稳的运动和/或时序不一致。现在,在尺寸/功率/等方面,m0+ 有一个小管道,但它仍然应该显示出它的影响。这些不是 pics 或 avrs 或 msp430s 没有理由期望时序循环是一致的。充其量您可以使用时序循环来处理 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,我能够使用定时循环和 nop 生成具有载波频率和开关的电视红外信号,以生成高度准确的信号。我重复说,对于其中一个可编程的 led 事物,在 cortex-m 上使用长长的线性指令列表并依赖于执行性能,它可以工作,但由于编译时间快且脏乱,因此非常有限。

您需要将注意力转移到以非正常方式使用定时器和/或芯片外设(如 uart、spi、i2c)来生成您想要生成的任何信号。对于大于或等于的情况,而不是在时间范围内的情况下,将定时循环甚至基于计时器的循环由其他循环包裹。如果无法用一个芯片做到这一点,那么就环顾四周,在制造产品时,您必须经常购买组件,跨供应商等。推送到了使用 CPLD 或 PAL 或 GAL 或类似的东西获得高度准确但自定义的时间。取决于你在做什么以及你更大的系统图是什么样的,带有 mpsse 的 ftdi usb 芯片有一个通用的状态机,你可以对其进行编程以生成一组信号,它们使用这个通用的可编程程序执行 i2c、spi、jtag、swd 等系统。

你没有指定芯片,我手边有很多不同的芯片/板,但只有一小部分,所以如果我想做一个演示,如果我的核心编译了一个,它可能不值得方式我可能无法让它展示一个变体,其中来自 arm 的相同内核在另一个芯片上以另一种方式编译可能很容易。我怀疑你的很多变化首先是因为你在一个更大的循环中进行调用,调用延迟调用以改变 gpio,并且你正在为实验重新编译它。或者更糟糕的是,如果您正在执行单次传递而不是围绕呼叫进行循环,则如您的问题所示,那么这可以最大限度地增加不一致。

于 2020-10-07T15:27:24.083 回答