Skip to content

lince_bus

Physical-address routing. This module owns the Ram block(s) and the SystemBus that maps physical addresses to either a backing RAM or a peripheral's MMIO range.

It depends on lince_interfaces only — no awareness of the CPU.

Source layout

src/bus/
├── include/lince/bus/
│   ├── ram.hpp           ← raw byte storage
│   └── system_bus.hpp    ← address router + IBusMaster
└── src/
    ├── ram.cpp
    └── system_bus.cpp

Ram

Ram is a thin wrapper around std::vector<std::byte>:

class Ram {
public:
    Ram(PhysAddr base, std::size_t size);
    PhysAddr base() const noexcept;
    std::size_t size() const noexcept;
    Result<void> read (PhysAddr, std::span<std::byte>);
    Result<void> write(PhysAddr, std::span<const std::byte>);
private:
    PhysAddr base_;
    std::vector<std::byte> bytes_;
};

It deliberately knows nothing about endianness — the bytes are stored exactly as they appear on the wire. Big-endian byte-swap is the job of SystemBus.

Ram is movable but not copyable; a copy would silently double the backing storage and is almost never what you want.

SystemBus

SystemBus is the central physical-address router. It owns:

  • An ordered list of Ram blocks (by unique_ptr<Ram>).
  • A non-owning list of IPeripheral* mappings.

And exposes:

  • Untyped accesses: read_physical(PhysAddr, std::span<std::byte>) / write_physical(PhysAddr, std::span<const std::byte>).
  • Typed accesses with big-endian byte-swap: read_physical_u32, write_physical_u32, read_physical_u16, write_physical_u16, read_physical_u8, write_physical_u8.
  • IBusMaster — DMA-side interface for peripherals. The same byte fabric.

The bus is non-copyable and non-movable because peripherals cache raw pointers into it (Decision 1 in design decisions).

Routing rules

  1. Walk the routing table in registration order.
  2. The first range whose [base, base + size) contains the access wins.
  3. If no range matches, return ErrorCode::InvalidAddress.
  4. If an access straddles two regions, return ErrorCode::BusError. Real hardware latches one transaction against one target.

Big-endian semantics

The typed accessors use encode_be / decode_be:

inline uint32_t decode_be(std::span<const std::byte, 4> bytes) noexcept {
    return  (uint32_t(std::to_integer<uint8_t>(bytes[0])) << 24)
          | (uint32_t(std::to_integer<uint8_t>(bytes[1])) << 16)
          | (uint32_t(std::to_integer<uint8_t>(bytes[2])) <<  8)
          |  uint32_t(std::to_integer<uint8_t>(bytes[3]));
}

Untyped read_physical / write_physical move bytes verbatim — they are the right tool for byte-level memcpy from a host buffer.

MMIO access constraints

  • Naturally aligned 1 / 2 / 4-byte accesses only (alignment errors are ErrorCode::AlignmentError).
  • Single-region per access (no straddle, returns BusError).

These constraints are enforced in SystemBus, before the peripheral is even called. That means a peripheral handler can assume its mmio_read / mmio_write arguments are valid.

Adding RAM

SystemBus bus;
bus.add_ram(std::make_unique<Ram>(PhysAddr{0x40000000}, 16 * 1024 * 1024));

The runtime adds RAM during Emulator::initialize() from the EmulatorConfig fields. You typically do not call add_ram from application code — but it is available if you need a multi-region memory map.

Adding peripherals

bus.map_peripheral(periph_ptr, periph_ptr->mmio_range());

This is what Emulator::add_peripheral does internally. The bus does not own the peripheral; it caches the raw pointer for routing only.

DMA

Peripherals that implement IBusMaster consumers (their PeripheralContext::bus field is the bus itself) can do:

ctx_.bus->dma_read(PhysAddr{src}, span<byte>);
ctx_.bus->dma_write(PhysAddr{dst}, span<const byte>);

DMA shares the exact same address map as CPU accesses. Including your own peripherals — a peripheral can DMA into another peripheral's MMIO range if you want it to.

What is intentionally not in lince_bus

  • No CPU-side code (the ICpuBus adapter lives in lince_runtime).
  • No virtual addresses (those come with the SRMMU in Phase 7).
  • No cache.
  • No notion of "cycle" or "tick" — the bus is a pure routing function.