Each input and output operand is described by a constraint string followed by a C expression in parantheses. AVR-GCC 3.3 knows the following constraint characters:

Constraint

Used for

Range

a

Simple upper registers

r16 to r23

b

Base pointer registers pairs

y, z

d

Upper register

r16 to r31

e

Pointer register pairs

x, y, z

q

Stack pointer register

SPH:SPL

r

Any register

r0 to r31

t

Temporary register

r0

w

Special upper register pairs

r24, r26, r28, r30

x

Pointer register pair X

x (r27:r26)

y

Pointer register pair Y

y (r29:r28)

z

Pointer register pair Z

z (r31:r30)

G

Floating point constant

0.0

I

6-bit positive integer constant

0 to 63

J

6-bit negative integer constant

-63 to 0

K

Integer constant

2

L

Integer constant

0

l

Lower registers

r0 to r15

M

8-bit integer constant

0 to 255

N

Integer constant

-1

O

Integer constant

8, 16, 24

P

Integer constant

1

Q

(GCC >= 4.2.x) A memory address based on Y or Z pointer with displacement.

R

(GCC >= 4.3.x) Integer constant.

-6 to 5

The selection of the proper contraint depends on the range of the constants or registers, which must be acceptable to the AVR instruction they are used with. The C compiler doesn't check any line of your assembler code. But it is able to check the constraint against your C expression. However, if you specify the wrong constraints, then the compiler may silently pass wrong code to the assembler. And, of course, the assembler will fail with some cryptic output or internal errors. For example, if you specify the constraint "r" and you are using this register with an "ori" instruction in your assembler code, then the compiler may select any register. This will fail, if the compiler chooses r2 to r15. (It will never choose r0 or r1, because these are uses for special purposes.) That's why the correct constraint in that case is "d". On the other hand, if you use the constraint "M", the compiler will make sure that you don't pass anything else but an 8-bit value. Later on we will see how to pass multibyte expression results to the assembler code.

The following table shows all AVR assembler mnemonics which require operands, and the related contraints. Because of the improper constraint definitions in version 3.3, they aren't strict enough. There is, for example, no constraint, which restricts integer constants to the range 0 to 7 for bit set and bit clear operations.

Mnemonic

Constraints

Mnemonic

Constraints

adc

r,r

add

r,r

adiw

w,I

and

r,r

andi

d,M

asr

r

bclr

I

bld

r,I

brbc

I,label

brbs

I,label

bset

I

bst

r,I

cbi

I,I

cbr

d,I

com

r

cp

r,r

cpc

r,r

cpi

d,M

cpse

r,r

dec

r

elpm

t,z

eor

r,r

in

r,I

inc

r

ld

r,e

ldd

r,b

ldi

d,M

lds

r,label

lpm

t,z

lsl

r

lsr

r

mov

r,r

movw

r,r

mul

r,r

neg

r

or

r,r

ori

d,M

out

I,r

pop

r

push

r

rol

r

ror

r

sbc

r,r

sbci

d,M

sbi

I,I

sbic

I,I

sbiw

w,I

sbr

d,M

sbrc

r,I

sbrs

r,I

ser

d

st

e,r

std

b,r

sts

label,r

sub

r,r

subi

d,M

swap

r

Constraint characters may be prepended by a single constraint modifier. Contraints without a modifier specify read-only operands. Modifiers are:

Modifier

Specifies

=

Write-only operand, usually used for all output operands.

+

Read-write operand

&

Register should be used for output only

Output operands must be write-only and the C expression result must be an lvalue, which means that the operands must be valid on the left side of assignments. Note, that the compiler will not check if the operands are of reasonable type for the kind of operation used in the assembler instructions.

Input operands are, you guessed it, read-only. But what if you need the same operand for input and output? As stated above, read-write operands are not supported in inline assembler code. But there is another solution. For input operators it is possible to use a single digit in the constraint string. Using digit n tells the compiler to use the same register as for the n-th operand, starting with zero. Here is an example:

asm volatile("swap %0" : "=r" (value) : "0" (value));

This statement will swap the nibbles of an 8-bit variable named value. Constraint "0" tells the compiler, to use the same input register as for the first operand. Note however, that this doesn't automatically imply the reverse case. The compiler may choose the same registers for input and output, even if not told to do so. This is not a problem in most cases, but may be fatal if the output operator is modified by the assembler code before the input operator is used. In the situation where your code depends on different registers used for input and output operands, you must add the & constraint modifier to your output operand. The following example demonstrates this problem:

asm volatile("in %0,%1"    "\n\t"
             "out %1, %2"  "\n\t" 
             : "=&r" (input) 
             : "I" (_SFR_IO_ADDR(port)), "r" (output)
            );

In this example an input value is read from a port and then an output value is written to the same port. If the compiler would have choosen the same register for input and output, then the output value would have been destroyed on the first assembler instruction. Fortunately, this example uses the & constraint modifier to instruct the compiler not to select any register for the output value, which is used for any of the input operands. Back to swapping. Here is the code to swap high and low byte of a 16-bit value:

asm volatile("mov __tmp_reg__, %A0" "\n\t"
             "mov %A0, %B0"         "\n\t"
             "mov %B0, __tmp_reg__" "\n\t"
             : "=r" (value)
             : "0" (value)
            );

First you will notice the usage of register __tmp_reg__, which we listed among other special registers in the Assembler Code section. You can use this register without saving its contents. Completely new are those letters A and B in %A0 and %B0. In fact they refer to two different 8-bit registers, both containing a part of value.

Another example to swap bytes of a 32-bit value:

asm volatile("mov __tmp_reg__, %A0" "\n\t"
             "mov %A0, %D0"         "\n\t"
             "mov %D0, __tmp_reg__" "\n\t"
             "mov __tmp_reg__, %B0" "\n\t"
             "mov %B0, %C0"         "\n\t"
             "mov %C0, __tmp_reg__" "\n\t"
             : "=r" (value)
             : "0" (value)
            );

Instead of listing the same operand as both, input and output operand, it can also be declared as a read-write operand. This must be applied to an output operand, and the respective input operand list remains empty:

asm volatile("mov __tmp_reg__, %A0" "\n\t"
             "mov %A0, %D0"         "\n\t"
             "mov %D0, __tmp_reg__" "\n\t"
             "mov __tmp_reg__, %B0" "\n\t"
             "mov %B0, %C0"         "\n\t"
             "mov %C0, __tmp_reg__" "\n\t"
             : "+r" (value));

If operands do not fit into a single register, the compiler will automatically assign enough registers to hold the entire operand. In the assembler code you use %A0 to refer to the lowest byte of the first operand, %A1 to the lowest byte of the second operand and so on. The next byte of the first operand will be %B0, the next byte %C0 and so on.

This also implies, that it is often neccessary to cast the type of an input operand to the desired size.

A final problem may arise while using pointer register pairs. If you define an input operand

"e" (ptr)

and the compiler selects register Z (r30:r31), then

%A0 refers to r30 and %B0 refers to r31.

But both versions will fail during the assembly stage of the compiler, if you explicitely need Z, like in

ld r24,Z

If you write

ld r24, %a0

with a lower case a following the percent sign, then the compiler will create the proper assembler line.