Skip to content

Assembling a machine

An Emulator is created from one EmulatorConfig struct. There are four ways to produce that struct, all ending at the same Emulator::create(cfg) call.

Approach For Recompile needed?
Kit as-is Running guests on the stock GR712RC / GR740 No (CLI) / once (library)
Kit + modification Stock silicon with a tweaked field or peripheral list Yes (your program)
.tero script Custom boards from the CLI, no C++ No
Hand-written config Full programmatic control, test harnesses Yes (your program)

1. Use a kit as-is

A kit is a full silicon board (gr712rc_config() / gr740_config() in tero::compose, src/compose/src/kits.cpp) returned as a ready-made EmulatorConfig: peripheral set, IRQ map, clock, RAM/PROM layout, AMBA Plug&Play table.

#include "tero/compose/kits.hpp"
#include "tero/runtime/emulator.hpp"

auto cfg = tero::compose::gr712rc_config();   // or gr740_config()
auto emu = tero::runtime::Emulator::create(cfg);

From the CLI the kit is selected by --soc:

tero-emu --soc gr712rc --image hello-world.elf

2. Modify a kit

The kit returns plain data — inspect or edit any field before Emulator::create:

auto cfg = tero::compose::gr712rc_config();
cfg.num_cores = 1;
cfg.pacing    = tero::runtime::PacingMode::Turbo;
// Drop a peripheral the run does not need:
std::erase_if(cfg.peripherals,
              [](const auto& s) { return s.instance_name == "grspw5"; });
auto emu = tero::runtime::Emulator::create(cfg);

To extend the board (new devices with correct PnP placement), start from the machine-returning form instead — gr712rc_machine(registry) returns the kit as a composable Machine you can create/map/ connect onto before build() (src/compose/include/tero/compose/kits.hpp).

The field reference is in Configuration.

3. Describe the board in a .tero script

tero-emu --machine board.tero composes the board from a text script instead of a kit. The script is authoritative for cores, clock, memory, and peripherals; --soc, --ram, --cores, --mhz, and --cpi are ignored (src/app/src/main.cpp:649-651).

tero-emu --machine examples/machines/gr712rc-min.tero \
          --image hello-world.elf

Grammar

One statement per line; # starts a comment. Six verbs exist (src/compose/src/script.cpp:97-167), each mapping onto one Machine call:

Statement Meaning
set <key>=<value> Machine scalar. Accepted keys: clock (Hz, required), cores (overrides the CPU-object count), pacing (turbo or realtime).
create <Type> <name> Instantiate a component. Type names are the registry's (Leon3, Ram, ApbUart, …); names must be unique.
write <name> <key>=<value> Set a property on a created object (irq=8, pnp=0x800FF000, image=boot.rom, …).
map <memspace> <addr> <size> <name> Place an object in the address space. Numbers: decimal, 0x hex, or K/M/G suffix (1024-based).
connect <from> <port> <to> <iface> Wire an edge: console ↔ UART binds the character device; controller ↔ bus joins a comms bus; CPU ↔ memory/IRQ edges document the graph without changing the config.
time_source <obj> <source> Optional; without it the engine's shared clock and scheduler are used.

There are no silicon defaults: a missing clock, an unmapped Ram, or a missing AhbCtrl/ApbCtrl fails the build with InvalidConfig. A syntax error reports line N: <reason>.

Complete example

examples/machines/gr712rc-min.tero — the smallest machine that boots RTEMS 5 leon3:

set clock=80000000        # system clock in Hz (required — no default)
set pacing=turbo

create Leon3       cpu0
create MemorySpace mem0
create AhbCtrl     ahb0       # AHB controller → the PnP scratch root
create ApbCtrl     apb0       # AHB→APB bridge
create Ram         ram0
create ApbUart     apbuart0
create IrqMP       irqmp
create GPTimer     gptimer0
create Console     tty0       # host stdout sink for the UART

write ahb0     pnp=0xFFFFF000
write apb0     pnp=0x800FF000
write apb0     base=0x80000000
write apb0     size=0x00100000
write apbuart0 irq=2
write gptimer0 irq=8

map mem0 0x40000000 0x01000000 ram0        # 16 MiB RAM
map mem0 0x80000100 0x100       apbuart0
map mem0 0x80000200 0x100       irqmp
map mem0 0x80000300 0x100       gptimer0

connect cpu0     memAccess mem0  MemAccessIface
connect apbuart0 serial    tty0  UartAIface
connect cpu0     irqClient irqmp IrqClientIface

The script describes the board only — the image and the run duration stay CLI arguments (--image, --budget). The same parser is callable from C++ via tero::compose::load_machine_script (src/compose/include/tero/compose/script.hpp).

Adding a device from a component library

--component-lib <path.so> (repeatable) loads a shared object that registers extra component types before the script is parsed; the script then instantiates them with a plain create. The flag requires --machine — component types are instantiated by the board script (src/app/src/main.cpp:614-619).

tero-emu --component-lib libscratchpad_component.so \
          --machine board.tero --turbo
# board.tero (excerpt — examples/custom-component-lib/board.tero)
create ScratchPad pad0
write  pad0 magic=0xCAFE0001
map    mem0 0x80000800 0x100 pad0

Authoring the .so (the TERO_COMPONENT_LIBRARY macro, the ABI handshake, the toolchain rules) is covered in Custom components.

4. Write the EmulatorConfig by hand

A bare EmulatorConfig plus your own PeripheralSpec entries gives full programmatic control with no compose layer at all:

#include "tero/runtime/emulator.hpp"
#include "tero/runtime/emulator_config.hpp"

tero::runtime::EmulatorConfig cfg;     // 1 core, 16 MiB RAM, 50 MHz
cfg.peripherals.push_back({
    .instance_name = "mydev0",
    .factory = [](const tero::PeripheralContext&) {
        return std::make_unique<MyDevice>(tero::PhysAddr{0x80000800});
    },
    .irqs = {tero::IrqLine{9}},
});
auto emu = tero::runtime::Emulator::create(cfg);   // validates the spec

Every field, the validation rules, and the PeripheralSpec contract are documented in Configuration; a from-scratch board exercising the whole surface is examples/custom-board/.

See also