Skip to content

User guide

This guide covers how to build, run, and consume the Lince emulator. If you want to modify the emulator internals, read the architecture overview first.

The dedicated sub-pages drill into individual topics:

The remainder of this page is a single linear walkthrough that covers the most common case end-to-end.

1. Build and Installation

Lince requires a modern C++ host environment.

Requirements: - CMake ≥ 3.25 - GCC ≥ 12 or Clang ≥ 16 - C++20 standard library support

Dependencies: Lince is practically dependency-free. The only external libraries are fetched automatically by CMake during the configure step: - Catch2 (v3.5.3) for the test suite. - tl::expected (v1.1.0) as a C++20 polyfill for the C++23 std::expected type.

Building:

git clone https://github.com/.../lince.git
cd lince
cmake -S . -B build -G Ninja
cmake --build build

Testing:

ctest --test-dir build --output-on-failure
You should see all 262 unit and integration tests passing. Some tests (like test_rtems_boot) automatically skip if you don't have the required cross-compiled ELF payloads.


2. Running the CLI (lince-emu)

The standalone application is located at build/src/app/lince-emu.

Options Reference

Option Default Description
--image <path> (required) Path to the SPARC big-endian ELF executable to load.
--ram <MiB> 16 Amount of simulated RAM in MiB.
--cores <1\|2> 1 Number of LEON cores to instantiate.
--budget <ns> 1000000000 Simulated time limit (1,000,000,000 ns = 1 second).
--verbose Off Enables Debug level logging to stderr.
--version Prints the emulator version and cleanly exits.
--help, -h Prints the usage menu and cleanly exits.

Exit Codes and ErrorMode

  • 0 (OK): Execution naturally hit the simulated time budget.
  • 2 (Invalid Args): Malformed CLI parameters.
  • 3 (Init Error): Invalid core/RAM configuration.
  • 4 (Load Error): Rejection of the ELF (invalid format, not SPARC, read fail).
  • 5 (ErrorMode): Execution was halted due to a fatal trap.

ErrorMode Diagnostics: If lince-emu returns exit code 5, a SPARC V8 fatal hardware fault occurred (specifically, a hardware TRAP or software Tcc generated while PSR.ET == 0). The CLI dumps a complete post-mortem dump of Core 0's registers, globals, and active window:

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

3. Using Lince as a C++ Library

Lince exposes a robust, type-safe C++ API for integration into other applications (such as automated testing rigs, debuggers, or SMP2 simulation wrappers).

The Minimal Flow

#include "lince/runtime/emulator.hpp"
#include "lince/runtime/emulator_config.hpp"
#include <iostream>

int main() {
    // 1. Get the sensible defaults for a GR712RC-like configuration
    auto cfg = lince::runtime::gr712rc_config();
    cfg.num_cores = 1;
    cfg.ram_size = 16 * 1024 * 1024; // 16 MiB

    // 2. Create the emulator instance
    auto emu_res = lince::runtime::Emulator::create(cfg);
    if (!emu_res) return -1; // Handle errors properly in real code
    auto emu = std::move(emu_res.value());

    // 3. Initialize mapping and default peripherals (IRQMP, UART, etc.)
    emu->initialize();

    // 4. Load a payload
    emu->load_elf("my_firmware.elf");

    // 5. Run the emulator for 5 seconds simulated time
    auto result = emu->run_for(lince::SimTimeNs{5'000'000'000ULL});

    std::cout << "Executed " << result.instructions_executed 
              << " instructions.\n";
    return 0;
}

4. Advanced Configuration

The EmulatorConfig Object

The EmulatorConfig object instructs the Emulator on its internal boundaries before runtime initialization.

Field Type Default Description
num_cores std::uint32_t 1 Core count (GR712RC supports 1 or 2). Secondary cores boot halted (Power-down).
ram_base std::uint32_t 0x40000000 Physical address where the internal RAM block starts.
ram_size std::uint32_t 16MiB Bytes allocated.
ns_per_insn std::uint64_t 20 Equivalent simulated instruction speed (~50 MIPS).
quantum std::uint32_t 1000 Cooperative instructions executed per core round-robin before context switching.
entry_point_override std::uint32_t 0 If non-zero, overrides the e_entry entrypoint dictated by an ELF header.

Accessing Result Data (RunResult)

A call to run_for() or run_until() returns a RunResult containing metadata about the completed execution block. Check the reason field (HaltReason) to know exactly why control was returned to the host.

HaltReason Enum Value Meaning Host Action
DurationExpired Sim time ran out (run_for). Proceed to another run_for().
DeadlineReached Sim time hit limit (run_until). Proceed to another run_until().
ErrorMode Trap fired while PSR.ET == 0 (fatal). Abort and inspect emu->core(0).
Breakpoint A software/hardware breakpoint hit. Proceed with debug inspection.

5. Console UART Emulation

By default, the emulator hooks the ApbUart component directly to stdout / stdin. To aggressively suppress console emissions during library integration (e.g. inside unit tests) or to capture output programmatically:

#include "tests/support/capturing_char_device.hpp"

// Inject a capture buffer before calling emu->initialize()
auto cap = std::make_unique<lince::test_support::CapturingCharDevice>();
auto* ref = cap.get();
emu->set_character_device(std::move(cap));

emu->run_for(...);

std::string output = ref->captured();

6. Writing Custom Peripherals

Adding a user-provided peripheral is designed to be trivial. A custom class must simply implement the lince::IPeripheral interface and be registered against the emulator with add_peripheral().

You do not need to recompile the project. The emulator natively routes physical bus traffic via the central SystemBus at runtime.

To learn exactly how to format the DMA memory translations, interface stubs, and attach-phase injection required to add a memory-mapped peripheral, read the complete custom peripheral tutorial.