How to add new AVR targets?

I'm beginning to use the AVR backend. I ran into the problem, that newer
(tiny1, mega0) µC as a tiny1614 are not supported. How do I add these to
the AVR backend?

Thanks!

Hey Wilhelm,

This should be possible by editing the ‘AVRDevices.td’ [1]TableGen definitions to add an entry for the newer chip types. You will need to instruct LLVM which features are available on the chip (such as extended LPM support), cross referencing with the instruction set manual for the AVR MCU you’re adding to figure out what is allowed and what is not.

If there are newer instructions not yet supported by the AVR backend, or if their are device-specific constraints not already handled by the backend in its AVRDevices feature declarations, then you will need to either extend the backend to support it, or declare the chip with a lower level of support than is strictly required as a workaround.

Then after that, it should be possible to recompile the backend and target the new chip type with ‘llc -march=avr -mcpu=tiny1614’. for example.

Regards,
Dylan

Thanks!

The new are of xmega3 architecture, which is already included. So this
should be simple.

Where is the information about ISR-vector table, SRAM addresses and so
on stored?

The new are of xmega3 architecture, which is already included. So this
should be simple.

Where is the information about ISR-vector table, SRAM addresses and so
on stored?

At the moment, this is not implemented in LLVM; these details are left to the frontend. Clang/compiler-rt does not include the usual ISR table or AVR-specific startup routines to initialize SRAM from program memory - avr-clang-compiled executables must currently be linked with avr-libc/libgcc. Users of the AVR-Rust frontend have implemented these tables and routines in assembly in each source repository, or more commonly just linked against avr-libc and GCC’s AVR CRT libraries. Ideally, we would provide our own implementation of these independent of the GCC/GNU ecosystem in somewhere like compiler-rt.

Hope that helps,

Regards,
Dylan

    The new are of xmega3 architecture, which is already included. So this
    should be simple.

    Where is the information about ISR-vector table, SRAM addresses and so
    on stored?

At the moment, this is not implemented in LLVM; these details are left
to the frontend. Clang/compiler-rt does not include the usual ISR table
or AVR-specific startup routines to initialize SRAM from program memory
- avr-clang-compiled executables must currently be linked with
avr-libc/libgcc. Users of the AVR-Rust frontend have implemented these
tables and routines in assembly in each source repository, or more
commonly just linked against avr-libc and GCC's AVR CRT libraries.
Ideally, we would provide our own implementation of these independent of
the GCC/GNU ecosystem in somewhere like compiler-rt.

If I interpret you correct, it should be sufficient to use avr-ld/avr-libc?

I just compiled clang with avr-target enabled and all seems to work
well. I did not check the startup-code and the placement / values of the
ISR vector-table, but this should be ok also, when avr-ld is in use.

How do I specify ISR-functions. The same way as in avr-gcc?

Thanks!

If I interpret you correct, it should be sufficient to use avr-ld/avr-libc?

Yes, this is correct.

I just compiled clang with avr-target enabled and all seems to work
well. I did not check the startup-code and the placement / values of the
ISR vector-table, but this should be ok also, when avr-ld is in use.

How do I specify ISR-functions. The same way as in avr-gcc?

Two steps

  • The C/C++ function needs to be declared with either the calling convention avr-interrupt or avr-non-blocking-interrupt. Skipping this step will cause regular ret instructions to be emitted for return-from-subroutine, instead of the required reti for interrupt handlers. ISRs also have stricter requirements on which registers must not be clobbered after execution, which the backend will handle properly by restoring all clobbered registers in the interrupt handler epilogue
  • The symbol names of the ISR function handlers must match those referred to in avr-libc/avr-libgcc/crt. This is because the ISR table is specified in assembly inside the GCC AVR CRT. The way it works is that the external symbol references in the CRT object files are declared with an exotic linkage type that causes the linker to skip linking of the symbols if they are undefined references. If you chose a custom ISR table in a custom CRT or runtime library, you would be free to choose ISR names as you pleased.
    To see what the ISRs function names are expected to be, either check out the avr-libc #define macros for declaring ISRs, as it exposes the final symbol names for the ISRs it generates, or you can find the symbol names via objdump directly from an AVR CRT object file, for example on my machine, avr-objdump -St /usr/avr/lib/avr5/crtatmega328.o shows this

00000000 w .text 00000000 __vector_1
00000000 w .text 00000000 __vector_2
00000000 w .text 00000000 __vector_3
00000000 w .text 00000000 __vector_4
00000000 w .text 00000000 __vector_5
00000000 w .text 00000000 __vector_6
00000000 w .text 00000000 __vector_7
00000000 w .text 00000000 __vector_8
00000000 w .text 00000000 __vector_9
00000000 w .text 00000000 __vector_10
00000000 w .text 00000000 __vector_11

I suspect the avr-libc ISR macros will have AVR-GCC specific incantations that AVR-LLVM will refuse to link, probably a custom attribute, but YMMV.

Thank you for your explanation. But I suspect I didn't get it right. Can
you please provide an example?

Thanks

Here you go Wilhelm,

https://github.com/dylanmckay/clang-avr-libc-interrupt-example

Hi Dylan,

the following code

volatile uint8_t v1;
volatile uint8_t v2;

__attribute__((interrupt)) void __vector_21(void) {
    v2 = v1;
}

produces in C mode:

00000092 <__vector_21>:
92: 80 91 61 00 lds r24, 0x0061 ; 0x800061 <v1>
96: 80 93 60 00 sts 0x0060, r24 ; 0x800060 <__data_end>
9a: 08 95 ret

and in C++ mode:

00000074 <_Z11__vector_21v>:
74: 80 91 60 00 lds r24, 0x0060 ; 0x800060 <__data_end>
78: 80 93 61 00 sts 0x0061, r24 ; 0x800061 <v2>
7c: 08 95 ret

So, in C++ mode it is not recognized as ISR due to name mangling.

Furthermore there are no register push/pos and no reti.

Whats wrong?

Thanks.

Answering partly to myself there was a extern "C" missing.

But the register pushes ans reti are still missing.

Whats wrong?

Hey Wilhelm,

Could you post the LLVM IR generated from your C++ file?

This can be achieved with ‘clang -S -emit-llvm’

Cheers

Hi Dylan,

I used the following commandline:

clang++ -Os -DF_OSC=20000000 -DF_CPU=20000000 --target=avr -I.
-I../include0 -I../../include0 -I../../../include0 -I../../include0/std
-I../include0/std -I../../../include0/std -I../../3rdparty/boost
-I/usr/avr/include -mmcu=atmega328p
/home/lmeier/Projekte/wmucpp/clang/bm00/bm00.cc -S -emit-llvm --output
bm00.ir

Please find the IR attached in the file bm00.ir

Thanks,
Wilhelm

bm00.ir (2.22 KB)

Hey Wilhelm,

That’s a bug, the “interrupt” attribute is not being recognized by the backend.

I have fixed it in https://github.com/llvm/llvm-project/commit/339b34266c1b54a9b5ff2f83cfb1da9cd8c9d90a

Pull the latest LLVM and it should be fixed.

Hi Dylan,

thank you. I'll be back with a test ...

Wilhelm

Hi Dylan,

looks ok now.

One thing:

the ISR is now:

__vector_21: ; @__vector_21
__vector_21$local:
sei
push r0
push r1
in r0, 63
push r0
clr r0
push r24
lds r24, v1
sts v2, r24
pop r24
pop r0
out 63, r0
pop r1
pop r0
reti

There are unneccessary push/pops of r1 and r0 too, since the clr is
useless ... GCC had the same problem but they made improvements.

Thanks.

Just as a comparison. In gcc we get:

_vector_21:
sei
__gcc_isr 1 ;
lds r24,v1 ; v1.1_1, v1
sts v2,r24 ; v2, v1.1_1
__gcc_isr 2 ;
reti

Should I create an issue in bugzilla for this? Just to be reminded ...

Is there anything I can do about it?

BTW: gcc is loosing the AVR backend, so I would assume, there will be a
greater interest to this in llvm compared to the past.

Thanks,
Wilhelm

Hey Wilhelm,

Raising a bug on bugs.llvm.org sounds good to me.

Is there anything I can do about it?

If you look at the AVR backend and figure out why the unnecessary instructions are emitted, it shouldn’t be too hard to skip them. I suspect the code that generates this lives in ‘llvm/lib/Target/AVR/AVRFrameLowering.cpp’. Look specifically for the ‘emitPrologue’ and ‘emitEpilogue’ functions.

If you can identify some subset of instructions which can be skipped for ISRs, add the appropriate conditionals and pop a review on reviews.llvm.org.

Most of the backend work thus far has been focused on correctness rather than efficiency, and so there is a lot of low hanging fruit laying there.

BTW: gcc is loosing the AVR backend, so I would assume, there will be a
greater interest to this in llvm compared to the past.

The GCC backend died of bitrot so perhaps not, although there does seem to be a bit more activity here. Historically almost all users of the AVR backend were using frontends other than clang, so I suspect it Clang+AVR will get a good stress testing now.

Regards,
Dylan

Hi Dylan,

I can do testing, but working on the backend is not an option for me :frowning:

I am very happy to see that there is an alternative to avr-gcc, but it
looks like at the moment, that there is very few interest in the clang
community for this backend. I think there must be some more activity
here, if this backend leaves (hast left) the experimental status.

Thank you!
Wilhelm