lince_interfaces¶
A header-only vocabulary library that defines the strong types and
contracts shared by every other module. It produces no .a file —
add_library(... INTERFACE) — and depends on nothing.
Headers¶
Located under src/interfaces/include/lince/:
| Header | What it defines |
|---|---|
types.hpp |
PhysAddr, VirtAddr, CoreId, SimTimeNs, IrqLine, AccessSize, ErrorCode, Result<T>, make_error() |
address_range.hpp |
AddressRange — half-open [base, base + size) for MMIO dispatch |
ilogger.hpp |
ILogger, LogLevel |
itime_source.hpp |
ITimeSource — pull-only sim-time source |
ipublisher.hpp |
IPublisher — SMP2-style observable field registration |
ifault_injector.hpp |
IFaultInjector — Level-2 FT hook surface |
icharacter_device.hpp |
ICharacterDevice — UART TX/RX byte path |
ientry_point.hpp |
IEntryPoint — named callable, maps to Smp::IEntryPoint |
iperipheral.hpp |
IPeripheral — unified contract for every device |
peripheral_context.hpp |
PeripheralContext — services injected via attach() |
ibus_master.hpp |
IBusMaster — DMA-side interface |
iinterrupt_source.hpp |
IInterruptSource — raise() / lower() |
ischeduler.hpp |
IScheduler — schedule_event(when, IEvent*) |
ievent.hpp |
IEvent — callable scheduled event |
icpu_bus.hpp |
ICpuBus — virtual-address CPU-side data bus |
The strong-type contract¶
The strong types in types.hpp are enum class aliases over fixed-width
unsigned integers:
enum class PhysAddr : std::uint32_t {};
enum class VirtAddr : std::uint32_t {};
enum class CoreId : std::uint8_t {};
enum class SimTimeNs: std::uint64_t {};
enum class IrqLine : std::uint8_t {};
enum class AccessSize : std::uint8_t {
Byte = 1, Half = 2, Word = 4
};
They are zero-overhead (the underlying integer is preserved) and
not implicitly convertible to each other or to raw int. The header
provides arithmetic operators only where they make physical sense:
PhysAddr + uint32_t → PhysAddr(offset)PhysAddr - PhysAddr → uint32_t(distance)SimTimeNs + SimTimeNs → SimTimeNs- No
CoreId + CoreId(a core ID is an identifier, not a number).
The to_underlying(x) helper extracts the raw integer when you really
need it (encoders, hex formatters). Prefer that to static_cast.
Result and ErrorCode¶
template <typename T>
using Result = tl::expected<T, ErrorCode>;
enum class ErrorCode : std::uint8_t {
Ok = 0,
BusError,
InvalidAddress,
AlignmentError,
TrapGenerated,
InvalidConfig,
ElfLoadError,
};
[[nodiscard]] inline tl::unexpected<ErrorCode> make_error(ErrorCode c);
Result<T> is the canonical return type at every public API boundary.
tl::expected is used in place of std::expected so the project can
stay on C++20; semantics match. When the project moves to C++23 this
will be a one-line using change.
IPeripheral contract¶
Every peripheral, no matter how trivial, implements the same interface:
class IPeripheral {
public:
virtual std::string_view name() const = 0;
virtual AddressRange mmio_range() const = 0;
virtual void attach(const PeripheralContext& ctx) = 0;
virtual void reset() = 0;
virtual Result<uint32_t> mmio_read (PhysAddr, AccessSize) = 0;
virtual Result<void> mmio_write(PhysAddr, AccessSize, uint32_t) = 0;
virtual void tick(SimTimeNs now) = 0;
virtual void publish(IPublisher&) {}
virtual ~IPeripheral() = default;
};
PeripheralContext carries everything a device might need:
IBusMaster* (DMA), IInterruptSource* (IRQ), IScheduler* (timed
events), ILogger*, ITimeSource*, and ICharacterDevice* (only
APBUart uses the last one).
The runtime injects this context through attach() once, then never
mutates it. A peripheral that needs DMA or IRQs simply caches the
relevant pointer.
What the module deliberately does not include¶
- No log macros (
ILoggeris the only logging surface). - No utility functions on
Result<T>beyond whattl::expectedprovides. - No global allocator, no global registry, no thread-local state.
Anything that is not strictly required by the type system or the contracts lives in a downstream module.