5
\$\begingroup\$

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?

\$\endgroup\$
7
  • \$\begingroup\$ AFAIK ADIW is only used for pointer-arithmeric. I guess there was no need to implement a generic Add-Intermediate for the whole register file. IMO this was a design decision based on complexity and size constraints without any architectural reasoning. Remember: AVR is old....really old (In terms of time and especially technology). But: This is my intuition - not my expertise. \$\endgroup\$ Commented May 4, 2023 at 6:47
  • \$\begingroup\$ The only people would could give you a definitive answer would be the designers of that chip. But note nearly all engineering decisions are a result of a set of design goals and more importantly, the compromises made to achieve them. Every instruction a CPU can execute requires some circuitry on the IC. I would guess (without any real proof) they decided the circuitry required to implement that instruction was too much to justify its inclusion, the space it would take up being more useful to other operations. \$\endgroup\$
    – Kyle B
    Commented May 4, 2023 at 6:58
  • \$\begingroup\$ maybe the add immediate can be accomplished with two instructions and use the same number of cycles as an actual add immediate instruction would \$\endgroup\$
    – jsotola
    Commented May 4, 2023 at 6:59
  • \$\begingroup\$ @jsotola This is incorrect for AVR, all 8 bit arithmetic is single cycle. An LDI+ADD would be 2 cycles. \$\endgroup\$
    – Jeroen3
    Commented May 4, 2023 at 7:19
  • 1
    \$\begingroup\$ @jsotola nope. The lovely thing about classic AVR is that essentially all instructions (everything but multiplication and memory access) take one cycle. ldi+add takes two cycles (and clobbers another register). subi takes one. \$\endgroup\$
    – hobbs
    Commented May 4, 2023 at 7:20

3 Answers 3

10
\$\begingroup\$

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
\$\endgroup\$
6
  • 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\$
    – supercat
    Commented 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\$
    – spuck
    Commented 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\$
    – Newbyte
    Commented May 14, 2023 at 8:36
3
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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)...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.