Assembly: Impossible constraint, why and how to fix?

I’m trying to have some fun with assembly programming, but I have stumbled over an annoying “issue” and I cannot seem to fix it. I can se others have had similar issues, but I do not understand why method_a compiles whilst method_b does not.

#include <inttypes.h>
#include <avr/io.h>

void method_a()
{
    volatile uint8_t * pin_port = &PORTB;
    uint8_t pin_bit = 2;
    asm volatile (
        "sbi %[pinp], %[pinb] \n"
        ::
        [pinp] "I" (_SFR_IO_ADDR(*pin_port)),
        [pinb] "I" (pin_bit)
    );
}

void method_b(volatile uint8_t * pin_port, uint8_t pin_bit)
{
    asm volatile (
        "sbi %[pinp], %[pinb] \n"
        ::
        [pinp] "I" (_SFR_IO_ADDR(*pin_port)),
        [pinb] "I" (pin_bit)
    );
}

int main()
{
    //init();
    method_a();
    method_b(&PORTB, 2);
}

Would sure like a way of passing a PORT* define as an argument to a method in where it is to be used in assembly :slight_smile:

TLDR: The second method doesn’t compile, because it is impossible by default. The compiler cannot dereference the function argument pin_port at compile time, because the content is unknown. The first function compiles, because it does not depend on function arguments. The compiler knows, that it has to dereference &PORTB and use the constant ‘2’ when generating the code.

Instead of a function you should use a macro, which will be expanded and inlined at compile time.

#define SETBIT(port, bit) __asm__ __volatile__("sbi %0, %1":: "I" (_SFR_IO_ADDR(port)), "I" (bit))

Instead of using the macro you should use c syntax, which will generate the same code.

// assembler macro
SETBIT(PORTB, 2);

// c syntax
PORTB |= (1 << 2);

compiles to (optimization level 1 and higher):

SETBIT(PORTB, 2);
00000040  SBI 0x05,2 Set bit in I/O register 

PORTB |= (1 << 2);
00000041  SBI 0x05,2 Set bit in I/O register

More detailed answer:
The avr-gcc uses strictly defined rules when calling/compiling a function. The register usage can be found in the official documentation.

When you call method_b with &PORTB and 2, the compiler will load the first argument into r25:r24 and the second argument into (r23:)r22.
In modern processors, function arguments are pushed onto the stack prior to calling the function. However, the avr has a lot more registers to be used (32 vs. 13 on ARM). This optimizes function calls, because costly memory access operations (pushing and poping to and from the stack) can be reduced.

Before the call or icall instruction is executed, the address of PORTB now resides in the register-pair r25:r24 and the bit to set in register r22. When generating the code for the function body, the compiler is aware of the argument placement in the register file. When resolving the arguments to the inline assembly, the arguments don’t match the constraints for the sbi instruction, which are I,I (two positive, 6-bit constants), because they are not constant but a registers content. They are not constant, because their value depends on the function call (calling with PORTC will result in a different address in r25:r24).

To resolve this conflict you have two possibilities:

  1. You can write to the memory region instead of the i/o region (using the st instruction). This requires you to perform a RMW-cycle on the memory location. The argument pin_bit has to contain the bitmask (shifted) instead of the pin number.
void method_b(volatile uint8_t * pin_port, uint8_t pin_bit)
{
    asm volatile (
    "ld __tmp_reg__ , %a0 \n\t"
    "or __tmp_reg__, %1 \n\t"
    "st %a0, __tmp_reg__ \n\t"
    :: "e" (pin_port),
        "r" (pin_bit)
    );
}
  1. You check which port was passed to the function in a switch/case or if/else block and invoke the right sbi instruction with hard-coded addresses.

These two options will result in very inefficient code, because what you really need to do is an sbi with the correct port and pin as constants. Use the macro or the c-style bit-set instead.

Thanks alot! Very well explained and much appreciated! :slight_smile:

The code is not about setting a bit in a port, the code is just a minimal example used to reproduce a problem in something else. I figured out a third way which will allow the “same” code to be used on multiple port/bit combinations in a sleek way:

template <unsigned char PIN_PORT, unsigned char PIN_BIT> void method_c()
{
    asm volatile (
        "sbi %[pinp], %[pinb] \n"
        ::
        [pinp] "I" (PIN_PORT),
        [pinb] "I" (PIN_BIT)
    );
}

const unsigned char pin_port = _SFR_IO_ADDR(PORTB);
const unsigned char pin_bit = 2;

int main()
{
    method_c<pin_port, pin_bit>();
}

Upside is that the code only has to be maintained in one place and it is easaly reusable (eg. in a library), downside is that whenever a new port/bit combination is introduced the code will need to reproduce and this will consume program storage. The code is yet to be fully tested, but it compiles! :slight_smile:

EDIT: Tested, works perfectly :slight_smile:

You’re welcome. Indeed, templating is quite similar to macros in a pure c implementation (depending on the code size of the function the compiler will insert either a call instruction or inlines the code). However, templating can only be used with C++ and for me it seems quite bothersome to use in this specific context. Also I think setbit(PORTC, 2) is more readable than setbit<MyPortC, 2>(), but that’s just my opinion.

I guess you are coding in assembler to gain better understanding of the inner workings of the processor and its peripherals? If so, have you considered writing the assembly code in pure assembly instead of inline assembly? This way everything that is going on becomes a lot clearer, because you have to develop an understanding of how everything works and also how the compiler “does stuff”.

As mentioned, this is not about setting a bit in a port - I would have used "PORTB |= bit(2)" if it was. It is a larger routine that spits out an array of bytes through a pin and timing is critical if the receiver has to understand it. The receiver is also going to be coded in assembly, of course! It is all just for fun and learning, though :slight_smile:

At some point I will try to code entirely in assembly, but for now it is just about understanding how to transmit data through a single pin/wire at different frequencies. Using serial for debugging is quite convenient with the Arduino serial monitor :wink:

Danois90:
At some point I will try to code entirely in assembly, but for now it is just about understanding how to transmit data through a single pin/wire at different frequencies. Using serial for debugging is quite convenient with the Arduino serial monitor :wink:

You can create a new file in the arduino IDE with the *.S extension (capital s!):
test.ino

extern "C" void asm_function();

void setup()
{
 asm_function();
}

void loop()
{ 
}

whatevername.S

.global asm_function

asm_function:
  push r16
  subi r16, 5
  pop r16
  ret

That way you can define functions in pure assembler and call them from your code like any other c function.