I've been doing some AVR assembly programming for a university course, and I recently happened upon a situation where I would have wanted an "add immediate" instruction. However, no such instruction seems to exist within the Atmel AVR instruction set. I find this weird given that there are immediate variants of various other instructions, such as subtract, logical AND, compare, and even "add immediate to word" (ADIW), among others. What can I use in place of add immediate to perform immediate addition when doing AVR assembly programming?
3 Answers
There is a subi
instruction - "subtract immediate". You can easily do addition using this instruction, so one can speculate there isnt a need for a dedicated add immediate instruction.
For example, to add 2:
subi r16, 254
Alternatively, you can also express it like this using a negative number:
subi r16, -2
-
1\$\begingroup\$ Is this going to work as expected with the various flags? This might be setting the carry/overflow/underflow bit, as an example... \$\endgroup\$ Commented May 4, 2023 at 15:44
-
1\$\begingroup\$ @VladimirCravero theoretically it shouldn't have any ill-effects. The
subi
instruction sets the flags based on two's complement overflow. Here we are subtracting minus 2 (in twos comp) so the overflow flags should give the correct result as if we just added 2. \$\endgroup\$ Commented May 4, 2023 at 15:52 -
1\$\begingroup\$ @TomCarpenter: For non-zero operand values, that would be true, but if AVR is typical of 8-bit machines with two's-complement carry/non-borrow flag, subtracting 0 would set the carry flag, while adding 0 would clear it. \$\endgroup\$– supercatCommented May 4, 2023 at 16:47
-
6\$\begingroup\$ I don't have an AVR IDE or assembler laying around, so I'll ask the lazy question: Would it be possible to code this as
subi r16, -2
as a hint to future maintainers what's being attempted here? \$\endgroup\$– spuckCommented May 4, 2023 at 18:38 -
2\$\begingroup\$ @spuck I tested this in Atmel Studio 7.0, and yes, it does work as you would expect. \$\endgroup\$– NewbyteCommented May 14, 2023 at 8:36
I find this weird given that there are immediate variants of various other instructions
The rationale behind this instruction design is that add-immediate can be performed by subtract-immediate, hence no silicon wasted1. All that has to be done is to take the 2's complement of the immediate value, which can be done with zero overhead as the value is known at assembly time.
The following code shows how to use that with the GNU assembler2.
For example, this code will add a value of 2
to 32-bit register R19:R16
subi r16, lo8(-2)
sbci r17, hi8(-2)
sbci r18, hlo8(-2)
sbci r19, hhi8(-2)
This can also be used with symbols that are only known at link time.
Suppose you have a 16-bit value in the Z-register and want to access an array of 16-bit values my_array
at index Z:
; Multiply Z by 2 to access array of 16-bit values
lsl r30
rol r31
; Add start address of array my_array to Z.
subi r30, lo8(-(my_array))
sbci r31, hi8(-(my_array))
; Read 16-bit value from my_array using post-increment addressing.
ld r16, Z+
ld r17, Z+
; ...
.data
my_array:
.space 2 * 256
In the case where the offset is known at assembly time and in the range 0...−63, subtracting from a W register wider than 16 bits can be combined3 with sbiw
, e.g. subtracting 10
from 32-bit register R24:
sbiw r24, 10
sbci r26, 0
sbci r27, 0
1Or to put silicon to a better use: The first AVR instruction design actually did have add-immediate, but soon was kicked out to free some silicon for instructions that cannot be trivially represented by others.
2Which has the benefit that it can be used in conjunction with avr-gcc, i.e. one can combine assembly with C/C++.
3Except for devices from the Reduced Tiny familiy like ATtiny10, which support neither sbiw
nor adiw
.
If the assembler isn't braindead, it should be synthesizing addi
using subi
. Some assemblers are lacking in that respect so YMMV. I'm sure Microchip's doesn't synthesize addi even though it'd be trivial to do so.
Anyway, all you need is a macro:
.MACRO ADDI
SUBI @0, low(256-@1)
.ENDMACRO
; Usage
LDI R0, 1
ADDI R0, $AB
; now R0 holds $AC
Yes, I am making a strong judgement about an assembler that supports subi
but doesn't synthesize addi
. What's up with that? Synthesis of more descriptive opcodes is common in various architectures.
Imagine that on RISCV you'd have no nop
because "it doesn't exist", and had to type addi x0, x0, 0
every time you meant nop
? And yes, RISCV does not "waste" an opcode on a nop. By convention, nop is encoded as addi x0, x0, 0
and that's that.
The situation on AVR isn't much different. If you mean addi
, the assembler should just do it, since the hardware supports it directly and in a single instruction...
Also, AVR wastes a perfectly good opcode (nop
) where it could have been encoded as, say mov r17, r17
= 0010'1111'0001'0001b
. Takes same time as a nop
, has same side effects (none)...
ldi+add
takes two cycles (and clobbers another register).subi
takes one. \$\endgroup\$