Search code examples
assemblyavr

AVR assembly: load a 16 bit number into two 8 bit registers


I am completely new to AVR assembly and I have a hex number 0x20C which is 16 bit.

I want to load this constant hex number in two 8 bit registers in AVR assembly

Is it possible to do something like:

LDI R17:R18 0x20C

EDIT: 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?


Solution

  • There are a few instructions to do 16-bit addition or increment with pairs of registers, but not loads from memory and certainly not immediates. You need to load each byte separately, using one ld/ldi/ldd/lds/whatever instruction for each byte / each destination register.

    (There is an instruction to copy a pair of registers to another pair, upported on many modern AVR CPUs, (see @ReAl's answer), but not load or load-immediate. You can make a macro for 2 instructions as ReAl shows, but it won't improve performance or code-size, just human readability.


    AVR is a RISC instruction-set where most (almost all?) whole instructions are 16-bit. There isn't room for a 16-bit immediate, only 8. You can always split an immediate into its two 8-bit halves, like ldi r17, 0x0c for the low half and ldi r18, 0x2 for the high half.

    I checked the AVR instruction set and didn't see any multi-byte loads or immediates (https://onlinedocs.microchip.com/pr/GUID-0B644D8F-67E7-49E6-82C9-1B2B9ABE6A0D-en-US-1/index.html - update; the online ISA ref is much less nicely formatted than it used to be).

    Then I compiled this C with AVR gcc on the Godbolt compiler explorer to see if maybe there was something I was missing.

    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
    

    So unless AVR gcc has a missed optimization, or there's some reason it's avoiding a word load, you can't do it.

    Update: gcc is targeting baseline AVR, so it can't use movw to copy a pair of registers.

    Compiling with -mmcu=avr6 makes add(int*, int*) more efficient:

    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
    

    But as we can see, there's still no instruction for doing anything else to a pair of registers, so it all has to be done separately. Still, copying a pair of registers with one machine instruction is quite nice because it's not rare to need to do that.