Skip to content

lince_runtime

The orchestration layer. Owns the Emulator class, the event scheduler, the ELF loader, the CPU-bus bridge and the GDB stub. This is the only module that knows about all the others.

Source layout

src/runtime/
├── include/lince/runtime/
│   ├── emulator.hpp
│   ├── emulator_config.hpp
│   ├── event_scheduler.hpp
│   ├── run_result.hpp
│   ├── elf_loader.hpp
│   ├── cpu_bus_bridge.hpp
│   ├── gdb_stub.hpp
│   └── version.hpp
└── src/
    ├── emulator.cpp
    ├── event_scheduler.cpp
    ├── elf_loader.cpp
    ├── cpu_bus_bridge.cpp
    └── gdb_stub.cpp

Depends on every other module.

Emulator

The public entry point. Owns:

  • EmulatorConfig (the live copy).
  • A vector of core::CpuState.
  • A bus::SystemBus.
  • A CpuBusBridge adapter (CPU's ICpuBus view of the bus).
  • An EventScheduler.
  • A vector of unique_ptr<IPeripheral> and matching IrqBridges.
  • Pointers to the four built-in peripherals (IrqMP, GPTimer, ApbUart, MemCtrl).
  • A unique_ptr<ILogger>, unique_ptr<IFaultInjector>, unique_ptr<ICharacterDevice>, and optional unique_ptr<GdbStub>.

API summary (full signatures in emulator.hpp):

static Result<unique_ptr<Emulator>> create(EmulatorConfig);
~Emulator();

void set_logger(unique_ptr<ILogger>);
void set_character_device(unique_ptr<ICharacterDevice>);

Result<void> initialize();
Result<void> reset();
RunResult    run_for  (SimTimeNs duration);
RunResult    run_until(SimTimeNs deadline);
SimTimeNs    current_sim_time() const noexcept;

Result<void>     load_elf   (const filesystem::path&);
Result<void>     load_binary(PhysAddr, span<const byte>, PhysAddr);

Result<void>      read_physical (PhysAddr, span<byte>);
Result<void>      write_physical(PhysAddr, span<const byte>);
Result<uint32_t>  read_physical_u32 (PhysAddr);
Result<void>      write_physical_u32(PhysAddr, uint32_t);

Result<void> add_peripheral(unique_ptr<IPeripheral>, IrqLine);
void         schedule_event(SimTimeNs, IEvent*);

const core::CpuState& core(size_t) const;
core::CpuState&       core(size_t);
size_t                num_cores() const noexcept;

GdbStub*              gdb_stub() noexcept;
const EmulatorConfig& config() const noexcept;
bus::SystemBus&       bus() noexcept;

The class is non-copyable and non-movable — own it through a unique_ptr.

EmulatorConfig

A plain struct, fully documented in Configuration. The factory gr712rc_config() returns the canonical SoC defaults.

EventScheduler

A min-heap of (SimTimeNs when, IEvent* ev) pairs. Used by:

  • The runtime itself to schedule periodic peripheral ticks (in addition to the per-quantum tick() call).
  • Custom peripherals via ctx_.scheduler->schedule_event(when, ev).

The scheduler exposes next_event_time() for the idle-skip logic.

CpuBusBridge

Adapts bus::SystemBus (physical, byte-oriented) into ICpuBus (virtual, instruction-aware). Today the adapter is a passthrough — no MMU — but it is the natural injection point for the SRMMU when Phase 7 delivers it. The CPU's step() routes all fetches and loads/stores through this object.

ElfLoader

Parses SPARC big-endian ET_EXEC ELF binaries:

  • Validates EI_CLASS == ELFCLASS32, EI_DATA == ELFDATA2MSB, e_machine == EM_SPARC.
  • Walks PT_LOAD segments, copies file bytes into RAM, zero-fills BSS.
  • Sets core(0).pc() to e_entry (or cfg.entry_point_override if non-zero).
  • Initialises secondary cores into power-down (Decision 28).

Errors map to ErrorCode::ElfLoadError with a descriptive log line.

GDB stub

GdbStub listens on 127.0.0.1:cfg.gdb_stub_port and speaks the GDB Remote Serial Protocol subset required by sparc-rtems-gdb:

Packet Meaning Status
g Read all registers
G Write all registers
m addr,length Read memory
M addr,length:bytes Write memory
c / C sig Continue
s / S sig Single-step
Z0/Z1 + z0/z1 Insert / remove software breakpoint
? Halt reason
vCont? / vCont Multi-thread step/continue partial
qSupported Capability negotiation
D Detach
k Kill ✅ (sets ErrorMode)

Single-stepping is implemented by calling step() once and returning to the stub's stop loop. Software breakpoints are tracked in a per-stub set; the stub patches ta 1 (0x91d02001) at the breakpoint address and restores the original word on hit.

The stub never blocks the host process — it integrates with the emulator via cooperative polling between quanta. When cfg.gdb_stub_wait_for_client = true, initialize() blocks until a client attaches.

RunResult

enum class HaltReason {
    DurationExpired,   // run_for budget reached
    DeadlineReached,   // run_until deadline reached
    ErrorMode,         // a core entered ErrorMode
    Breakpoint,        // GDB stub stopped on a breakpoint
};

struct RunResult {
    HaltReason       reason;
    SimTimeNs        sim_time;             // current_sim_time after the run
    std::uint64_t    instructions_executed;
};

A Breakpoint reason hands control to the GDB stub; a typical CLI loop is while (run_until(deadline).reason == Breakpoint) gdb->stop_loop();.

What is intentionally not in lince_runtime

  • No CLI parsing (that is lince_app).
  • No file format other than ELF (raw images go through load_binary).
  • No virtualisation of host services beyond ILogger / ICharacterDevice — the runtime is a user of the defaults, not a definer.