assembly 最快的轮询循环-如何削减1个CPU周期?

cczfrluj  于 2023-06-30  发布在  其他
关注(0)|答案(2)|浏览(107)

在ARM Cortex M3(类似于STM32 F101)上的实时应用¹中,我需要轮询内部外设寄存器的一个位,直到它为零,尽可能紧密地循环。我使用位绑定来访问适当的位。C代码(工作)是

while (*(volatile uint32_t*)kMyBit != 0);

该代码被复制到片上可执行RAM中。经过一些手动优化²,轮询循环下降到以下,我将时间控制在³ 6个周期:

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 D1FC      BNE      0x00600200

**如何降低投票的不确定性?**一个5周期循环符合我的目标:在相同位变为零之后尽可能接近15.5个周期对相同位进行采样。

我的规格要求可靠地检测一个低脉冲至少6.5 CPU时钟周期;如果其持续小于12.5个周期,则可靠地将其分类为短;并且如果其持续超过18.5个周期,则可靠地将其分类。脉冲与CPU时钟没有明确的相位关系,CPU时钟是我唯一精确的定时参考。这最多需要5个时钟的轮询循环。实际上,我正在模拟在一个有几十年历史的8位CPU上运行的代码,该CPU可以用5个时钟周期进行轮询,并且它所做的已经成为规范。
我尝试在循环之前插入NOP来抵消代码对齐,在我尝试的许多变体中,但从未观察到变化。
我尝试反转CMP和LDR,但仍然得到6个周期:

0x00600200 681A      LDR      r2,[r3,#0x00]
; we loop here
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FC      BNE      0x00600202

这个是8个周期

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 681A      LDR      r2,[r3,#0x00]
0x00600204 2A00      CMP      r2,#0x00
0x00600206 D1FB      BNE      0x00600200

这是9个循环:

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FB      BNE      0x00600200

¹在没有中断发生的情况下,测量位处于低电平的时间。
²编译器生成的初始代码使用r12作为目标寄存器,这给循环增加了4个代码字节,花费了1个周期。
³给出的数字是通过假定周期精确的实时STIce emulator及其 * 模拟器触发 * 功能在读取寄存器地址时获得的。以前我在循环中使用断点尝试了“States”计数器,但结果取决于断点的位置。单步更糟糕:当LDR至少有时下降到3时,它总是给予4个周期。

gjmwrych

gjmwrych1#

如果我正确理解了这个问题,那么需要减少的不一定是循环周期,而是后续样本之间的周期数(即LDR指令)。但是每次迭代可以有多于一个LDR。你可以试试这样的方法:

ldrb    r1, [r0]

loop:
    cbz     r1, out
    ldrb    r2, [r0]
    cbz     r2, out
    ldrb    r1, [r0]
    b       loop

out:

两个LDRB指令之间的间隔变化,因此样本不是均匀间隔的。
这可能会稍微延迟退出循环,但从问题描述中我不能说它是否重要。
我碰巧可以访问周期精确的M7模型,当该过程稳定时,您的原始循环在M7上以每次迭代3个周期运行(意味着LDR每3个周期),而上面提出的循环在4个周期内运行,但现在有两个LDR在那里(因此LDR每2个周期)。采样率明显提高。
为了给予信贷,展开与CBZ作为一个休息是由@彼得科德斯在评论中提出的。
诚然,M3会较慢,但它仍然值得一试,如果它的采样率后,你。
你也可以检查LDRB而不是LDR(如上面的代码)是否改变了什么,尽管我不希望它改变。
UPD:我有另一个2-LDR循环版本,在M7上完成3个循环,您可以尝试感兴趣(CBZ中断也允许循环后轻松平衡路径):

ldr     r1, [r0]

loop:
    ldr     r2, [r0]
    cbz     r1, out_slow
    cbz     r2, out_fast
    ldr     r1, [r0]
    b       loop

out_fast:
    /* NOPs as required */

out_slow:
vulvrdjw

vulvrdjw2#

你可以试试这个,但我怀疑它会给予同样的6个周期

0x00600200 581a      LDR      r2,[r3,r0]; initialize r0 to 0x0
0x00600202 4282      CMP      r2,r0
0x00600204 D1FC      BNE      0x00600200

相关问题