繁体   English   中英

AVR Assembly BRNE 延迟循环如何工作?

[英]How do AVR Assembly BRNE delay loops work?

对于运行在 16MHz 的芯片,在线延迟循环生成器为我提供了 0.5 秒的运行时间延迟循环。

我心中的问题是:

  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) 等待以使其成为足足800万。

所以:

    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 : 127 3 + 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我仍然需要r19 = 106 384 个周期,这正是 384/3 = 128 个循环来r20 = 256-128 = 128 无余数,无漏循环,完美800万。

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

编辑:Jester 之前在他的链接中对此进行了解释。 而且我懒得把它整理成一些简单的公式来创建自己的在线计算器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM