Skip to content

RR16X Extension API

Alex edited this page Aug 1, 2026 · 7 revisions

NOTE:

  • APList contains non-owning pointers.

  • The bus does not manage peripheral lifetime.

  • Peripheral instances are created and owned externally (normally by main).

  • Peripheral lifetime must equal or exceed the lifetime of the bus and APList usage.

  • Peripheral tick() calls occur in APList order.

  • Address collisions between peripherals are not supported. If two peripherals claim the same address, behavior is unspecified, and is programmer error.

  • A call to tick() represents one emulator clock cycle from the peripheral's perspective.

  • A peripheral must not directly access CPU state.

  • Communication with the CPU occurs through bus-visible registers and optional interrupt lines.

  • Should a device require an interrupt line, the class InterruptEnhancer is the current interrupt system.

  • tick is called AFTER interrupt evaluation and BEFORE the CPU itself

Interrupt-capable peripherals

Peripherals requiring interrupts communicate through InterruptEnhancer.

A peripheral:

  • owns acknowledgement of its own interrupt condition.
  • raises an interrupt by calling InterruptEnhancer::raise_interrupt().
  • clears its interrupt by calling InterruptEnhancer::clear_interrupt().

Interrupts are level triggered. A peripheral must keep its interrupt condition asserted until serviced.

The CPU does not acknowledge peripheral interrupts directly.

To add a new extension, please observe the following requirements:

  • Inherit from AbstractPeripheral
  • expose and implement a write(), read(), tick() marked as override
  • init in main, then put in APList as a reference. A peripheral should only respond to addresses listed in readableAddresses/writableAddresses.

The bus uses these lists to determine which peripheral receives a memory access. EXAMPLE:

NewPeripheral.h

STEP 1

#include "AbstractPeripheral.h"
class NewPeripheral: public AbstractPeripheral
{
private:
// local vars go here
public:
NewPeripheral();
uint16_t read(uint32_t address) override;
void write(uint32_t address, uint16_t value) override;
void tick() override; // called every cycle
};

STEP 2

NewPeripheral.cpp

#include "NewPeripheral.h"
NewPeripheral::NewPeripheral()
{
readableAddresses = {}; // put the addresses where you'll respond to a read call here, each element is a uint32_t
writableAddresses = {}; // put the addresses where you'll respond to a write call here, each element is a uint32_t
}
uint16_t NewPeripheral::read(uint32_t address)
{
// put your logic to respond to a read call here
}
void NewPeripheral::write(uint32_t address, uint16_t value)
{
// put your logic to respond to a write call here
}
void NewPeripheral::tick()
{
// put logic to be called every cycle here
}

RR16X_EMULATOR.cpp

STEP 3

#include <iostream>
#include <fstream>
#include <string>
#include "CPU.h"
#include "bus.h"
#include "AbstractPeripheral.h"

#include "interruptEnhancer.h"
#include "Timer.h"
#include "Multiplier.h"
#include "UART.h"
#include "WideIntCoprocessor.h"
#include "FP32Coprocessor.h"
#include "DMA.h"

#include "NewPeripheral.h" // <- new stuff here

#include <filesystem>

STEP 4

InterruptEnhancer IE;

   // std::cout << "[TRACER 2] Initializing Timer...\n";
    Timer timer(IE, 0);

    //std::cout << "[TRACER 3] Initializing Multiplier...\n";
    Multiplier multiplier;

    //std::cout << "[TRACER 4] Initializing UART...\n";
    UART uart(IE, 0);

   // std::cout << "[TRACER 5] Initializing WIC...\n";
    WideIntCoprocessor WIC;

   // std::cout << "[TRACER 6] Initializing FC...\n";
    FP32Coprocessor FC;

   // std::cout << "[TRACER 7] Initializing DMA...\n";
    DMA dma(myBus, IE, 0);
    
    NewPeripheral NP; // init here

STEP 5

std::vector<AbstractPeripheral*> APList = { &timer,&dma,&uart,&IE,&multiplier,&WIC,&FC, &NP}; // add the new peripheral to APList by reference

Clone this wiki locally