AVR 汇编:将一个 16 位数字加载到两个 8 位寄存器中
AVR assembly: load a 16 bit number into two 8 bit registers
我对 AVR 汇编完全陌生,我有一个 16 位的十六进制数 0x20C
。
我想在 AVR 程序集的两个 8 位寄存器中加载这个常量十六进制数
是否可以这样做:
LDI R17:R18 0x20C
编辑:如果无法通过这种方式在两个 8 位寄存器中加载 16 位数字,有人可以给我一个替代方案吗?
有一些指令可以对寄存器对进行 16 位加法或递增,但不能从内存加载,当然也不能立即执行。 你需要单独加载每个字节,使用一个ld
/ldi
/ldd
/lds
/每个字节的任何指令/每个目标寄存器。
(有一条指令可以将一对寄存器复制到另一对,在许多现代 AVR CPU 上都支持,(参见 ),但不是加载或立即加载。您可以为2 条指令,如 ReAl 所示,但它不会提高性能或代码大小,只会提高人类的可读性。
AVR 是一个 RISC 指令集,其中大部分(几乎所有?)整个指令都是 16 位的。没有空间容纳 16 位立即数,只有 8 个。您始终可以将一个立即数分成两个 8 位的一半,例如 ldi r17, 0x0c
用于低半,而 ldi r18, 0x2
对于高半部分。
我检查了 AVR 指令集,没有看到任何多字节加载或立即数(https://onlinedocs.microchip.com/pr/GUID-0B644D8F-67E7-49E6-82C9-1B2B9ABE6A0D-en-US-1/index.html - 更新;在线 ISA ref 的格式远不如以前)。
然后我用 AVR gcc on the Godbolt compiler explorer 编译了这个 C,看看是否有我遗漏的东西。
int return_immediate(void) { return 0x20c; }
ldi r24,lo8(524) # ldi r24, 0x0c
ldi r25,hi8(524) # ldi r25, 0x02
ret
int glob;
int return_global(void) {
return glob;
}
lds r24,glob
lds r25,glob+1
ret
int add(int *a, int *b) {
return *a + *b;
}
mov r30,r24
mov r31,r25 Z = a
ld r24,Z
ldd r25,Z+1 retval = *a
mov r30,r22
mov r31,r23 Z = b
ld r18,Z
ldd r19,Z+1 tmp = *b
add r24,r18
adc r25,r19 retval += tmp
ret
因此,除非 AVR gcc 有遗漏的优化,或者出于某种原因它避免了单词加载,否则您不能这样做。
更新:gcc 以基线 AVR 为目标,因此它不能使用 movw
复制一对寄存器。
使用 -mmcu=avr6
编译使 add(int*, int*)
更高效:
add:
movw r30,r24 # Z = a
ld r24,Z
ldd r25,Z+1
movw r30,r22 # Z = b
ld r18,Z
ldd r19,Z+1
add r24,r18
adc r25,r19
ret
但正如我们所见,仍然没有对一对寄存器执行任何其他操作的指令,因此所有这些都必须单独完成。尽管如此,用一条机器指令复制一对寄存器还是很不错的,因为需要这样做的情况并不少见。
正如Peter Cordes answer中提到的,AVR指令集没有将16位值加载到寄存器对中的指令。但是……
if it is not possible to load a 16 bit number in two 8 bit registers
this way, may someone could give me an alternative option?
为方便起见,汇编器通常具有创建宏的能力(如果您打算在汇编中进行大量编程,请注意这个机会)。
宏语法依赖于工具链。对于 gnu as
我使用以下宏将 16 位立即值加载到给定和下一个寄存器
.macro ldi_w reg:req, val:req
ldi \reg, lo8(\val)
ldi \reg + 1, hi8(\val)
.endm
所以
ldi_w r16, 0xBEEF
将 0xEF
加载到 r16
并将 0xBE
加载到 r17
。
重要说明: gnu as
允许使用寄存器编号 (ldi 16, 0xC3
) 以及寄存器名称 (ldi r16, 0xC3
)。上面的宏需要寄存器 numbers,这样我所有的汇编程序源代码都包含 avr_reg_numbers.h
,其中包含
之类的定义
#ifdef r0
#undef r0
#endif
#define r0 0
使用此样式宏,Peter Cordes answr 的示例可以重写为
mov_w r30, r24 ; r30 ← r24, r31 ← r25
ld_w r24, Z
mov_w r30, r22
ld_w r18, Z
add_w r24, r18
p.s.0 我的 avr_as_macro.h
包含很多宏,例如 lds_w
、sts_w
、push_w
、…、ror_w
和很快。它们中的大多数由其他宏汇编程序命令生成。例如,由
生成的asr_w
、lsr_w
和ror_w
//---------------------------- right word shifts
// asr_w, lsr_w, ror_w
.irp cmd, asr, lsr, ror
.macro \cmd\()_w reg:req
\cmd \reg+1 $ ror \reg
.endm
.endr
p.s.1 一些 AVR 芯片有汇编指令 movw
在一个 cpu 周期内移动寄存器对,其中一些(旧的或非常简单的)没有这样的命令.因此,我使用调用 movw
的宏 mov_w
或生成一对命令,具体取决于当前的 AVR arch 级别。
//---------------------------------- word move
// movw exist in all new AVRs except __AVR_TINY__ (tiny4..10/20/40)
// Not exist in tiny26
// odd register numbers and overlapping are not supported
#ifdef __AVR_HAVE_MOVW__
.macro mov_w dst:req, src:req
movw \dst, \src
.endm
#else
.macro mov_w dst:req, src:req
mov \dst, \src $ mov \dst+1, \src+1
.endm
#endif
我对 AVR 汇编完全陌生,我有一个 16 位的十六进制数 0x20C
。
我想在 AVR 程序集的两个 8 位寄存器中加载这个常量十六进制数
是否可以这样做:
LDI R17:R18 0x20C
编辑:如果无法通过这种方式在两个 8 位寄存器中加载 16 位数字,有人可以给我一个替代方案吗?
有一些指令可以对寄存器对进行 16 位加法或递增,但不能从内存加载,当然也不能立即执行。 你需要单独加载每个字节,使用一个ld
/ldi
/ldd
/lds
/每个字节的任何指令/每个目标寄存器。
(有一条指令可以将一对寄存器复制到另一对,在许多现代 AVR CPU 上都支持,(参见
AVR 是一个 RISC 指令集,其中大部分(几乎所有?)整个指令都是 16 位的。没有空间容纳 16 位立即数,只有 8 个。您始终可以将一个立即数分成两个 8 位的一半,例如 ldi r17, 0x0c
用于低半,而 ldi r18, 0x2
对于高半部分。
我检查了 AVR 指令集,没有看到任何多字节加载或立即数(https://onlinedocs.microchip.com/pr/GUID-0B644D8F-67E7-49E6-82C9-1B2B9ABE6A0D-en-US-1/index.html - 更新;在线 ISA ref 的格式远不如以前)。
然后我用 AVR gcc on the Godbolt compiler explorer 编译了这个 C,看看是否有我遗漏的东西。
int return_immediate(void) { return 0x20c; }
ldi r24,lo8(524) # ldi r24, 0x0c
ldi r25,hi8(524) # ldi r25, 0x02
ret
int glob;
int return_global(void) {
return glob;
}
lds r24,glob
lds r25,glob+1
ret
int add(int *a, int *b) {
return *a + *b;
}
mov r30,r24
mov r31,r25 Z = a
ld r24,Z
ldd r25,Z+1 retval = *a
mov r30,r22
mov r31,r23 Z = b
ld r18,Z
ldd r19,Z+1 tmp = *b
add r24,r18
adc r25,r19 retval += tmp
ret
因此,除非 AVR gcc 有遗漏的优化,或者出于某种原因它避免了单词加载,否则您不能这样做。
更新:gcc 以基线 AVR 为目标,因此它不能使用 movw
复制一对寄存器。
使用 -mmcu=avr6
编译使 add(int*, int*)
更高效:
add:
movw r30,r24 # Z = a
ld r24,Z
ldd r25,Z+1
movw r30,r22 # Z = b
ld r18,Z
ldd r19,Z+1
add r24,r18
adc r25,r19
ret
但正如我们所见,仍然没有对一对寄存器执行任何其他操作的指令,因此所有这些都必须单独完成。尽管如此,用一条机器指令复制一对寄存器还是很不错的,因为需要这样做的情况并不少见。
正如Peter Cordes answer中提到的,AVR指令集没有将16位值加载到寄存器对中的指令。但是……
if it is not possible to load a 16 bit number in two 8 bit registers this way, may someone could give me an alternative option?
为方便起见,汇编器通常具有创建宏的能力(如果您打算在汇编中进行大量编程,请注意这个机会)。
宏语法依赖于工具链。对于 gnu as
我使用以下宏将 16 位立即值加载到给定和下一个寄存器
.macro ldi_w reg:req, val:req
ldi \reg, lo8(\val)
ldi \reg + 1, hi8(\val)
.endm
所以
ldi_w r16, 0xBEEF
将 0xEF
加载到 r16
并将 0xBE
加载到 r17
。
重要说明: gnu as
允许使用寄存器编号 (ldi 16, 0xC3
) 以及寄存器名称 (ldi r16, 0xC3
)。上面的宏需要寄存器 numbers,这样我所有的汇编程序源代码都包含 avr_reg_numbers.h
,其中包含
#ifdef r0
#undef r0
#endif
#define r0 0
使用此样式宏,Peter Cordes answr 的示例可以重写为
mov_w r30, r24 ; r30 ← r24, r31 ← r25
ld_w r24, Z
mov_w r30, r22
ld_w r18, Z
add_w r24, r18
p.s.0 我的 avr_as_macro.h
包含很多宏,例如 lds_w
、sts_w
、push_w
、…、ror_w
和很快。它们中的大多数由其他宏汇编程序命令生成。例如,由
asr_w
、lsr_w
和ror_w
//---------------------------- right word shifts
// asr_w, lsr_w, ror_w
.irp cmd, asr, lsr, ror
.macro \cmd\()_w reg:req
\cmd \reg+1 $ ror \reg
.endm
.endr
p.s.1 一些 AVR 芯片有汇编指令 movw
在一个 cpu 周期内移动寄存器对,其中一些(旧的或非常简单的)没有这样的命令.因此,我使用调用 movw
的宏 mov_w
或生成一对命令,具体取决于当前的 AVR arch 级别。
//---------------------------------- word move
// movw exist in all new AVRs except __AVR_TINY__ (tiny4..10/20/40)
// Not exist in tiny26
// odd register numbers and overlapping are not supported
#ifdef __AVR_HAVE_MOVW__
.macro mov_w dst:req, src:req
movw \dst, \src
.endm
#else
.macro mov_w dst:req, src:req
mov \dst, \src $ mov \dst+1, \src+1
.endm
#endif