4

I am currently developing software for the Atmel AVR32 AT32UC3C0512C. The software consists of C modules (.c files) as well as standalone assembler modules (.S files). My IDE is Microchip Studio 7.0.2594 (formerly known as Atmel Studio). The toolchain incorporates avr32-gcc 4.4.7.

In the assembler modules, I actually can access global variables that I have defined in the C modules, but the method I am using since many years seems quite dumb to me:

Suppose I have defined a global variable global_Test in a C file (e.g., unsigned long global_Test; in one of the C modules). In a standalone assembler .S module (important note: that is really an uppercase S, not a lowercase s), I can access the variable by the following method:

.pushsection .MySection, "ax", @progbits
.org 0x00000000

/* Define the function with appropriate scope */
.global MyFunc
.type MyFunc, @function
.balign 4

MyFunc:

/* Some code ... */

/* Load the variable address into R8 */
LDDPC R8, global_Test_Addr

/* Do something with the variable, e.g., set it to 1 */
MOV   R9, 1
ST.W  R8, R9

/* Further code ... */
ret

.type global_Test_Addr, @function
.balign 4
global_Test_Addr:
  .long global_Test

This method works reliably, but as mentioned above, it seems dumb to me. It is clear that the address of the variable is known at compile time or link time, respectively, but I didn't find a way to use it in another way than shown above. For example, the following does not work:

.pushsection .MySection, "ax", @progbits
.org 0x00000000

/* Define the function with appropriate scope */
.global MyFunc
.type MyFunc, @function
.balign 4

MyFunc:

/* Some code ... */

/* Load the variable address into R8 */
MOV R8, global_Test & 0x0000FFFF
ORH R8, global_Test >> 16

/* Further code ... */
ret

With the above code, the assembler throws errors like

error: invalid operands (*UND* and *ABS* sections) for `&'
error: invalid operands (*UND* and *ABS* sections) for `>>'

In case somebody wonders: Loading a register like R8 in two steps is necessary because the AVR32 instruction set does not feature an instruction that loads a 32-bit immediate into a register at once.

I am aware that the MCU mentioned above has only 64 kB of internal RAM, and hence I simply could use MOV R8, global_Test, which works and does not lead to error messages.

However, I am interested in general why something simple like MOV R8, global_Test >> 16 leads to errors, especially taking into account that e.g. MOV R8, 123456789 >> 16 does not lead to errors. I don't get why the constant 123456789 is treated differently from the constant global_Test, which (in assembler code) is the address of global_Test and is evaluated at compile or linkage time.

Update / Additional information

I should have mentioned that I believe that the toolchain is constructed in a way that I wouldn't have expected. It seems that the assembler modules are not translated by a special assembler like gas, but by the normal avr32-gcc that also translates the C modules. That is, the (preprocessing) assembler is the same executable as the C compiler. When building the project, I see the following line in the output:

Invoking: AVR32/GNU Preprocessing Assembler : 4.4.7
"C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr32\avr32-gnu-toolchain\bin\avr32-gcc.exe" -x assembler-with-cpp -c -mpart=uc3c0512c -I "C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\UC3C_DFP\1.0.49\include\AT32UC3C0512C" -I "../source" -I "C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\UC3C_DFP\1.0.77\include\AT32UC3C0512C"  -MD -MP -MF "core/slave/tc-s.d" -MT"core/slave/tc-s.d" -MT"core/slave/tc-s.o" -Wa,-g   -o "core/slave/tc-s.o" "../core/slave/tc-s.S" 

I vaguely remember that I can invoke a different assembler if I change the file extension for the assembler module from an uppercase S to a lowercase s, but this didn't work a few moments ago (the same compiler / assembler was invoked). I'll have to investigate this.

10
  • Guessing from my NASM/8086 experience, you may be right that the label address is resolved at link time. But there isn't necessarily an object file relocation format for such calculations, so the assembler cannot encode your desired instruction as it cannot communicate it to the linker. Commented Oct 29 at 19:29
  • 1
    @ecm: AVR is an 8-bit ISA with 16-bit (or wider) addresses; it needs relocations like that to work. But there's special syntax for them; assemblers don't try to pattern-recognize arbitrary math operators and constants into those hi/lo relocations. Commented Oct 29 at 19:54
  • 1
    @PeterCordes AVR32 family is not 8 bit. Commented Oct 29 at 21:38
  • 1
    @0___________ Oh I see, en.wikipedia.org/wiki/AVR32 is a totally unrelated 32-bit RISC ISA. I fixed the tags. Commented Oct 29 at 22:36
  • 1
    gcc on a .S runs it through the C preprocessor (for #if / #define / #include processing), then through the assembler as. That's all, no magic, same assembler you'd use with a .s. The hi/lo stuff is handled by the assembler not the preprocessor. As 0___________ says, look at C compiler output for a function the returns the address of a global, or which loads or stores a global, to see exactly what they're called in AVR32 syntax. The GAS manual might also say, but asking a compiler is easy in this case. (For load or store, hopefully can use lo(symbol) in an addr mode.) Commented Oct 30 at 12:59

1 Answer 1

8

you cannot use C-style bitwise operators (&, >>) on symbols like global_Test, because those are relocatable.

You must use GAS relocation modifiers that tell the assembler to take the low or high 16 bits of the address.

EDIT: tested and lo() hi() is the correct syntax

    mov  r8, lo(global_Test)
    orh  r8, hi(global_Test)

lo(global_Test) tells the assembler: Insert a relocation for the lower 16 bits of global_Test’s address.

hi(global_Test) tells it: Insert a relocation for the upper 16 bits of global_Test’s address.

The linker will then fix these correctly when the final address is known.

EDIT:

Compile using -Os -S

unsigned x;


void foo(void)
{
    unsigned *volatile p = &x;
}

and see the asembler output. I do not have avr32 toolchain but it can be lo16(global_Test) or lo(global_Test)

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the explanation, appreciated and upvoted. However, I already had suspected something like that and therefore had already tried various variants of similar syntax (e.g., LOW() and HIGH()) without success. Unfortunately, the variant you have mentioned (%lo() and %hi()) does not work either: It leads to the error error: bad expression. I still think that it is possible somehow, but I am not able to find the correct syntax. The AVR32 toolchain is extremely poorly documented, if at all. What would be the syntax in case of MC 68000? The AVR32 architecture is somehow related.
68000 is a CISC which can put a 32-bit immediate into a register in a single instruction. It wouldn't have an equivalent.
Thanks again. I simply thought that there would be a syntax for moving the low part of an immediate to a register, even though it can move the whole immediate at once. I was hoping that this syntax would then be similar in AVR32-GCC.
Please see also my update to the question.
@0___________ Thank you very much for updating your answer. You are right. lo(global_Test) works. That was probably the only syntax I didn't try until now :-). The assembler accepts it without any error. I still have to verify that it produces the correct code / addresses, but I am convinced that this will be case. I'll report back if not.
It does - it for this purpose. Different GCC MCU implementations have a bit different syntax :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.