-
Notifications
You must be signed in to change notification settings - Fork 0
RR16X Extension API
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.
-
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.
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