Skip to content

CLI reference

tero-emu is the standalone front-end built from the tero_app target (src/app/src/main.cpp). It is a thin shell over tero::runtime::Emulator: it parses arguments, builds an EmulatorConfig from a SoC recipe, wires a StdoutLogger and a StdoutCharDevice (on the console UART) as defaults, loads the image, and runs.

Run tero-emu --help for the built-in usage summary, or --version to print the build version.

Synopsis

tero-emu --image <path> | --prom <path>
          [--bin <path>@<addr>]...
          [--soc gr712rc|gr740]
          [--ram <MiB>] [--cores <N>]
          [--mhz <N>] [--cpi <value>]
          [--budget <ns>] [--histogram-out <path>]
          [--turbo] [--mt]
          [--gdb-port <port>] [--gdb-wait]
          [--quantum <N>]
          [--verbose]
          [--version] [--help|-h]
          [diagnostic flags — see below]

If you invoke tero-emu with neither --image nor --prom, it prints a hint and exits 0 — nothing is run. One of the two image options is required.

Common options

Option Default Meaning / when to use
--image <path> SPARC big-endian ELF (ET_EXEC) loaded into RAM via load_elf(): segments copied, PC/%sp set to the ELF entry / top of RAM. Required unless --prom boots the guest.
--prom <path> PROM image: raw bytes or a mkprom2 ELF .rom (detected by magic, PT_LOAD segments flattened into the PROM window). With --image omitted the guest boots from PROM at reset PC 0x0 — the silicon boot path. Remember --ram must match the size baked by mkprom2 -ramsize.
--bin <path>@<addr> Raw binary copied verbatim to physical address <addr> (hex 0x.. or decimal) during initialize(). Repeatable; later images overwrite overlapping earlier ones. Targets must be writable mapped memory (RAM, FTAHBRAM, flash-bank Ram entities) — the PROM window is read-only, use --prom for it. Never touches PC.
--soc <name> gr712rc SoC recipe: gr712rc (dual-core LEON3FT) or gr740 (quad-core LEON4FT). Selects the peripheral map, IRQ assignments, RAM base, default clock, and default core count.
--ram <MiB> 16 RAM size in MiB. Ignored when --soc gr740 (the GR740 recipe fixes 256 MiB at 0x00000000). Must be ≥ 1.
--cores <N> 1 Number of LEON cores, 1..4. Ignored when --soc gr740 (that recipe is quad-core). On GR712RC use 1 or 2.
--mhz <N> recipe Override the CPU/system clock in MHz, 1..1000. Default keeps the recipe clock (GR712RC 80 MHz, GR740 250 MHz). Direct-ELF guests need no rebuild — initialize() re-derives the GPTIMER scaler so the RTEMS timer tick stays exact at any clock.
--cpi <value> 1.0 Cycles per instruction (must be > 0). 1.0 = one cycle per instruction (TEMU-style). Raise it to make the core proportionally slower in simulated time; set below 1 to express an IPC target (cpi = 1/ipc). Only CPU sim-time scales — peripheral/timer clocks stay on the bus clock.
--budget <ns> 1_000_000_000 Simulated-time budget in nanoseconds (default 1 s). The run stops when current_sim_time() advances by this amount, on HaltedMode, or on a breakpoint.
--histogram-out <path> stderr Destination for the opcode-histogram CSV. Only a build configured with -DTERO_OPCODE_HISTOGRAM=ON produces one; a normal build emits nothing either way.
--turbo off Disable wall-clock pacing (PacingMode::Turbo). By default tero-emu runs PacingMode::Realtime (1 s simulated ≈ 1 s real); --turbo runs as fast as the host allows. Use for CI, benchmarks, batch runs.
--mt, --multithread off MultiThread (ADR-001): run each simulated core on its own host thread — a faithful true-concurrency SMP model. SMP guests that livelock under the cooperative SingleThread round-robin run correctly here. No effect with one core.
--gdb-port <p> 0 (off) If non-zero, bind the GDB remote stub to 127.0.0.1:p during initialize(). GDB can attach at any time thereafter (late-binding); the run loop polls the listener each quantum. 0 disables the stub (one null-pointer test on the hot path). See Debugging with GDB.
--gdb-wait off With --gdb-port, block in initialize() until a GDB client connects. Use when you need to inspect the very first instruction; otherwise late-binding is enough.
--quantum <N> recipe Instructions per core per scheduling round (≥ 1). A scheduling-granularity knob — smaller is finer cross-core interleaving at higher overhead. Mainly diagnostic; leave at the recipe default unless tuning SMP behaviour.
--verbose off Switch the logger to Debug level (output on stderr). Narrates config validation, peripheral wiring, ELF loading, and idle-skip events.
--version Print the emulator version and exit.
--help, -h Print the usage summary and exit.

GR740 ignores --ram and --cores

--ram and --cores only apply to --soc gr712rc. When you pass --soc gr740 the recipe pins 4 cores and 256 MiB RAM at 0x00000000; the two flags are silently ignored. To change those on GR740, embed Tero as a library and edit the config after gr740_config().

Diagnostic options

These exist for emulator development and bug-isolation (JIT-vs-interpreter divergence, SMP ordering, instruction tracing). They are stable and safe to use, but most users never need them.

Option Effect
--no-translation Force the Switch interpreter (translation = false) — the reference/oracle path, one instruction at a time. Slowest but the canonical correctness baseline.
--ir-interp-only Translation on, but never JIT-compile: every block runs on the IR interpreter (jit_baseline_threshold = ∞, background opt off). Isolates IR-frontend/interpreter semantics from LLVM codegen.
--no-jit-opt Baseline-only JIT: disable the background O2/optimising tier (jit_background_opt = false). Lowest, most deterministic compile latency; lower steady-state throughput.
--jit-region-blocks <N> Cap basic-block fusion per JIT region (jit_max_region_blocks, ≥ 1). 1 disables chaining beyond the self-loop.
--trace Emit a per-instruction cpu <N> <pc> trace to stderr (guest UART stays on stdout). Installing any observer forces the deterministic Switch path, so the trace is reproducible. Used for Tero-vs-SIS lockstep.
--lockstep Run two full N-core emulators on the same image — one Switch (oracle), one IR interpreter — one guest instruction at a time, comparing every core's whole register blob after each step. Prints the first divergence (core, register, recent-PC trace) and exits.
--oracle-lockstep Drive the production SMP round-robin under the IR engine and validate every clean-exit IR block against core::step on a scratch copy. Reports the first block-level divergence.

--lockstep and --oracle-lockstep are mutually-exclusive run modes that take over main; they force PacingMode::Turbo and ignore the GDB, pacing, and observer paths.

Flag-to-config mapping

Every common flag maps onto an EmulatorConfig field (or an Emulator::set_* call). This is exactly what main.cpp does:

CLI flag EmulatorConfig field / call Notes
--soc chooses gr712rc_config() / gr740_config() The whole recipe, not one field.
--ram ram_size (× 1 MiB) GR712RC only.
--cores num_cores GR712RC only.
--mhz cpu_clock_hz (× 1 MHz) Recomputes ns_per_insn.
--cpi cpi Recomputes ns_per_insn.
--budget argument to run_for(SimTimeNs{...}) Not stored in the config.
--turbo pacing = Turbo (else Realtime)
--mt execution_mode = MultiThread
--gdb-port gdb_stub_port
--gdb-wait gdb_stub_wait_for_client
--quantum quantum
--no-jit-opt jit_background_opt = false
--no-translation translation = false
--ir-interp-only jit_baseline_threshold = ∞, jit_background_opt = false
--jit-region-blocks jit_max_region_blocks
--verbose set_logger(StdoutLogger{Debug}) else Info.
--trace set_observer(TraceObserver) Forces the Switch path.

Fields the CLI does not expose (ram_base, pacing_slice_ns, entry_point_override, prom_*, jit_promotion_threshold, quantum_batch, the peripherals/character_devices vectors) are left at their recipe/struct defaults. To set them, embed Tero as a library or extend main.cpp. See Configuration for every field.

Exit codes

Code Meaning
0 Time budget consumed cleanly (DurationExpired / DeadlineReached), or --help/--version/no-image hint.
2 Invalid / missing CLI arguments (bad --ram, --cores out of range, unknown flag, missing value).
3 Configuration validation failed (Emulator::create) or initialize() failed.
4 ELF load failure (not SPARC, malformed, IO error).
5 A core entered HaltedMode — a trap fired with PSR.ET = 0, which on SPARC stops the processor. This is usually the guest's own deliberate shutdown (ta 0 / _exit) or an unrecoverable fault. A core-0 post-mortem is dumped to stderr.

HaltedMode is not an emulator bug

Exit 5 means the guest stopped the processor (RTEMS calls _CPU_Fatal_halt, or hits an unrecoverable trap with traps disabled). Emulation behaved correctly and simply has nothing more to run. The distinct ErrorMode reason is reserved for genuine internal emulator errors and is not normally observed.

The halt summary

On every run the CLI prints a one-line summary to stderr when the run loop returns:

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

reason is one of DurationExpired, DeadlineReached, HaltedMode, ErrorMode, Breakpoint.

HaltedMode post-mortem

When tero-emu exits with code 5 it dumps a complete snapshot of core 0 — equivalent to attaching a SPARC debugger to a halted CPU:

[tero-emu] core0 post-mortem: pc=0x400034a0 npc=0x400034a4
            tbr=0x40000020 tt=0x02 psr=0x00000083 wim=0x00000002
[tero-emu]   g0-g7: 0 0x00000000 0x00000000 0x00000000 ...
[tero-emu]    i0-i7: 0x400fff40 0x00000008 ...
[tero-emu]    l0-l7: 0x00000000 ...
[tero-emu]    o0-o7: 0x400fff40 0x00000008 ...

tt = 0x02 is illegal_instruction — typical when a program jumps to unrelocated memory. tbr tells you exactly which trap handler the CPU was about to enter. SPARC V8 stores the faulting PC/nPC in %l1/%l2 of the trap-handler window, so the locals expose the offending instruction even before the handler runs. The full trap-type table is in Traps and interrupts.

Logging levels

The CLI's StdoutLogger accepts four levels; output goes to stderr so it never mixes with guest UART output on stdout.

Level Shown when Typical contents
Debug --verbose Per-peripheral MMIO detail, ELF segment loading, idle-skip events, JIT/IR notes.
Info always Lifecycle markers (initialize, load_elf, run begin/end), peripheral wiring summary.
Warn always Configuration adjustments and recoverable oddities.
Error always Bus / load / config failures.

Info and above are always shown; Debug is gated on --verbose.

Notes on argument handling

  • Arguments are parsed by hand (a simple loop in main.cpp), not getopt. Flags can appear in any order; each value-taking flag expects its value as the next argument (--ram 64, not --ram=64).
  • --ram is in MiB; it is converted to bytes (MiB × 1024 × 1024) with no further rounding.
  • --budget is decimal nanoseconds; e.g. --budget 5000000000 is "5 simulated seconds".
  • A malformed value (non-numeric --ram, --cores outside 1..4, --cpi ≤ 0, --mhz outside 1..1000, etc.) prints an error and exits 2.
  • An unknown flag exits 2 after printing the usage summary.

Pacing: realtime vs turbo

tero-emu runs PacingMode::Realtime by default. The Emulator slices each run into pacing_slice_ns chunks (default 10 ms simulated) and std::this_thread::sleep_untils on std::chrono::steady_clock between chunks, so a UART message scheduled to print every simulated second appears on stdout once per real second.

The simulated clock rate is set by cpu_clock_hz and cpi (which derive ns_per_insn). Every executed instruction advances simulated time by ns_per_insn, so a faster simulated clock means the host must execute more instructions per real second to keep up. If the host is too slow the simulation falls behind silently — sleep_until cannot make it faster.

--turbo flips to PacingMode::Turbo (free-running). See Pacing for the full model.

Worked invocations

# Boot an RTEMS ELF on the default GR712RC, realtime pacing:
tero-emu --image hello-world.elf

# Same, but fast as possible, 2 cores:
tero-emu --turbo --cores 2 --image hello-world.elf

# GR740 quad-core (RAM/cores fixed by the recipe), 2-second budget:
tero-emu --soc gr740 --budget 2000000000 --image gr740_app.elf

# Model 100 MHz with a 2× CPI penalty (half the effective MIPS):
tero-emu --mhz 100 --cpi 2.0 --image bench.elf

# Boot from PROM (mkprom2 .rom) — the silicon path; --ram matches -ramsize:
tero-emu --prom hello-prom.rom --ram 64

# Flight-style setup: PROM bootloader + raw FSW images in flash banks:
tero-emu --prom bootloader.rom --bin fsw-a.bin@0x01000000 --bin fsw-b.bin@0x01800000

# Debug a PROM image: stub on :1234, late-binding attach:
tero-emu --prom hello-prom.rom --ram 64 --gdb-port 1234

# True-concurrency SMP for a guest that livelocks cooperatively:
tero-emu --soc gr740 --mt --turbo --image smp_app.elf