Search code examples
cassemblyavrinline-assemblyavr-gcc

ASM inline assembly bubble sort; constant value required, garbage at end of line


I have an Arduino MEGA program written in C that populates an array with random integers, then calls a bubble sort algorithm written in ASM inline assembly. The sorted integers are then converted into binary and eight LEDs are lit with each LED corresponding to one bit of the binary number.

Firstly, the declared global variables.

const byte arraySize = 10;
volatile byte randomNums[arraySize]; 
volatile byte limit = arraySize-1;
volatile byte counter = 1;
volatile byte iteration = 1;

Next, the main program loop (for simplicity, I will omit the binary conversion and LED code).

void loop() {

    for (int i = 0; i < arraySize; i++) {
        randomNums[i] = random(255);
    }

    // asm inline bubble sort here
}

Lastly, the bubble sort algorithm in ASM inline assembly.

asm volatile(

    "               lds        r20, (limit)        ; position before end of array \n"
    "               lds        r21, (counter)      ; counter for loop is defined i=0 \n"
    "               lds        r22, (iteration)    ; counter set for iteration of sort algorithm k=1 \n"
    "               mov        r21, r27            ; i=1 \n"
    "               mov        r22, r28            ; iteration number in r28=k \n"
    "               lds        r23, (randomNums)   ; point to beginning of array by r23 'element' \n"
    "               mov        r23, r24            "
    "               add        1, r24              ; point to 'neighbour' \n"
    
    " check%=:      mov        r23, r25            ; get 'element' and place in r25 \n"
    "               mov        r24, r26            ; get 'neighbour' in array in r26 \n"
    "               cp         r25, r26            ; compare both values \n"
    "               brge       swap%=              ; swap the numbers \n"
    "               add        1, r23              ; increment pointer r23 \n"
    "               add        1, r24              ; increment pointer r24 \n"
    "               add        1, r27              ; increment loop counter \n"
    "               eor        r27, (limit)        ; xor check if not exceeding array size \n"
    "               brne       check%=             "
    
    " swap%=:       mov        r23, r24            ; swap content where index r23 is pointing to where index r24 is pointing \n"
    "               mov        r26, r23            ; move greater number to position after smaller number \n"
    "               add        1, r23              ; increment pointer r23 \n"
    "               add        1, r24              ; increment pointer r24 \n"
    "               add        1, r28              ; increment loop counter \n"
    "               cp         r28, (arraySize)    ; check not exceeding array capacity \n"
    "               ret                            "         
                              
    ::: "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28"); // clobbered registers

The program works (save for the sorting) when the assembly is commented out. When I compile the program with the assembly, I get the following obscure errors.

C:\Users\USERNAME\AppData\Local\Temp\cc2d2uAp.s: Assembler messages:
C:\Users\USERNAME\AppData\Local\Temp\cc2d2uAp.s:1963: Error: garbage at end of line
C:\Users\USERNAME\AppData\Local\Temp\cc2d2uAp.s:1971: Error: constant value required
C:\Users\USERNAME\AppData\Local\Temp\cc2d2uAp.s:1972: Error: garbage at end of line
C:\Users\USERNAME\AppData\Local\Temp\cc2d2uAp.s:1977: Error: constant value required
lto-wrapper.exe: fatal error: E:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avr-gcc returned 1 exit status
compilation terminated.
e:/program files (x86)/arduino/hardware/tools/avr/bin/../lib/gcc/avr/7.3.0/../../../../avr/bin/ld.exe: error: lto-wrapper failed
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Arduino Mega ADK.

I've searched online and taken a look at the AVR assembly manual and I can't figure out what these errors mean and where they are occurring. I'll add that I'm new to AVR assembly and ASM inline.


Solution

  • The "garbage at end of line" problems are caused by missing \n characters at the end of the lines. There are three of these lines but I suspect the third is okay because it's at the end.

    The other two problems are with the following lines, I don't believe these are allowed to have memory operands:

    eor r27, (limit)
    cp  r28, (arraySize)
    

    The line number differences between those two lines match the difference (1977 - 1971) in the generated errors, once you take into account the following two lines are considered as one due to the missing \n on the first:

    "               brne       check%=   "
    " swap%=:       mov        r23, r24  ; swap content ... \n'
    

    As an aside (as pointed out by Jester in the comments), it looks like Atmel don't have an immediate-operand ADD instruction (see here), so I'd be circumspect about instructions of the form:

    add 1, r27
    

    There are a number of possibilities why this might be allowed:

    • The problem could just be hidden by your current errors;
    • It could be a deficiency in the inline assembler that treats it as add r1, r27;
    • It could be a feature in the inline assembler in that it could be generating code to emulate this operation.

    On that last point, I've seen this sort of thing done before, such as a "multi-register push":

    push r1, r7, r42
    

    which just assembles to three single-register pushes.

    It may be that the assembler is smart enough to turn add 1, r27 into inc r27. Since the only immediate value you're adding is 1, this is a possibility.

    This also points to a possible solution should it turn out to be a deficiency and it's erroneously adding r1 rather than 1, to r27. Just turn those add instructions into inc instructions.


    And just commenting on your code logic rather than just the syntax, I'm not sure you've correctly separated the pointer and content concepts correctly. It looks like r23/24 are meant to be the addresses of cells in the randomNums array but the instruction:

    lds r23, (randomNums)
    

    will load the first value of that array into the register. So, consider:

                        +---+---+---+---+
    randomNums @ 0x1000 | 4 | 2 | 3 | 1 |
                        +---+---+---+---+
    

    The instruction you use would have r23 being the value 4 rather than he address 1000. The ldi instruction is what you would use to load up an immediate value.


    Even after fixing that, extracting the values using:

    mov r23, r25
    mov r24, r26
    

    won't work because you're transferring the addresses, not using those addresses to get the values for comparison.

    Loading values indirectly using a register is usually done by loading that register into one that can be used by the lpm instruction, such as Z (R30/31).


    Additionally, while you branch to swap, you return from there meaning, even if you had the right addresses set up, you would swap at most one pair of elements.

    One fix is to call swap as a subroutine, rather than branching to it, and modify it so that it only swaps and returns, removing the register manipulations and comparison - they should be done back in the main code.

    The other (preferable in my opinion) is to treat it like an if statement and just skip over the code that swaps, something like:

               cp    r25, r26      ; compare both values.
               brle  noswap%=      ; skip swap if already ordered.
    
               @swap (r25), (r26)  ; actual code to swap goes here.
    noswap%=:
               add        1, r23   ; carry on with loop.