Skip to content

tero_peripherals

The GRLIB device models needed to boot RTEMS on the GR712RC and GR740. Every device is an IPeripheral subclass; each has a dedicated reference page under Peripherals covering register layouts bit by bit. This page is the module-level view: the class set, the shared contract, and the lifecycle.

# src/peripherals/CMakeLists.txt — peripherals sit on the bus, not the core
target_link_libraries(tero_peripherals
    PUBLIC  tero::interfaces tero::bus
    PRIVATE tero::warnings)

Responsibility

Model GRLIB IP cores as IPeripheral MMIO slaves: react to bus accesses, raise/lower IRQs through the injected bridges, schedule timed work, and DMA through the bus — all without ever seeing the CPU.

Depends on bus, not core

Peripherals link tero_interfaces and tero_bus. They DMA through the bus and never reference architectural state, so they cannot couple to the CPU.

The device set

src/peripherals/include/tero/peripherals/
├── irqmp.hpp        irqamp.hpp      ← interrupt controllers (GR712RC / GR740)
├── gptimer.hpp                       ← general-purpose timer unit
├── apbuart.hpp                       ← APB UART (console + aux)
├── memctrl.hpp                       ← FTMCTRL memory controller (stub regs)
├── prom.hpp                          ← boot ROM (IMemoryRegion)
├── grgpio.hpp       signal_port.hpp  ← GPIO port + concrete ISignalPort
└── version.hpp
Class Header Default base IRQ (recipe) Implements beyond IPeripheral Page
IrqMP irqmp.hpp:30 0x80000200 IInterruptController IRQMP
IrqAMP irqamp.hpp:33 0xFF904000 IInterruptController IRQMP
GPTimer gptimer.hpp:28 0x80000300 8 (GR712RC) / 1 (GR740) GPTimer
ApbUart apbuart.hpp:22 0x80000100 2 / 17..21 (GR712RC); 29/30 (GR740) APBUART
MemCtrl memctrl.hpp:22 0x80000000 MemCtrl
Prom prom.hpp:27 cfg.prom_base IMemoryRegion
GrGpio grgpio.hpp:36 0x80000900 / 0x80000A00 1..15 exposes ISignalPort per pin
SignalPort signal_port.hpp:15 concrete ISignalPort

IRQMP vs IRQAMP are distinct IP cores

The GR740 IRQ(A)MP is a separate GRLIB IP core from the GR712RC IRQMP, with a divergent register map and extended-IRQ delivery. They are modelled as sibling peripherals (IrqMP, IrqAMP), both satisfying the common IInterruptController surface — GR740 features are not bolted onto IrqMP.

Who instantiates them

Peripherals are not hard-wired into the Emulator. The SoC kits (gr712rc_config() / gr740_config()) populate EmulatorConfig::peripherals with one PeripheralSpec per device — each carrying an instance_name, a factory lambda, its IRQ lines, and an optional chardev_index. Emulator::initialize() runs each factory in vector order, allocates one IrqBridge per declared IRQ line, injects any chardev, and calls attach(). PROM is wired separately from the cfg.prom_* fields. There is no silicon-specific peripheral creation outside the recipes — the user can inspect, modify, or replace any entry.

The recipe wiring (from emulator_config.cpp):

  • GR712RC: irqmp, memctrl, gptimer0 (IRQ 8), six APBUARTs (apbuart0 IRQ 2 = console; apbuart1..5 IRQ 17..21 via EIRQ), and two grgpio ports.
  • GR740: irqamp, memctrl, gptimer0 (IRQ 1), two APBUARTs (apbuart0 IRQ 29, apbuart1 IRQ 30, both via EIRQ).

Common skeleton

All devices follow the same shape:

class FooPeripheral final : public IPeripheral {
public:
    explicit FooPeripheral(PhysAddr base);
    std::string_view device_class() const override;  // IP-core kind ("foo"); name() is final = instance name
    AddressRange     mmio_range() const override;
    void             attach(const PeripheralContext&) override;
    void             reset() override;
    Result<std::uint32_t> mmio_read (PhysAddr, AccessSize) override;
    Result<void>          mmio_write(PhysAddr, AccessSize, std::uint32_t) override;
    void             tick(SimTimeNs now) override;
    void             publish(IPublisher&) override;
private:
    PhysAddr          base_;
    PeripheralContext ctx_;   // cached from attach()
    /* per-device register state */
};

Lifecycle

Phase Call What happens
Construct factory lambda device built with its MMIO base
Attach attach(ctx) caches IBusMaster*, irqs, chardev, scheduler, logger, time, observer, num_cores, ns_per_cycle
Connect connect_ports(resolver) + declared ISignalPort edges peer wiring (GPIO etc.); most devices skip this
Reset reset() restore power-on register values
Run tick(now) / mmio_* / scheduled IEvents react to time and bus

The tick contract

tick(SimTimeNs now) is called once per scheduling round. Which devices do real work:

  • GPTimer decrements its prescaler and sub-timers (and schedules IRQ raises through ctx.irqs[...]).
  • ApbUart polls its ICharacterDevice for incoming bytes into the RX FIFO; TX bytes go out as they are written.
  • MemCtrl, IrqMP/IrqAMP do nothing in tick — they react to bus writes and external asserts, not to time.

MMIO size policy

The register-backed peripherals (APBUART, IRQMP, GPTIMER, MemCtrl) reject non-word accesses with ErrorCode::AlignmentError — RTEMS only ever uses st/ld (word) against them. The SystemBus MMIO constraints (alignment + single region) are checked before the device is even called.

Capabilities in use

  • IMemoryRegionProm (prom.hpp:27,56) implements it so the bus serves span-based bulk reads (debugger, fetch) directly from the ROM bank; memory_region() returns this. Register-only devices must not implement it.
  • ISignalPortGrGpio exposes input pins as ISignalPorts ("pin0".."pin31") so an external model can drive them; SignalPort (signal_port.hpp:15) is the reusable concrete one-bit line.

Custom peripherals

Anything you build follows the same IPeripheral contract — the runtime treats your device exactly like a built-in. A fully wired DMA-capable peripheral with an IRQ is in examples/demo-dma/ (both the PeripheralSpec and the add_peripheral forms) and walked through in the custom peripheral guide.

See also