Skip to content

First RTEMS boot

This walkthrough takes you from zero to a real RTEMS hello-world running on the emulator, using the RCC 1.3.2 SPARC cross-toolchain. By the end you will have run an RTEMS image, read its UART console output, and recognised what a clean RTEMS boot looks like.

flowchart LR
    A[RTEMS source<br/>main.c] -->|sparc-gaisler-rtems5-gcc| B[hello.elf<br/>SPARC BE ET_EXEC]
    B -->|optional: mkprom2| C[hello.rom<br/>PROM image]
    B -->|--image .elf| D[tero-emu]
    C -->|--image .rom| D
    D -->|APBUART0 → stdout| E[Console output]

Prerequisites

  • A working build of Tero (see Installation).
  • The RCC 1.3.2 toolchain installed under /opt/rcc-1.3.2-gcc/ (the path the repo's build scripts assume). RCC 1.3.2 ships RTEMS 5.x.
ls /opt/rcc-1.3.2-gcc/bin/sparc-gaisler-rtems5-gcc   # should exist

No cross-toolchain? Use the pre-built image

The repository ships a pre-built hello-world.elf at tests/guest-programs/rtems/hello-world/hello-world.elf. You can run that directly without RCC; you only need the toolchain to rebuild it or to compile your own guests.

1. Build or obtain the hello-world image

The repository includes the pre-built binary above. To rebuild from source (requires sparc-gaisler-rtems5-gcc on PATH):

# From the repository root:
cmake --build build --target sparc_test_binaries
# The cross-compiled ELFs end up under the build directory.

You can also supply your own RCC toolchain and link script to produce a compatible ELF — just place it at tests/guest-programs/rtems/hello-world/hello-world.elf for the integration tests to find it. For a full from-scratch walkthrough (source, compile flags, and the optional mkprom2 PROM image) see the Hello World guide.

The RTEMS BSP leon3 is configured without MMU (default for the testsuite goal) and without networking. The image links a single user task that prints a banner and exits cleanly.

ELF vs. PROM image

tero-emu --image accepts either a plain SPARC ELF (loaded into RAM at its segment addresses, like a debugger) or an mkprom2 PROM image (the bootloader-wrapped .rom that mirrors how real silicon boots from PROM). Emulator::initialize() detects the ELF magic and flattens the PROM image's PT_LOAD segments automatically. New RTEMS guests in this repo follow the PROM path; the bundled hello-world.elf uses the direct-ELF path.

2. Run it

./build/src/app/tero-emu \
    --image tests/guest-programs/rtems/hello-world/hello-world.elf

Expected console output (on stdout):

*** BEGIN OF TEST HELLO WORLD ***
*** TEST VERSION: 5.x.y
*** TEST STATE: EXPECTED-PASS
*** TEST BUILD:
*** TEST TOOLS: 7.5.0 20191114 (RCC 1.3.2 [bcc-2.2.2-gcc])
Hello World

*** END OF TEST HELLO WORLD ***

And a halt summary on stderr:

[tero-emu] halted: reason=DurationExpired instructions=... sim_time_ns=1000000000

What success looks like

Signal Meaning
The banner and Hello World appear on stdout The BSP booted, the console UART is wired, and the user task ran.
reason=DurationExpired (or DeadlineReached) The run ended because the time budget expired — a clean, expected ending.
Exit code 0 Normal completion.
reason=HaltedMode + exit code 5 The guest deliberately stopped (e.g. _exit/_CPU_Fatal_halt issuing ta 0 with traps disabled), or hit an unrecoverable fault. This is the guest's own doing, not an emulator failure; a register post-mortem of core 0 is dumped to stderr.

For test scoring, an RTEMS testsuite program is counted PASS when its captured UART output contains the exact string *** END OF TEST.

Why the CLI exits 0 even though RTEMS never calls exit()

In this configuration RTEMS returns to its idle thread, which immediately enters power-down via wr %g0, %asr19. The emulator's idle-time skip then fast-forwards simulated time straight to the deadline. The run ends with DurationExpired and exit code 0.

Pacing

tero-emu runs in real-time pacing mode by default: 1 second of simulated time takes roughly 1 second of wall-clock time, so periodic UART output appears at its scheduled intervals. For a faster boot-and-exit cycle while iterating, pass --turbo to disable pacing:

./build/src/app/tero-emu --turbo \
    --image tests/guest-programs/rtems/hello-world/hello-world.elf

3. Run the sptest integration suite

The repository can build a set of SPARC RTEMS sptests as ELFs (populated locally under tests/guest-programs/rtems/sptests/bin/). The CTest wrapper runs them and checks the captured UART:

ctest --test-dir build -R rtems_sptests --output-on-failure

A sptest is recorded PASS when its captured UART contains the exact string *** END OF TEST.

4. Capture the UART programmatically

To integrate Tero into a CI pipeline, replace the default StdoutCharDevice on the console UART with the test-support CapturingCharDevice and assert on its contents:

#include "tero/runtime/emulator.hpp"
#include "tests/support/capturing_char_device.hpp"

auto cap  = std::make_unique<tero::test_support::CapturingCharDevice>();
auto* ref = cap.get();
emu->set_character_device(std::move(cap));   // console UART = UART0
emu->initialize();                            // ← latches the chardev; do it after
emu->load_elf("hello-world.elf");
emu->run_for(tero::SimTimeNs{2'000'000'000ULL});

if (ref->captured().find("*** END OF TEST") != std::string::npos) {
    /* PASS */
}

The order matters: each APBUART latches its ICharacterDevice* during initialize(). Set the chardev before initializing. See UART and console for more capture, routing, and suppression strategies.

What is happening under the hood?

The boot sequence on real GR712RC and on Tero is the same:

  1. _start runs from the ELF entry point (typically 0x40000000), or from the mkprom2 bootloader in PROM at address 0.
  2. The RTEMS bootstrap probes the MemCtrl (FTMCTRL) for memory layout, programs the GPTimer prescaler for the system tick, and unmasks IRQs at the IRQMP.
  3. The first user task is created and scheduled.
  4. printf/printk writes through the APBUART data register; Tero's ApbUart peripheral forwards every byte to the injected ICharacterDevice.
  5. The task ends, the idle thread halts each CPU via wr %g0, %asr19, and the emulator skips simulated time to the next pending event.

For the deep dive on each of these, see Execution model, Traps and interrupts, and the peripheral reference.

Next: debug the guest with GDB

Once hello-world.elf boots, the natural next step is to attach a debugger. Tero embeds a GDB Remote Serial Protocol stub:

# Terminal A — start the emulator with the stub listening
./build/src/app/tero-emu \
    --image tests/guest-programs/rtems/hello-world/hello-world.elf \
    --gdb-port 1234

# Terminal B — attach (point GDB at the ELF for symbols)
sparc-gaisler-rtems5-gdb tests/guest-programs/rtems/hello-world/hello-world.elf
(gdb) target remote :1234
(gdb) info threads          # RTEMS-aware: shows task name / state / priority
(gdb) break Init
(gdb) c

GDB can connect at any time after the emulator starts (late-binding); no --gdb-wait is needed unless you want to break on the very first instruction. Full reference: Debugging with GDB.