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:
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).
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).
# 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¶
- Configuration — every
EmulatorConfigfield. - Custom components — authoring component
libraries (
.so). - CLI reference — all
tero-emuflags. - tero_compose module reference — internals:
Machine::build()stages, kits, PnP derivation.