tero_app¶
The standalone CLI executable, tero-emu (OUTPUT_NAME "tero-emu"). A
thin shell over tero::runtime::Emulator that parses arguments, builds an
EmulatorConfig from a SoC recipe, injects the default host services,
loads an ELF, and runs.
# src/app/CMakeLists.txt
add_executable(tero_app src/main.cpp)
set_target_properties(tero_app PROPERTIES OUTPUT_NAME "tero-emu")
target_link_libraries(tero_app
PRIVATE tero::runtime tero::defaults tero::warnings fmt::fmt)
Responsibility
Wire a CLI invocation to a runnable Emulator: recipe → config →
inject defaults → initialize → load → run → report. It is the only
place in the tree where direct host I/O (stdout/stderr) is allowed.
Source layout¶
src/app/
└── src/main.cpp ← single translation unit (~670 LOC incl. the
diagnostic lockstep/oracle harnesses)
Output discipline: guest UART text goes to stdout; emulator status,
the post-mortem dump, and the --trace stream go to stderr (so guest
output stays cleanly separable). The only logging the app itself emits is
through StdoutLogger, which it injects.
CLI flags¶
Parsed by a hand-rolled parse_cli (main.cpp:123) — a simple --flag
value scan over argv, not getopt_long.
| Flag | Effect | Maps to |
|---|---|---|
--image <path> |
ELF image to load (SPARC BE, ET_EXEC) | load_elf |
--soc <gr712rc\|gr740> |
SoC recipe (default gr712rc) |
gr712rc_config() / gr740_config() |
--ram <MiB> |
RAM size (default 16; ignored for gr740) | cfg.ram_size |
--cores <1..4> |
core count (default 1; ignored for gr740) | cfg.num_cores |
--mhz <1..1000> |
override CPU frequency (default: recipe clock) | cfg.cpu_clock_hz |
--cpi <value> |
cycles per instruction (default 1.0; <1 raises IPC) |
cfg.cpi |
--budget <ns> |
max simulated ns (default 1e9 = 1 s) | run_for argument |
--gdb-port <N> |
bind GDB stub to TCP port N (0 = off) | cfg.gdb_stub_port |
--gdb-wait |
block at startup until GDB connects | cfg.gdb_stub_wait_for_client |
--turbo |
free-run (skip wall-clock pacing) | cfg.pacing = Turbo |
--mt / --multithread |
thread-per-core (ADR-001) | cfg.execution_mode = MultiThread |
--verbose |
debug-level logging | StdoutLogger(LogLevel::Debug) |
--version / --help / -h |
print and exit | — |
Diagnostic flags (developer use): --no-jit-opt (Baseline-only JIT),
--no-translation (force the Switch interpreter / oracle),
--ir-interp-only (translation on, never JIT-compile), --trace
(per-instruction cpu pc trace to stderr — installs an observer, which
forces the Switch path), --quantum <N>, --jit-region-blocks <N>,
--lockstep (IR-vs-Switch multi-core divergence diff), --oracle-lockstep
(per-block IR-vs-core::step oracle).
See the CLI reference for the full guide.
What main() does¶
- Parse arguments into
CliOptions(main.cpp:65).--version/--helpreturn early; a parse error returns exit 2. No--imageprints a hint and returns 0. - Branch to the diagnostic harnesses if
--lockstep/--oracle-lockstepwere given (run_lockstep/run_oracle). - Build the config from the SoC recipe, then overlay CLI overrides
(
cores/ram/mhz/cpi/pacing/execution_mode/JIT knobs/GDB port).ns_per_insnis re-derived byEmulator::createfromcpu_clock_hzandcpi. - Create the emulator:
Emulator::create(cfg). Failure → exit 3. - Inject defaults:
- Initialize (
emu.initialize()— wires RAM, PROM, peripherals, PnP, bus routing, the JIT). Failure → exit 3. - Load the ELF (
emu.load_elf(opts.image)). Failure → exit 4. - Run:
emu.run_for(SimTimeNs{opts.budget_ns}). - GDB stop/resume loop: while the run halted on a
Breakpointand a client is connected, hand control tostub->process_until_resume(); onContinue/Stepre-enterrun_forwith the remaining budget; exit the loop onDetach/Kill. - Report and exit (see below).
Exit codes and the post-mortem¶
The app prints a one-line halt summary (reason,
instructions_executed, sim_time_ns) to stderr, then:
| Halt reason | Action | Exit |
|---|---|---|
DurationExpired / DeadlineReached |
normal end | 0 |
Breakpoint (no/disconnected stub) |
falls through | 0 |
HaltedMode |
dump core 0 post-mortem | 5 |
The post-mortem (main.cpp:639) dumps, all to stderr:
pc,npc,tbr,tt(=(tbr >> 4) & 0xFF),psr,wim;%g0..%g7;- the active window's
%i0..%i7,%l0..%l7,%o0..%o7.
The locals are printed because SPARC V8 stores the faulting PC/nPC in
%l1/%l2 of the trap-handler window, so they expose the offending
instruction even before the handler runs.
HaltedMode, not a crash
Exit 5 with HaltedMode means a guest core took a trap with ET=0 —
typically RTEMS' deliberate _CPU_Fatal_halt / _exit (ta 0). The
emulator behaved correctly; see RunResult.
Why such a minimal CLI¶
- The library is the product. The CLI exists so a newcomer can run
tero-emu --image hello.elfin one shot, but the canonical integration is viatero_runtime. - It is the canonical example of injecting defaults. New embedders
learn by reading
main.cpp. - An SMP2 wrapper has no use for it. Anything CLI-flag-shaped becomes an SMP2 model property.
Extending the CLI¶
- Fork
main.cpp. Copy it, add your flags, linktero_runtime, ship a different binary. - Add a field to
EmulatorConfig. If the knob is general enough to belong on the public API, add it there and expose a flag.
The CLI will not grow into a configuration framework — anything sufficiently complex is a library consumer's job.
See also¶
- Runtime — the
EmulatorAPI andEmulatorConfigthe CLI drives. - Defaults — the services the CLI injects.
- CLI reference — the full flag guide.
- Debugging with GDB — the
--gdb-*flow.