AVR 组装 BRNE 延迟环路如何工作



在线延迟环路发生器为我提供了运行时间为0.5秒的延迟环路,用于运行频率为16MHz的芯片。

我想到的问题是:

  1. 如果寄存器变为负数,分支是否继续分支?
  2. 如何准确计算开始时加载的值?

    ldi  r18, 41
    ldi  r19, 150
    ldi  r20, 128
    L1: dec  r20
    brne L1
    dec  r19
    brne L1
    dec  r18
    brne L1
    

准确回答您的问题:

1:DEC 指令不知道"有符号"数字,它只是递减一个 8 位寄存器。 二进制补码算术的奇迹使这在环绕(0x00 -> 0xFF,与 0 -> -1 的位模式相同)。 DEC 指令还在状态寄存器中设置 Z 标志,BRNE 使用该标志来确定是否应该发生分支。

2:从AVR手册中可以看出,DEC是一个单周期指令。 BRNE在不分支时也是单循环,分支时也是2个周期。 因此,要计算循环的时间,您需要计算每条路径将被采用的次数。

考虑单个 DEC/BRNE 循环:

ldi r8 0
L1: dec r8
brne L1

此循环将执行 256 次,即 DEC 的 256 个周期和 BRNE 的 512 个周期,总共 768 个周期。 在16MHz时,这是48us。

将其包装在外部延迟循环中:

ldi r7 10
ldi r8 0
L1: dec r8
brne L1
dec r7
brne L1

您可以看到,每次内循环计数器达到 0 时,外循环计数器都会递减。 因此,在我们的示例中,外循环 DEC/BRNE 将发生 10 次(768 个周期),内循环将发生 10 x 256 次,因此此循环的总时间为 10 x 48us + 48us(528us)。同样适用于 3 个嵌套循环。

从这里开始,计算每个循环应该执行多少次才能达到所需的延迟是微不足道的。 这是外部循环可以执行的最大迭代次数少于所需时间,然后取出该时间,对下一个嵌套循环执行相同的操作,依此类推,直到最内部循环填满剩余的少量。

如何准确计算开始时加载的值?

计算总周期数 => 0.5s * 16000000 = 8000000

知道r20和r19环路的总周期(从零到零),AVR寄存器是8位,所以一个完整的环路是256次(dec 0 = 255)。dec是 1 个周期。 条件(分支)发生时brne为 2 个周期,未发生时为 1 个周期。

所以最内在的循环:

L1: dec  r20
brne L1

从零到零 (r20=0): 255 * (1+2) + 1 * (1+1) = 767 个周期(分支被占用 255 次,它通过 1 次)。


使用r19的第二个包装循环为:255 * (767+1+2) + 1 * (767+1+1) = 197119 个循环

当分支被采用时,单个r18循环是197119+1+2 = 197122个周期。(197121当分支不被采用=延迟循环的最终退出时,我将在下一步通过一个技巧来避免这个-1)。

现在这几乎足以计算初始r18,让我们先用 O(1) 代码调整总周期,即指令ldi三倍,需要 1 个周期:total2 = 8000000 - (1+1+1) + 1 = 7999998 ...等等,最后的+1是多少?这是假的额外周期来延迟,使最终r18循环假装它的成本与非最终循环相同,即197122周期。

就是这样,初始r18必须足以等待至少7999998个周期:r18 = (7999998 + 197122 - 1) div 197122 = 41." + 197122 - 1" 部分将确保丰度循环符合约束:0 <= abundant_cycles < 197122(余数由197122除法)。

41 * 197122 = 8082002......这太多了,但现在我们可以通过设置r19r20到特定值来减少额外的周期,以微调延迟。那么要剃掉多少呢?8082002 - 7999998 = 82004周期。

r19环路在分支时需要 770 个周期,在退出时需要 769 个周期,所以让我们再次通过将 82004 调整为仅 82003 来避免 769 被剃掉。82003 div 770 = 106: 可以跳过 106 个r19循环,r19 = 256 - 106 = 150.现在这将减少 81620 个周期,因此 82003 - 81620 = 383 个周期要减少。

r20循环在分支时需要 3 个周期,在退出时需要 2 个周期。我将再次考虑到退出循环只有 2 个周期 -> 383 => 382 剃掉。和382 div 3 = 127,余数1。r20 = 256 - 127 = 129并少做一个以剃掉额外的 3 个周期(以覆盖剩余部分)= 128。然后缺少 2 个周期 (3-1) 等待以使其成为完整的 8mil。

所以:

ldi  r18, 41
ldi  r19, 150
ldi  r20, 128
L1: dec  r20
brne L1
dec  r19
brne L1
dec  r18
brne L1

根据我的计算,应该等待 8000000-2 个周期(如果没有被其他东西中断)。

让我们尝试验证:

初始r20: 1273 +1 2 = 383 个周期
初始r19: 1*(383+1+2) + 148*(767+1+2) + 1*(767+1+1) = 115115 个周期 (这是初始r20不完整循环一次,然后是 149 次全时r20周期,最后一个周期由于退出brne而为 -1)
r18总数:1*(115115+1+2) + 39*(197119+1+2) + 1*(197119+1+1) = 7999997 个周期。

三个ldi是 +3 个周期 = 7999997+3 = 8000000。

而且缺少的 2 个周期无处可见,所以我在某个地方犯了一个错误。

如您所见,背后的数学相当简单,但手工完成非常平凡,并且容易出错......

啊,我想我知道我在哪里犯了错误。当我剃掉丰富的周期时,不涉及终止循环(这是实际延迟过程的一部分),所以我不应该将to_shave_off周期调整为 -1。然后r19 = 106之后,我仍然需要剃掉 384 个周期,这正好是 384/3 = 128 个循环才能从r20 = 256-128 = 128中剃掉.没有剩余,没有缺失的循环,完美的8mil。

如果您无法遵循这种反向计算,请尝试其他方式,想象 2 位寄存器(仅 0..3 个值),并在纸上用 r18=r19=r20=2 进行类似的循环,并手动计算周期以查看它是如何演变的。 即 3x LDI = +3, dec r20,brne,dec r20,brne(skip) = +5 个周期, dec r19, brne = +3, ...等。

编辑:杰斯特之前在他的链接中解释过这一点。而且我懒得把它清理成一些简单的公式来创建自己的在线计算器。

最新更新