User-mode emulation for TriCore with TSIM and the GNU toolchain
QEMU User-mode Emulation is a practical solution for testing algorithms directly without having to set up a microcontroller system and boot it every time you test. Unfortunately, QEMU only supports full system emulation of Aurix microcontrollers with TriCore instruction set. User-mode emulation is not available for TriCore.
Since Infineon’s Tricore instruction set simulator TSIM has a virtual I/O feature that can be used for user-mode emulation.
Compile for Tricore and TSIM
Download and install the Tricore GNU toolchain (including GCC, binutils, GDB and newlib) with
git clone --recursive https://github.com/NoMore201/tricore-gcc-toolchain
cd tricore-gcc-toolchain
mkdir build
cd build
../configure --prefix=~/.local/share/tricore-gcc
make -j$(nproc) stamps/build-gcc-stage
or directly use the less experimental upstream repository from EEESlab
git clone --recursive git@github.com:EEESlab/tricore-gcc-toolchain-11.3.0.git
./build-toolchain --all
Compile foo.c to foo.elf with
tricore-elf-gcc -mcpu=tc39xx -g -nostartfiles -T tsim.ld tsim_startup.c \
-o foo.elf" foo.c
Two additional files cannot be avoided: the internal linker script of gcc does not define memory regions and the internal startup code does access an invalid memory mapped register which causes an exception.
Minimal Linker Script
The linker script starts with the declaration of the target architecture:
OUTPUT_FORMAT("elf32-tricore")
OUTPUT_ARCH(tricore)
TSIM comes with config scripts for the Aurix TC39xx family of microcontrollers. We use two memory regions: 240 KiByte data scratchpad RAM at 0x70000000 and 4MiByte program flash ROM at 0x80000000:
MEMORY {
data_scratchpad_ram (w!xp): org = 0x70000000, len = 240k
program_flash_rom (rx!p): org = 0x80000000, len = 4M
}
Most sections are automatically assigned by the linker. Only the
.heap section must be defined, because newlib expects the
symbols __HEAP and __HEAP_END to point at the
start and the end of the heap memory area.
HEAP_SIZE = 64k;
SECTIONS {
.heap : FLAGS(aw) {
. = ALIGN(4);
__HEAP = .;
. += HEAP_SIZE;
__HEAP_END = .;
} > data_scratchpad_ram
}
Complete file: tsim.ld
Minimal Startup Code
Program entry is at the _start function. At this point
the stack is not initialised yet. Therefore the function is of pure
assembly and only sets the stack pointer a10 to 0x7003A000 and jumps to
the C code in __startup.
void _start( void ) __attribute__((used,noinline)) ;
void _start(void)
{
asm("movh.a %a10, hi:(0x7003A000) \n\t" \
"lea %a10, [%a10]lo:(0x7003A000) \n\t" \
"dsync \n\t" \
"movh.a %a15, hi:(__startup) \n\t" \
"lea %a15, [%a15]lo:(__startup) \n\t" \
"ji %a15");
}
Main task of __startup is to create the singly linked
list of CSAs. Each CSA is 64 bytes long and the first 4 byte word points
to the next entry. Core register FCX points to the first free entry in
the CSA list. LCX points to one of the last entries, but not the last.
When FCX reaches LCX a trap is taken and there should be some CSA
remaining for the execution of the trap. The pointers in FCX, LCX and
the next field are not direct addresses, but compressed ones that can
only address the first 4 MiByte of each segment.
void __startup() __attribute__((used,noinline,noreturn)) ;
void __startup()
{
/* Setup the context save area linked list. */
unsigned int csa_addr = 0x7003A000;
unsigned int csa_end = 0x7003C000;
unsigned int next_pcxi = ((csa_addr & 0xF0000000) >> 12) | /* segment */
((csa_addr & 0X003FFFC0) >> 6); /* offset */
_mtcr(0xFE38/*FCX*/, next_pcxi); /* store 1st PCXI value in FCX */
while (csa_addr < csa_end) {
next_pcxi++;
*(unsigned int *)csa_addr = next_pcxi;
csa_addr += 64;
}
*(unsigned int *)(csa_end - 64) = 0; /* mark end of CSA list */
_mtcr(0xFE3C/*LCX*/, next_pcxi - 3);
_dsync();
exit(main());
}
Complete file: tsim_startup.c
Run with TSIM
After registration, TSIM can be downloaded from the Infineon website. Install with
sudo apt install ./tsim_1.18.196_linux_x64.deb
TSIM will be installed to a fixed path under
/opt/Tools/.... Emulate foo.elf with
tsim_path=/opt/Tools/TSIM-Tricore-instruction-set-simulator/1.18.196
${tsim_path}/bin/tsim16p_e -e -h -s -H -z \
-MConfig ${tsim_path}/config/tc162/tc39xx/MConfig \
-OConfig ${tsim_path}/config/tc162/tc39xx/OConfig \
-o foo.elf -trace-instr-file foo.tsim
Appendix: TSIM Virtual I/O Interface
A system call to the virtual I/O is done by the debug
instruction. The parameters are passed in registers according to the
Tricore calling convention. The syscall number is passed in register
D12. After the syscall, D11 is set to the
return value and D12 to errno if an error
occured. The following syscall numbers are supported:
| no | function |
|---|---|
| 1 | int open(char *filename, int flags, int mode) |
| 2 | close(int fd) |
| 3 | int lseek(int fd, int offset, int whence) |
| 4 | int read(int fd, void *buf, int len) |
| 5 | int write(int fd, void *buf, int len) |
| 6 | int creat(char *filename, int mode) |
| 7 | int unlink(char *filename) |
| 8 | int stat(char *filename, void *statbuf) |
| 9 | int fstat(int fd, void *statbuf) |
| 10 | gettimeofday() ? |
| 11 | int ftruncate(int fd, int len) |
| 13 | int rename(char *oldname, char *newname) |
2025-11-13