4.11.2.1 Input and Output Operands
Following the template is a comma-separated list of zero or more output operands, which indicate the names of C objects modified by the assembly code and input operands, which make values from C variables and expressions available to the assembly code.
Each operands has several components, described by:
[ [asmSymbolicName] ] constraint (Cexpression)
where asmSymbolicName
is an optional symbolic
name for the operand, constraint
is string specifying
constraints on the placement of the operand, and Cexpression
is
the C variable or expression to be used by the operand and which is enclosed in
parentheses.
The first (left-most) output operand is numbered 0, any subsequent output operands are numbered one higher than the operand before it, with input operands being numbered in the same way.
The supported constraint letters are tabulated below (see table later in this section for operand modifiers).
Letter | Constraint | Range |
---|---|---|
a |
Simple upper registers | r16 to r23 |
b |
Base pointer registers pairs | r28 to r32 (Y, Z) |
d |
Upper register | r16 to r31 |
e |
Pointer register pairs | r26 to r31 (X, Y, Z) |
l |
Lower registers | r0 to r15 |
q |
Stack pointer register | SPH:SPL |
r |
Any register | r0 to r31 |
t |
Temporary register | r0 |
w |
Special upper register pairs usable in
adiw instruction |
r24, r26, r28, r30 |
x |
Pointer register pair X | r27:r26 (X) |
y |
Pointer register pair Y | r29:r28 (Y) |
z |
Pointer register pair Z | r31:r30 (Z) |
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 |
M |
8-bit integer constant | 0 to 255 |
N |
Integer constant | -1 |
O |
Integer constant | 8, 16, 24 |
P |
Integer constant | 1 |
Q |
Memory address based on Y or Z pointer with displacement | |
Cm2 |
Integer constant | -2 |
C0n |
Integer constant, where
n ranges from 0 to 7 |
n |
Can |
n-byte integer constant that allows AND
without clobber register, where n ranges from 2
to 4 |
|
Con |
n-byte integer constant that allows OR
without clobber register, where n ranges from 2
to 4 |
|
Cxn |
n-byte integer constant that allows XOR
without clobber register, where n ranges from 2
to 4 |
|
Csp |
Integer constant | -6 to 6 |
Cxf |
4-byte integer constant with at least one 0xF nibble | |
C0f |
4-byte integer constant with no 0xF nibbles | |
Ynn |
Fixed-point constant known at compile time | |
Y0n |
Fixed-point or integer constant, where
n ranges from 0 to 2 |
n |
Ymn |
Fixed-point or integer constant, where
n ranges from 1 to 2 |
-n |
YIJ |
Fixed-point or integer constant | -0x3F to 0x3F |
The constraint you choose should match the registers or constants that
are appropriate for the AVR instruction operand. The compiler will check the constraint
against your C expression; however, if the wrong constraint is used, there is the
possibility of code failing at runtime. For example, if you specify the constraint
r
with an ori
instruction, then the compiler is
free to select any register (r0 thru r31) for that operand. This will fail, if the
compiler chooses a register in the range r2 to r15. The correct constraint in this case
is d
. On the other hand, if you use the constraint M
,
the compiler will make sure that you only use an 8-bit immediate value operand.
The table below shows all the AVR assembler mnemonics that require operands and the relevant constraints for each of those operands.
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 |
r,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. Constraints without a modifier specify read-only operands. The constraint modifiers are tabulated below.
Letter | Constraint |
---|---|
= |
Write-only operand, usually used for all output operands. |
+ |
Read-write operand |
& |
Register should be used for output only |
So, in the example:
asm("in %0, %1" : "=r" (value) : "I" (_SFR_IO_ADDR(PORTD)) );
the assembler instruction is defined by the template, "in %0,
%1"
. The %0
token refers to the first output operand,
"=r" (value)
, and %1
refers to the first input
operand, "I" (_SFR_IO_ADDR(PORTD))
. No clobbered registers were
indicated in this example.
The compiler might encode the above in-line assembly as follows:
lds r24,value
/* #APP */
in r24, 12
/* #NOAPP */
sts value,r24
The comments have been added by the compiler to inform the assembler
that the enclosed instruction was hand-written. In this example, the compiler selected
register r24 for storage of the value read from PORTD; however, it might not explicitly
load or store the value, nor include your assembler code at all, based on the compiler's
optimization strategy. For example, if you never use the variable value in the remaining
part of the C program, the compiler could remove your in-line assembly code unless you
switch off the optimizers. To avoid this, you can add the volatile
attribute to the asm()
statement, as shown below:
asm volatile(“in %0, %1” : “=r” (value) : “I” (_SFR_IO_ADDR(PORTD)));
Operands can be given names, if desired. The name is prepended in
brackets to the constraints in the operand list and references to the named operand use
the bracketed name instead of a number after the %
sign. Thus, the
above example could also be written as
asm(“in %[retval], %[port]” :
[retval] “=r” (value) :
[port] “I” (_SFR_IO_ADDR(PORTD)) );
The clobber list is primarily used to tell the compiler about modifications done by the assembler code. This section of the statement can be omitted, but all other sections are required. Use the delimiting colons, but leave the operand field empty if there is no input or output used, for example:
asm volatile(“cli”::);
Output operands must be write-only and the C expression result must be an lvalue, i.e., be valid on the left side of an assignment. Note, that the compiler will not check if the operands are of a reasonable type for the kind of operation used in the assembler instructions. Input operands are read-only.
In cases where you need the same operand for input and output, read-write operands are not supported, but it is possible to indicate which operand’s register to use as the input register by a single digit in the constraint string. 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 used
by 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 can be an issue if the output operand is modified by the
assembler code before the input operand is used. In the situation where your code
depends on different registers used for input and output operands, you must use the
constraint modifier, &
, with the output operand, as shown in the
following example.
asm volatile("in %0,%1" "\n\t"
"out %1,%2" "\n\t"
: "=&r" (result)
: "I" (_SFR_IO_ADDR(port)), "r" (source)
);
Here, a value is read from a port and then a value is written to the
same port. If the compiler chooses the same register for input and output, then the
output value will be clobbered by the first assembler instruction; however, the use of
the &
constraint modifier prevents the compiler from selecting any
register for the output value that is also used for any of the input operands.
Here is another example that swaps the 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)
);
Notice the usage of register __tmp_reg__
, which you can
use without having to save its content. The letters A
and
B
, used in the tokens representing the instruction operands refer
to byte components of a multi-byte register, A
referring to the least
significant byte, B
the next most significant byte, etc.
The following example, which swaps bytes of a 32-bit value, uses the
C
and D
components of a 4 byte quantity, and
rather than list the same operand as both input and output operand (via
"0"
as the input operand constraint), it can also be declared as a
read-write operand by using "+r"
as the output constraint.
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. This also implies, that it is often necessary to cast the type of an input operand to the desired size.
If an input operand constraint indicates a pointer register pair, such
as "e" (ptr)
, and the compiler selects register Z (r30:r31), then you
must use %a0
(lower case a
) to refer to the Z
register, when used in a context like:
ld r24,Z