Skip to content

Hello World on RTEMS for GR712RC

A complete walkthrough: write a minimal RTEMS app, cross-compile it with rcc (sparc-gaisler-rtems5-gcc), wrap it into a PROM image with mkprom2, and boot it on Lince — both via the hello-gr712rc example and via the lince-emu CLI.

flowchart LR
    A[main.c] -->|sparc-gaisler-rtems5-gcc| B[hello.elf]
    B -->|mkprom2| C[hello.rom]
    C -->|prom_image_path / --image| D[lince-emu / hello-gr712rc]
    D -->|APBUART0 → stdout| E["Hello world!"]

Already have RTEMS images?

If you only want to run a pre-built image, skip to step 4 — or use the bundled tests/guest-programs/rtems/hello-world/hello-world.elf with lince-emu --image (see First RTEMS boot). You need RCC + mkprom2 only to build the guest yourself.

A minimal RTEMS app needs two pieces: source code compiled with rcc (sparc-gaisler-rtems5-gcc), then a PROM image produced with mkprom2.

1. Source (main.c)

#include <rtems.h>
#include <rtems/bspIo.h>

rtems_task Init(rtems_task_argument arg) {
  while (1) {
    printk("Hello world!\n");
    rtems_task_wake_after(rtems_clock_get_ticks_per_second());
  }
  rtems_task_exit();
}

#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER
#define CONFIGURE_MAXIMUM_TASKS 4
#define CONFIGURE_RTEMS_INIT_TASKS_TABLE
#define CONFIGURE_INIT
#include <rtems/confdefs.h>

The confdefs.h macros declare what the kernel must include before linking; Init is the entry point RTEMS calls after boot.

2. Compilation with rcc

rcc is Gaisler's RTEMS Cross-Compiler — sparc-gaisler-rtems5-gcc — a gcc patched for SPARC LEON.

sparc-gaisler-rtems5-gcc \
  -qbsp=gr712rc -mcpu=leon3 -mfix-gr712rc -g -O2 \
  -o hello.elf main.c

Flag breakdown:

  • -qbsp=gr712rc — selects the GR712RC Board Support Package (links the right BSP, startup, and linker script; default load address 0x40000000 in RAM).
  • -mcpu=leon3 — emit LEON3 SPARC V8 instructions (FPU, CASA, etc.).
  • -mfix-gr712rc — apply errata workarounds specific to the GR712RC silicon (compiler avoids known buggy instruction patterns).
  • -g — DWARF debug info (for gdb).
  • -O2 — standard optimization; safe with the errata fixes above.

The same flags are passed at link time, since the BSP selection drives the linker script and startup objects.

3. PROM image with mkprom2

mkprom2 wraps the ELF with a small bootstrap that initializes the memory controller, copies .text/.data into RAM, and jumps to _start.

mkprom2 -ccprefix sparc-gaisler-rtems5 \
        -leon3 -freq 80 -baud 38400 \
        -nocomp -nomsg \
        hello.elf -o hello.rom

Flag breakdown:

  • -ccprefix sparc-gaisler-rtems5 — tells mkprom2 which cross-toolchain to invoke when assembling its bootstrap stub (it calls <prefix>-gcc, <prefix>-objcopy, etc.).
  • -leon3 — generate a LEON3-compatible bootstrap (memory controller init, trap table, register window setup match LEON3).
  • -freq 80 — system clock in MHz; used to program SDRAM timing and the UART divisor. GR712RC dev board typically runs at 80 MHz.
  • -baud 38400 — boot-message UART baudrate, derived from -freq.
  • -nocomp — do not LZSS-compress the payload (faster boot, larger image).
  • -nomsg — suppress the bootstrap's startup banner over UART.

4. Output

The resulting hello.rom is the bootable PROM image. Once running on the board, Hello world! is printed on UART0 once per second.

5. SMP boot on Lince

GR712RC silicon is dual-core, but only CPU 0 starts executing at reset. CPU 1 stays halted in a power-down state until software writes IRQMP.MPSTAT.MPS[1] = 1. The mkprom2 bootstrap (and the RTEMS BSP startup that follows) runs entirely on CPU 0; CPU 1 is only released later, by the RTEMS SMP scheduler — or never, if the BSP is uniprocessor.

The Lince emulator mirrors this reset model in both the PROM path (prom_image_path) and the ELF path (load_elf): on initialize(), all cores are reset to cfg.reset_pc, and then CPU 1..N are explicitly set to power-down. Running the example below boots cleanly with cores = 2 even though hello.rom is a uniprocessor app:

./build/examples/hello-gr712rc/hello-gr712rc hello.rom
# [INFO] [emulator] Initialized with 2 core(s), 67108864 bytes RAM at 0x40000000
# [apbuart0] Hello world!
# [apbuart0] Hello world!
# ...

If both cores entered the mkprom2 bootstrap from PC = 0 in parallel they would collide on shared state, one would double-trap, and the emulator would report HaltedMode after a single quantum (~1280 instructions). The parking step prevents that. The N=2 case is covered by the hello-lince: PROM-boot RTEMS app prints via APBUart integration test (sections uniprocessor (N=1) and SMP (N=2, GR712RC default)).

6. Boot it with the lince-emu CLI

The example program is one way to boot; the standalone CLI is another. --image accepts the mkprom2 .rom directly (its PT_LOAD segments are flattened into PROM by initialize()):

./build/src/app/lince-emu --image hello.rom            # GR712RC, realtime pacing
./build/src/app/lince-emu --turbo --image hello.rom    # as fast as the host can

UART0 output (Hello world!) appears on stdout once per simulated second; in --turbo mode it appears as fast as the guest produces it. See the CLI reference for every flag.

mkprom2 -freq must match the emulator clock

A mkprom2 image bakes the -freq value (here 80) into its bootloader, which programs the GPTIMER scaler for that frequency. The GR712RC recipe runs at 80 MHz, so -freq 80 matches. If you change the emulator clock (--mhz / cfg.cpu_clock_hz), rebuild the .rom with a matching -freq. Direct-ELF guests have no baked frequency — initialize() re-derives the scaler — so they need no rebuild. See Clock frequency.

Next steps