n64: large refactoring to introduce timing in memory handlers

This very boring commit should basically produce no changes. It just
make sure all devices can now step the CPU while the I/O is in
progress, if needed.
This commit is contained in:
Giovanni Bajo authored and Luke Usher committed 2023-02-01 08:21:27 +00:00
1 parent 21c985b3da
commit ad16c4a67a
33 files changed
+363 -262

No files matched your search

+3 -3
View File
@@ -1,6 +1,6 @@
//Audio Interface
struct AI : Thread, Memory::IO<AI> {
struct AI : Thread, Memory::RCP<AI> {
Node::Object node;
Node::Audio::Stream stream;
@@ -23,8 +23,8 @@ struct AI : Thread, Memory::IO<AI> {
auto power(bool reset) -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
+2 -2
View File
@@ -1,4 +1,4 @@
auto AI::readWord(u32 address) -> u32 {
auto AI::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0xfffff) >> 2;
n32 data;
@@ -21,7 +21,7 @@ auto AI::readWord(u32 address) -> u32 {
return data;
}
auto AI::writeWord(u32 address, u32 data_) -> void {
auto AI::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0xfffff) >> 2;
n32 data = data_;
+3 -1
View File
@@ -38,10 +38,12 @@ struct Cartridge {
u32 source = 0;
u32 offset = 0;
} flash;
struct ISViewer : Memory::IO<ISViewer> {
struct ISViewer : Memory::PI<ISViewer> {
Memory::Writable ram; //unserialized
//isviewer.cpp
auto readHalf(u32 address) -> u16;
auto writeHalf(u32 address, u16 data) -> void;
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
} isviewer;
+17 -6
View File
@@ -1,10 +1,9 @@
auto Cartridge::ISViewer::readWord(u32 address) -> u32 {
auto Cartridge::ISViewer::readHalf(u32 address) -> u16 {
address = (address & 0xffff);
u32 data = ram.read<Word>(address);
return data;
return ram.read<Half>(address);
}
auto Cartridge::ISViewer::writeWord(u32 address, u32 data) -> void {
auto Cartridge::ISViewer::writeHalf(u32 address, u16 data) -> void {
address = (address & 0xffff);
if(address == 0x14) {
@@ -17,12 +16,24 @@ auto Cartridge::ISViewer::writeWord(u32 address, u32 data) -> void {
// In order to satisfy both libraries, we assume it behaves as libdragon
// expects, and by forcing the write to never hit ram, libultra remains
// functional.
for(auto address : range(u16(data))) {
for(auto address : range(data)) {
char c = ram.read<Byte>(0x20 + address);
fputc(c, stdout);
}
return;
}
ram.write<Word>(address, data);
ram.write<Half>(address, data);
}
auto Cartridge::ISViewer::readWord(u32 address) -> u32 {
address = (address & 0xffff);
return ram.read<Word>(address);
}
auto Cartridge::ISViewer::writeWord(u32 address, u32 data) -> void {
address = (address & 0xffff);
ram.write<Half>(address+0, data >> 16);
ram.write<Word>(address+2, data & 0xffff);
}
+18 -16
View File
@@ -147,26 +147,26 @@ struct CPU : Thread {
cpu.step(48);
valid = 1;
tag = address & ~0x0000'0fff;
words[0] = bus.read<Word>(tag | index | 0x00);
words[1] = bus.read<Word>(tag | index | 0x04);
words[2] = bus.read<Word>(tag | index | 0x08);
words[3] = bus.read<Word>(tag | index | 0x0c);
words[4] = bus.read<Word>(tag | index | 0x10);
words[5] = bus.read<Word>(tag | index | 0x14);
words[6] = bus.read<Word>(tag | index | 0x18);
words[7] = bus.read<Word>(tag | index | 0x1c);
words[0] = cpu.busRead<Word>(tag | index | 0x00);
words[1] = cpu.busRead<Word>(tag | index | 0x04);
words[2] = cpu.busRead<Word>(tag | index | 0x08);
words[3] = cpu.busRead<Word>(tag | index | 0x0c);
words[4] = cpu.busRead<Word>(tag | index | 0x10);
words[5] = cpu.busRead<Word>(tag | index | 0x14);
words[6] = cpu.busRead<Word>(tag | index | 0x18);
words[7] = cpu.busRead<Word>(tag | index | 0x1c);
}
auto writeBack(CPU& cpu) -> void {
cpu.step(48);
bus.write<Word>(tag | index | 0x00, words[0]);
bus.write<Word>(tag | index | 0x04, words[1]);
bus.write<Word>(tag | index | 0x08, words[2]);
bus.write<Word>(tag | index | 0x0c, words[3]);
bus.write<Word>(tag | index | 0x10, words[4]);
bus.write<Word>(tag | index | 0x14, words[5]);
bus.write<Word>(tag | index | 0x18, words[6]);
bus.write<Word>(tag | index | 0x1c, words[7]);
cpu.busWrite<Word>(tag | index | 0x00, words[0]);
cpu.busWrite<Word>(tag | index | 0x04, words[1]);
cpu.busWrite<Word>(tag | index | 0x08, words[2]);
cpu.busWrite<Word>(tag | index | 0x0c, words[3]);
cpu.busWrite<Word>(tag | index | 0x10, words[4]);
cpu.busWrite<Word>(tag | index | 0x14, words[5]);
cpu.busWrite<Word>(tag | index | 0x18, words[6]);
cpu.busWrite<Word>(tag | index | 0x1c, words[7]);
}
auto read(u32 address) const -> u32 { return words[address >> 2 & 7]; }
@@ -260,6 +260,8 @@ struct CPU : Thread {
auto segment(u64 vaddr) -> Context::Segment;
auto devirtualize(u64 vaddr) -> maybe<u64>;
auto fetch(u64 vaddr) -> u32;
template<u32 Size> auto busWrite(u32 address, u64 data) -> void;
template<u32 Size> auto busRead(u32 address) -> u64;
template<u32 Size> auto read(u64 vaddr) -> maybe<u64>;
template<u32 Size> auto write(u64 vaddr, u64 data) -> bool;
auto addressException(u64 vaddr) -> void;
+20 -20
View File
@@ -11,21 +11,21 @@ template<u32 Size> auto CPU::DataCache::Line::fill(u32 address, u64 data) -> voi
switch(address & 8) {
case 0:
if constexpr(Size != Dual) {
words[0] = bus.read<Word>(tag | index | 0x0);
words[1] = bus.read<Word>(tag | index | 0x4);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
}
write<Size>(address, data);
words[2] = bus.read<Word>(tag | index | 0x8);
words[3] = bus.read<Word>(tag | index | 0xc);
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
break;
case 8:
if constexpr(Size != Dual) {
words[2] = bus.read<Word>(tag | index | 0x8);
words[3] = bus.read<Word>(tag | index | 0xc);
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
}
write<Size>(address, data);
words[0] = bus.read<Word>(tag | index | 0x0);
words[1] = bus.read<Word>(tag | index | 0x4);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
break;
}
}
@@ -38,16 +38,16 @@ auto CPU::DataCache::Line::fill(u32 address) -> void {
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
words[0] = bus.read<Word>(tag | index | 0x0);
words[1] = bus.read<Word>(tag | index | 0x4);
words[2] = bus.read<Word>(tag | index | 0x8);
words[3] = bus.read<Word>(tag | index | 0xc);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
break;
case 8:
words[2] = bus.read<Word>(tag | index | 0x8);
words[3] = bus.read<Word>(tag | index | 0xc);
words[0] = bus.read<Word>(tag | index | 0x0);
words[1] = bus.read<Word>(tag | index | 0x4);
words[2] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
break;
}
}
@@ -55,10 +55,10 @@ auto CPU::DataCache::Line::fill(u32 address) -> void {
auto CPU::DataCache::Line::writeBack() -> void {
cpu.step(40);
dirty = 0;
bus.write<Word>(tag | index | 0x0, words[0]);
bus.write<Word>(tag | index | 0x4, words[1]);
bus.write<Word>(tag | index | 0x8, words[2]);
bus.write<Word>(tag | index | 0xc, words[3]);
cpu.busWrite<Word>(tag | index | 0x0, words[0]);
cpu.busWrite<Word>(tag | index | 0x4, words[1]);
cpu.busWrite<Word>(tag | index | 0x8, words[2]);
cpu.busWrite<Word>(tag | index | 0xc, words[3]);
}
auto CPU::DataCache::line(u32 address) -> Line& {
+23 -9
View File
@@ -108,6 +108,20 @@ auto CPU::devirtualize(u64 vaddr) -> maybe<u64> {
unreachable;
}
template<u32 Size>
inline auto CPU::busWrite(u32 address, u64 data) -> void {
u32 cycles = 0;
bus.write<Size>(address, data, cycles);
step(cycles);
}
template<u32 Size>
inline auto CPU::busRead(u32 address) -> u64 {
u32 cycles = 0; u64 data;
data = bus.read<Size>(address, cycles);
return step(cycles), data;
}
auto CPU::fetch(u64 vaddr) -> u32 {
switch(segment(vaddr)) {
case Context::Segment::Unused:
@@ -119,7 +133,7 @@ auto CPU::fetch(u64 vaddr) -> u32 {
if(auto match = tlb.load(vaddr)) {
if(match.cache) return icache.fetch(match.address & context.physMask, cpu);
step(1);
return bus.read<Word>(match.address & context.physMask);
return busRead<Word>(match.address & context.physMask);
}
step(1);
addressException(vaddr);
@@ -130,10 +144,10 @@ auto CPU::fetch(u64 vaddr) -> u32 {
return icache.fetch(vaddr & 0xffff'ffff, cpu);
case Context::Segment::Direct:
step(1);
return bus.read<Word>(vaddr & 0x1fff'ffff);
return busRead<Word>(vaddr & 0x1fff'ffff);
case Context::Segment::Direct32:
step(1);
return bus.read<Word>(vaddr & 0xffff'ffff);
return busRead<Word>(vaddr & 0xffff'ffff);
}
unreachable;
@@ -166,7 +180,7 @@ auto CPU::read(u64 vaddr) -> maybe<u64> {
if(auto match = tlb.load(vaddr)) {
if(match.cache) return dcache.read<Size>(match.address & context.physMask);
step(1);
return bus.read<Size>(match.address & context.physMask);
return busRead<Size>(match.address & context.physMask);
}
step(1);
addressException(vaddr);
@@ -177,10 +191,10 @@ auto CPU::read(u64 vaddr) -> maybe<u64> {
return dcache.read<Size>(vaddr & 0xffff'ffff);
case Context::Segment::Direct:
step(1);
return bus.read<Size>(vaddr & 0x1fff'ffff);
return busRead<Size>(vaddr & 0x1fff'ffff);
case Context::Segment::Direct32:
step(1);
return bus.read<Size>(vaddr & 0xffff'ffff);
return busRead<Size>(vaddr & 0xffff'ffff);
}
unreachable;
@@ -213,7 +227,7 @@ auto CPU::write(u64 vaddr, u64 data) -> bool {
if(auto match = tlb.store(vaddr)) {
if(match.cache) return dcache.write<Size>(match.address & context.physMask, data), true;
step(1);
return bus.write<Size>(match.address & context.physMask, data), true;
return busWrite<Size>(match.address & context.physMask, data), true;
}
step(1);
addressException(vaddr);
@@ -224,10 +238,10 @@ auto CPU::write(u64 vaddr, u64 data) -> bool {
return dcache.write<Size>(vaddr & 0xffff'ffff, data), true;
case Context::Segment::Direct:
step(1);
return bus.write<Size>(vaddr & 0x1fff'ffff, data), true;
return busWrite<Size>(vaddr & 0x1fff'ffff, data), true;
case Context::Segment::Direct32:
step(1);
return bus.write<Size>(vaddr & 0xffff'ffff, data), true;
return busWrite<Size>(vaddr & 0xffff'ffff, data), true;
}
unreachable;
+2 -1
View File
@@ -24,9 +24,10 @@ auto CPU::Recompiler::emit(u32 address) -> Block* {
auto block = (Block*)allocator.acquire(sizeof(Block));
beginFunction(3);
u32 memCycles;
bool hasBranched = 0;
while(true) {
u32 instruction = bus.read<Word>(address);
u32 instruction = bus.read<Word>(address, memCycles);
bool branched = emitEXECUTE(instruction);
if(unlikely(instruction == 0x1000'ffff)) {
//accelerate idle loops
+3 -1
View File
@@ -1,6 +1,6 @@
//Disk Drive
struct DD : Memory::IO<DD> {
struct DD : Memory::PI<DD> {
Node::Object obj;
Node::Port port;
Node::Peripheral node;
@@ -65,6 +65,8 @@ struct DD : Memory::IO<DD> {
auto rtcTickSecond() -> void;
//io.cpp
auto readHalf(u32 address) -> u16;
auto writeHalf(u32 address, u16 data) -> void;
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
+109 -97
View File
@@ -1,31 +1,31 @@
auto DD::readWord(u32 address) -> u32 {
address = (address & 0x7f) >> 2;
n32 data;
auto DD::readHalf(u32 address) -> u16 {
address = (address & 0x7f) >> 1;
n16 data = 0;
//ASIC_DATA
if(address == 0) {
data.bit(16,31) = io.data;
data.bit(0,15) = io.data;
}
//ASIC_MISC_REG
if(address == 1) {
if(address == 2) {
}
//ASIC_STATUS
if(address == 2) {
data.bit(16) = io.status.diskChanged;
data.bit(17) = io.status.mechaError;
data.bit(18) = io.status.writeProtect;
data.bit(19) = io.status.headRetracted;
data.bit(20) = io.status.spindleMotorStopped;
data.bit(22) = io.status.resetState;
data.bit(23) = io.status.busyState;
data.bit(24) = (bool)disk; //disk present
data.bit(25) = irq.mecha.line;
data.bit(26) = irq.bm.line;
data.bit(27) = io.bm.error;
data.bit(28) = io.status.requestC2Sector;
data.bit(30) = io.status.requestUserSector;
if(address == 4) {
data.bit(0) = io.status.diskChanged;
data.bit(1) = io.status.mechaError;
data.bit(2) = io.status.writeProtect;
data.bit(3) = io.status.headRetracted;
data.bit(4) = io.status.spindleMotorStopped;
data.bit(6) = io.status.resetState;
data.bit(7) = io.status.busyState;
data.bit(8) = (bool)disk; //disk present
data.bit(9) = irq.mecha.line;
data.bit(10) = irq.bm.line;
data.bit(11) = io.bm.error;
data.bit(12) = io.status.requestC2Sector;
data.bit(14) = io.status.requestUserSector;
//acknowledge bm interrupt (tested on real hardware)
if(irq.bm.line) {
@@ -36,133 +36,133 @@ auto DD::readWord(u32 address) -> u32 {
}
//ASIC_CUR_TK
if(address == 3) {
data.bit(16,31) = io.currentTrack;
if(address == 6) {
data.bit(0,15) = io.currentTrack;
}
//ASIC_BM_STATUS
if(address == 4) {
data.bit(16) = io.bm.c1Error;
data.bit(21) = io.bm.c1Single;
data.bit(22) = io.bm.c1Double;
data.bit(23) = io.bm.c1Correct;
data.bit(24) = io.bm.blockTransfer;
data.bit(25) = io.micro.error;
data.bit(26) = io.bm.error;
data.bit(31) = io.bm.start;
if(address == 8) {
data.bit(0) = io.bm.c1Error;
data.bit(5) = io.bm.c1Single;
data.bit(6) = io.bm.c1Double;
data.bit(7) = io.bm.c1Correct;
data.bit(8) = io.bm.blockTransfer;
data.bit(9) = io.micro.error;
data.bit(10) = io.bm.error;
data.bit(15) = io.bm.start;
}
//ASIC_ERR_SECTOR
if(address == 5) {
data.bit(16,23) = io.error.sector;
data.bit(24) = io.error.selfStop;
data.bit(25) = io.error.clockUnlock;
data.bit(26) = ~(bool)disk; //no disk
data.bit(27) = io.error.offTrack;
data.bit(28) = io.error.overrun;
data.bit(29) = io.error.spindle;
data.bit(30) = io.micro.error;
data.bit(31) = io.error.am;
if(address == 10) {
data.bit(0,7) = io.error.sector;
data.bit(8) = io.error.selfStop;
data.bit(9) = io.error.clockUnlock;
data.bit(10) = ~(bool)disk; //no disk
data.bit(11) = io.error.offTrack;
data.bit(12) = io.error.overrun;
data.bit(13) = io.error.spindle;
data.bit(14) = io.micro.error;
data.bit(15) = io.error.am;
}
//ASIC_SEQ_STATUS
if(address == 6) {
if(address == 12) {
}
//ASIC_CUR_SECTOR
if(address == 7) {
data.bit(24,31) = io.currentSector;
data.bit(16,23) = 0xc3;
if(address == 14) {
data.bit(8,15) = io.currentSector;
data.bit(0,7) = 0xc3;
}
//ASIC_HARD_RESET
if(address == 8) {
if(address == 16) {
}
//ASIC_C1_S0
if(address == 9) {
if(address == 18) {
}
//ASIC_HOST_SECBYTE
if(address == 10) {
data.bit(16,23) = io.sectorSizeBuf;
if(address == 20) {
data.bit(0,7) = io.sectorSizeBuf;
}
//ASIC_C1_S2
if(address == 11) {
if(address == 22) {
}
//ASIC_SEC_BYTE
if(address == 12) {
data.bit(16,23) = io.sectorSize;
data.bit(24,31) = io.sectorBlock;
if(address == 24) {
data.bit(0,7) = io.sectorSize;
data.bit(8,15) = io.sectorBlock;
}
//ASIC_C1_S4
if(address == 13) {
if(address == 26) {
}
//ASIC_C1_S6
if(address == 14) {
if(address == 28) {
}
//ASIC_CUR_ADDRESS
if(address == 15) {
if(address == 30) {
}
//ASIC_ID_REG
if(address == 16) {
data.bit(16,31) = io.id;
if(address == 32) {
data.bit(0,15) = io.id;
}
//ASIC_TEST_REG
if(address == 17) {
if(address == 34) {
}
//ASIC_TEST_PIN_SEL
if(address == 18) {
if(address == 36) {
}
debugger.io(Read, address, data);
return data;
}
auto DD::writeWord(u32 address, u32 data_) -> void {
address = (address & 0x7f) >> 2;
n32 data = data_;
auto DD::writeHalf(u32 address, u16 data_) -> void {
address = (address & 0x7f) >> 1;
n16 data = data_;
//ASIC_DATA
if(address == 0) {
io.data = data.bit(16,31);
io.data = data.bit(0,15);
}
//ASIC_MISC_REG
if(address == 1) {
if(address == 2) {
}
//ASIC_CMD
if(address == 2) {
command(data.bit(16,31));
if(address == 4) {
command(data.bit(0,15));
}
//ASIC_CUR_TK
if(address == 3) {
if(address == 6) {
}
//ASIC_BM_CTL
if(address == 4) {
io.bm.reset |= data.bit(28);
io.bm.readMode = data.bit(30);
//irq.bm.mask = ~data.bit(29);
io.bm.disableORcheck = data.bit(27);
io.bm.disableC1Correction = data.bit(26);
io.bm.blockTransfer = data.bit(25);
if (data.bit(24)) {
if(address == 8) {
io.bm.reset |= data.bit(12);
io.bm.readMode = data.bit(14);
//irq.bm.mask = ~data.bit(13);
io.bm.disableORcheck = data.bit(11);
io.bm.disableC1Correction = data.bit(10);
io.bm.blockTransfer = data.bit(9);
if (data.bit(8)) {
//mecha int reset
lower(IRQ::MECHA);
}
io.currentSector = data.bit(16,23);
if (!data.bit(28) && io.bm.reset) {
io.currentSector = data.bit(0,7);
if (!data.bit(12) && io.bm.reset) {
//BM reset
io.bm.start = 0;
io.bm.error = 0;
@@ -172,76 +172,88 @@ auto DD::writeWord(u32 address, u32 data_) -> void {
lower(IRQ::BM);
}
if(data.bit(31) && disk) {
if(data.bit(15) && disk) {
//start BM
io.bm.start |= data.bit(31);
io.bm.start |= data.bit(15);
//TODO: proper research into seek and access times
queue.insert(Queue::DD_BM_Request, 50'000 + (io.currentTrack.bit(0,11) / 15));
}
}
//ASIC_ERR_SECTOR
if(address == 5) {
if(address == 10) {
}
//ASIC_SEQ_CTL
if(address == 6) {
io.micro.enable = data.bit(30);
if(address == 12) {
io.micro.enable = data.bit(14);
}
//ASIC_CUR_SECTOR
if(address == 7) {
if(address == 14) {
}
//ASIC_HARD_RESET
if(address == 8) {
if(address == 16) {
if((data >> 16) == 0xAAAA) {
power(true);
}
}
//ASIC_C1_S0
if(address == 9) {
if(address == 18) {
}
//ASIC_HOST_SECBYTE
if(address == 10) {
io.sectorSizeBuf = data.bit(16,23);
if(address == 20) {
io.sectorSizeBuf = data.bit(0,7);
}
//ASIC_C1_S2
if(address == 11) {
io.sectorSize = data.bit(16,23);
io.sectorBlock = data.bit(24,31);
if(address == 22) {
io.sectorSize = data.bit(0,7);
io.sectorBlock = data.bit(8,15);
}
//ASIC_SEC_BYTE
if(address == 12) {
if(address == 24) {
}
//ASIC_C1_S4
if(address == 13) {
if(address == 26) {
}
//ASIC_C1_S6
if(address == 14) {
if(address == 28) {
}
//ASIC_CUR_ADDRESS
if(address == 15) {
if(address == 30) {
}
//ASIC_ID_REG
if(address == 16) {
if(address == 32) {
}
//ASIC_TEST_REG
if(address == 17) {
if(address == 34) {
}
//ASIC_TEST_PIN_SEL
if(address == 18) {
if(address == 36) {
}
debugger.io(Write, address, data);
}
auto DD::readWord(u32 address) -> u32 {
n32 data;
data.bit(0,15) = readHalf(address + 0);
data.bit(16,31) = readHalf(address + 2);
return (u32)data;
}
auto DD::writeWord(u32 address, u32 data) -> void {
writeHalf(address + 0, data >> 16);
writeHalf(address + 2, data & 0xffff);
}
+30 -30
View File
@@ -1,30 +1,30 @@
template<u32 Size>
inline auto Bus::read(u32 address) -> u64 {
inline auto Bus::read(u32 address, u32& cycles) -> u64 {
static constexpr u64 unmapped = 0;
address &= 0x1fff'ffff - (Size - 1);
if(address <= 0x007f'ffff) return rdram.ram.read<Size>(address);
if(address <= 0x03ef'ffff) return unmapped;
if(address <= 0x03ff'ffff) return rdram.read<Size>(address);
if(address <= 0x0407'ffff) return rsp.read<Size>(address);
if(address <= 0x040f'ffff) return rsp.status.read<Size>(address);
if(address <= 0x041f'ffff) return rdp.read<Size>(address);
if(address <= 0x042f'ffff) return rdp.io.read<Size>(address);
if(address <= 0x043f'ffff) return mi.read<Size>(address);
if(address <= 0x044f'ffff) return vi.read<Size>(address);
if(address <= 0x045f'ffff) return ai.read<Size>(address);
if(address <= 0x046f'ffff) return pi.read<Size>(address);
if(address <= 0x047f'ffff) return ri.read<Size>(address);
if(address <= 0x048f'ffff) return si.read<Size>(address);
if(address <= 0x03ff'ffff) return rdram.read<Size>(address, cycles);
if(address <= 0x0407'ffff) return rsp.read<Size>(address, cycles);
if(address <= 0x040f'ffff) return rsp.status.read<Size>(address, cycles);
if(address <= 0x041f'ffff) return rdp.read<Size>(address, cycles);
if(address <= 0x042f'ffff) return rdp.io.read<Size>(address, cycles);
if(address <= 0x043f'ffff) return mi.read<Size>(address, cycles);
if(address <= 0x044f'ffff) return vi.read<Size>(address, cycles);
if(address <= 0x045f'ffff) return ai.read<Size>(address, cycles);
if(address <= 0x046f'ffff) return pi.read<Size>(address, cycles);
if(address <= 0x047f'ffff) return ri.read<Size>(address, cycles);
if(address <= 0x048f'ffff) return si.read<Size>(address, cycles);
if(address <= 0x04ff'ffff) return unmapped;
if(address <= 0x1fbf'ffff) return pi.read<Size>(address);
if(address <= 0x1fcf'ffff) return si.read<Size>(address);
if(address <= 0x7fff'ffff) return pi.read<Size>(address);
if(address <= 0x1fbf'ffff) return pi.read<Size>(address, cycles);
if(address <= 0x1fcf'ffff) return si.read<Size>(address, cycles);
if(address <= 0x7fff'ffff) return pi.read<Size>(address, cycles);
return unmapped;
}
template<u32 Size>
inline auto Bus::write(u32 address, u64 data) -> void {
inline auto Bus::write(u32 address, u64 data, u32& cycles) -> void {
address &= 0x1fff'ffff - (Size - 1);
if constexpr(Accuracy::CPU::Recompiler) {
cpu.recompiler.invalidate(address + 0); if constexpr(Size == Dual)
@@ -33,20 +33,20 @@ inline auto Bus::write(u32 address, u64 data) -> void {
if(address <= 0x007f'ffff) return rdram.ram.write<Size>(address, data);
if(address <= 0x03ef'ffff) return;
if(address <= 0x03ff'ffff) return rdram.write<Size>(address, data);
if(address <= 0x0407'ffff) return rsp.write<Size>(address, data);
if(address <= 0x040f'ffff) return rsp.status.write<Size>(address, data);
if(address <= 0x041f'ffff) return rdp.write<Size>(address, data);
if(address <= 0x042f'ffff) return rdp.io.write<Size>(address, data);
if(address <= 0x043f'ffff) return mi.write<Size>(address, data);
if(address <= 0x044f'ffff) return vi.write<Size>(address, data);
if(address <= 0x045f'ffff) return ai.write<Size>(address, data);
if(address <= 0x046f'ffff) return pi.write<Size>(address, data);
if(address <= 0x047f'ffff) return ri.write<Size>(address, data);
if(address <= 0x048f'ffff) return si.write<Size>(address, data);
if(address <= 0x03ff'ffff) return rdram.write<Size>(address, data, cycles);
if(address <= 0x0407'ffff) return rsp.write<Size>(address, data, cycles);
if(address <= 0x040f'ffff) return rsp.status.write<Size>(address, data, cycles);
if(address <= 0x041f'ffff) return rdp.write<Size>(address, data, cycles);
if(address <= 0x042f'ffff) return rdp.io.write<Size>(address, data, cycles);
if(address <= 0x043f'ffff) return mi.write<Size>(address, data, cycles);
if(address <= 0x044f'ffff) return vi.write<Size>(address, data, cycles);
if(address <= 0x045f'ffff) return ai.write<Size>(address, data, cycles);
if(address <= 0x046f'ffff) return pi.write<Size>(address, data, cycles);
if(address <= 0x047f'ffff) return ri.write<Size>(address, data, cycles);
if(address <= 0x048f'ffff) return si.write<Size>(address, data, cycles);
if(address <= 0x04ff'ffff) return;
if(address <= 0x1fbf'ffff) return pi.write<Size>(address, data);
if(address <= 0x1fcf'ffff) return si.write<Size>(address, data);
if(address <= 0x7fff'ffff) return pi.write<Size>(address, data);
if(address <= 0x1fbf'ffff) return pi.write<Size>(address, data, cycles);
if(address <= 0x1fcf'ffff) return si.write<Size>(address, data, cycles);
if(address <= 0x7fff'ffff) return pi.write<Size>(address, data, cycles);
return;
}
+69 -16
View File
@@ -1,9 +1,13 @@
template<typename T>
struct IO {
struct RCP { //A device which is part of RCP
const u32 DefaultReadCycles = 0;
const u32 DefaultWriteCycles = 0;
template<u32 Size>
auto read(u32 address) -> u64 {
auto read(u32 address, u32& cycles) -> u64 {
cycles = DefaultReadCycles;
if constexpr(Size == Byte) {
auto data = ((T*)this)->readWord(address);
auto data = ((T*)this)->readWord(address, cycles);
switch(address & 3) {
case 0: return data >> 24;
case 1: return data >> 16;
@@ -12,43 +16,92 @@ struct IO {
}
}
if constexpr(Size == Half) {
auto data = ((T*)this)->readWord(address);
auto data = ((T*)this)->readWord(address, cycles);
switch(address & 2) {
case 0: return data >> 16;
case 2: return data >> 0;
}
}
if constexpr(Size == Word) {
return ((T*)this)->readWord(address);
return ((T*)this)->readWord(address, cycles);
}
if constexpr(Size == Dual) {
u64 data = ((T*)this)->readWord(address);
return data << 32 | ((T*)this)->readWord(address + 4);
u64 data = ((T*)this)->readWord(address, cycles);
return data << 32 | ((T*)this)->readWord(address + 4, cycles);
}
unreachable;
}
template<u32 Size>
auto write(u32 address, u64 data) -> void {
auto write(u32 address, u64 data, u32& cycles) -> void {
cycles = DefaultWriteCycles;
if constexpr(Size == Byte) {
switch(address & 3) {
case 0: return ((T*)this)->writeWord(address, data << 24);
case 1: return ((T*)this)->writeWord(address, data << 16);
case 2: return ((T*)this)->writeWord(address, data << 8);
case 3: return ((T*)this)->writeWord(address, data << 0);
case 0: return ((T*)this)->writeWord(address, data << 24, cycles);
case 1: return ((T*)this)->writeWord(address, data << 16, cycles);
case 2: return ((T*)this)->writeWord(address, data << 8, cycles);
case 3: return ((T*)this)->writeWord(address, data << 0, cycles);
}
}
if constexpr(Size == Half) {
switch(address & 2) {
case 0: return ((T*)this)->writeWord(address, data << 16);
case 2: return ((T*)this)->writeWord(address, data << 0);
case 0: return ((T*)this)->writeWord(address, data << 16, cycles);
case 2: return ((T*)this)->writeWord(address, data << 0, cycles);
}
}
if constexpr(Size == Word) {
((T*)this)->writeWord(address, data);
((T*)this)->writeWord(address, data, cycles);
}
if constexpr(Size == Dual) {
((T*)this)->writeWord(address, data >> 32);
((T*)this)->writeWord(address, data >> 32, cycles);
}
}
};
template<typename T>
struct PI { //A device which is reachable only behind PI
template<u32 Size>
auto read(u32 address) -> u64 {
static_assert(Size == Half || Size == Word); //PI bus will do 32-bit (CPU) or 16-bit (DMA) only
if constexpr(Size == Half) {
return ((T*)this)->readHalf(address);
}
if constexpr(Size == Word) {
return ((T*)this)->readWord(address);
}
unreachable;
}
template<u32 Size>
auto write(u32 address, u64 data) -> void {
static_assert(Size == Half || Size == Word); //PI bus will do 32-bit (CPU) or 16-bit (DMA) only
if constexpr(Size == Half) {
return ((T*)this)->writeHalf(address, data);
}
if constexpr(Size == Word) {
return ((T*)this)->writeWord(address, data);
}
unreachable;
}
};
template<typename T>
struct SI { //A device which is reachable only behind SI
template<u32 Size>
auto read(u32 address) -> u64 {
static_assert(Size == Word); //SI bus will do 32-bit (CPU/DMA)
if constexpr(Size == Word) {
return ((T*)this)->readWord(address);
}
unreachable;
}
template<u32 Size>
auto write(u32 address, u64 data) -> void {
static_assert(Size == Word); //PI bus will do 32-bit (CPU/DMA)
if constexpr(Size == Word) {
return ((T*)this)->writeWord(address, data);
}
unreachable;
}
};
+2 -2
View File
@@ -31,8 +31,8 @@ namespace Memory {
struct Bus {
//bus.hpp
template<u32 Size> auto read(u32 address) -> u64;
template<u32 Size> auto write(u32 address, u64 data) -> void;
template<u32 Size> auto read(u32 address, u32& cycles) -> u64;
template<u32 Size> auto write(u32 address, u64 data, u32& cycles) -> void;
};
extern Bus bus;
+2 -2
View File
@@ -1,4 +1,4 @@
auto MI::readWord(u32 address) -> u32 {
auto MI::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0xfffff) >> 2;
n32 data;
@@ -42,7 +42,7 @@ auto MI::readWord(u32 address) -> u32 {
return data;
}
auto MI::writeWord(u32 address, u32 data_) -> void {
auto MI::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0xfffff) >> 2;
n32 data = data_;
+3 -3
View File
@@ -1,6 +1,6 @@
//MIPS Interface
struct MI : Memory::IO<MI> {
struct MI : Memory::RCP<MI> {
Node::Object node;
struct Debugger {
@@ -27,8 +27,8 @@ struct MI : Memory::IO<MI> {
auto power(bool reset) -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
+2 -2
View File
@@ -1,4 +1,4 @@
inline auto PI::readWord(u32 address) -> u32 {
inline auto PI::readWord(u32 address, u32& cycles) -> u32 {
if(address <= 0x046f'ffff) return ioRead(address);
if (unlikely(io.ioBusy)) {
@@ -50,7 +50,7 @@ inline auto PI::busRead(u32 address) -> u32 {
return unmapped; //accesses here actually lock out the RCP
}
inline auto PI::writeWord(u32 address, u32 data) -> void {
inline auto PI::writeWord(u32 address, u32 data, u32& cycles) -> void {
if(address <= 0x046f'ffff) return ioWrite(address, data);
if(io.ioBusy) return;
+3 -3
View File
@@ -1,6 +1,6 @@
//Peripheral Interface
struct PI : Memory::IO<PI> {
struct PI : Memory::RCP<PI> {
Node::Object node;
struct Debugger {
@@ -28,8 +28,8 @@ struct PI : Memory::IO<PI> {
auto ioWrite(u32 address, u32 data) -> void;
//bus.hpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
auto writeFinished() -> void;
auto writeForceFinish() -> void;
template <u32 Size>
+1 -1
View File
@@ -1,6 +1,6 @@
//PIF-NUS
struct PIF : Memory::IO<PIF> {
struct PIF : Memory::SI<PIF> {
Node::Object node;
Memory::Readable rom;
Memory::Writable ram;
+4 -4
View File
@@ -1,4 +1,4 @@
auto RDP::readWord(u32 address) -> u32 {
auto RDP::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0xfffff) >> 2;
n32 data;
@@ -56,7 +56,7 @@ auto RDP::readWord(u32 address) -> u32 {
return data;
}
auto RDP::writeWord(u32 address, u32 data_) -> void {
auto RDP::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0xfffff) >> 2;
n32 data = data_;
@@ -113,7 +113,7 @@ auto RDP::writeWord(u32 address, u32 data_) -> void {
debugger.ioDPC(Write, address, data);
}
auto RDP::IO::readWord(u32 address) -> u32 {
auto RDP::IO::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0xfffff) >> 2;
n32 data;
@@ -144,7 +144,7 @@ auto RDP::IO::readWord(u32 address) -> u32 {
return data;
}
auto RDP::IO::writeWord(u32 address, u32 data_) -> void {
auto RDP::IO::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0xfffff) >> 2;
n32 data = data_;
+6 -6
View File
@@ -1,6 +1,6 @@
//Reality Display Processor
struct RDP : Thread, Memory::IO<RDP> {
struct RDP : Thread, Memory::RCP<RDP> {
Node::Object node;
struct Debugger {
@@ -66,8 +66,8 @@ struct RDP : Thread, Memory::IO<RDP> {
auto setColorImage() -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
auto flushCommands() -> void;
//serialization.cpp
@@ -333,13 +333,13 @@ struct RDP : Thread, Memory::IO<RDP> {
} x, y;
} fillRectangle_;
struct IO : Memory::IO<IO> {
struct IO : Memory::RCP<IO> {
RDP& self;
IO(RDP& self) : self(self) {}
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
struct BIST {
n1 check;
+2 -2
View File
@@ -1,4 +1,4 @@
auto RDRAM::readWord(u32 address) -> u32 {
auto RDRAM::readWord(u32 address, u32& cycles) -> u32 {
u32 chipID = address >> 13 & 3;
auto& chip = chips[chipID];
address = (address & 0x3ff) >> 2;
@@ -63,7 +63,7 @@ auto RDRAM::readWord(u32 address) -> u32 {
return data;
}
auto RDRAM::writeWord(u32 address, u32 data) -> void {
auto RDRAM::writeWord(u32 address, u32 data, u32& cycles) -> void {
u32 chipID = address >> 13 & 3;
auto& chip = chips[chipID];
address = (address & 0x3ff) >> 2;
+3 -3
View File
@@ -1,6 +1,6 @@
//RAMBUS RAM
struct RDRAM : Memory::IO<RDRAM> {
struct RDRAM : Memory::RCP<RDRAM> {
Node::Object node;
Memory::Writable ram;
@@ -24,8 +24,8 @@ struct RDRAM : Memory::IO<RDRAM> {
auto power(bool reset) -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
+2 -2
View File
@@ -1,4 +1,4 @@
auto RI::readWord(u32 address) -> u32 {
auto RI::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0xfffff) >> 2;
n32 data = 0;
@@ -58,7 +58,7 @@ auto RI::readWord(u32 address) -> u32 {
return data;
}
auto RI::writeWord(u32 address, u32 data_) -> void {
auto RI::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0xfffff) >> 2;
n32 data = data_;
+3 -3
View File
@@ -1,6 +1,6 @@
//RDRAM Interface
struct RI : Memory::IO<RI> {
struct RI : Memory::RCP<RI> {
Node::Object node;
struct Debugger {
@@ -19,8 +19,8 @@ struct RI : Memory::IO<RI> {
auto power(bool reset) -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
+3 -3
View File
@@ -490,10 +490,10 @@ auto RSP::Disassembler::sccRegisterName(u32 index) const -> string {
}
auto RSP::Disassembler::sccRegisterValue(u32 index) const -> string {
u32 value = 0;
if(index <= 6) value = rsp.readWord((index & 7) << 2);
u32 value = 0; u32 cycles;
if(index <= 6) value = rsp.readWord((index & 7) << 2, cycles);
if(index == 7) value = self.status.semaphore; //rsp.readSCC(7) has side-effects
if(index >= 8) value = rdp.readWord((index & 7) << 2);
if(index >= 8) value = rdp.readWord((index & 7) << 2, cycles);
if(showValues) return {sccRegisterName(index), hint("{$", hex(value, 8L), "}")};
return sccRegisterName(index);
}
+6 -2
View File
@@ -1,9 +1,13 @@
auto RSP::MFC0(r32& rt, u8 rd) -> void {
u32 cycles = 0;
if((rd & 8) == 0) rt.u32 = Nintendo64::rsp.ioRead ((rd & 7) << 2);
if((rd & 8) != 0) rt.u32 = Nintendo64::rdp.readWord((rd & 7) << 2);
if((rd & 8) != 0) rt.u32 = Nintendo64::rdp.readWord((rd & 7) << 2, cycles);
step(cycles);
}
auto RSP::MTC0(cr32& rt, u8 rd) -> void {
u32 cycles = 0;
if((rd & 8) == 0) Nintendo64::rsp.ioWrite ((rd & 7) << 2, rt.u32);
if((rd & 8) != 0) Nintendo64::rdp.writeWord((rd & 7) << 2, rt.u32);
if((rd & 8) != 0) Nintendo64::rdp.writeWord((rd & 7) << 2, rt.u32, cycles);
step(cycles);
}
+4 -4
View File
@@ -1,4 +1,4 @@
auto RSP::readWord(u32 address) -> u32 {
auto RSP::readWord(u32 address, u32& cycles) -> u32 {
if(address <= 0x0403'ffff) {
if(address & 0x1000) return imem.read<Word>(address);
else return dmem.read<Word>(address);
@@ -67,7 +67,7 @@ auto RSP::ioRead(u32 address) -> u32 {
return data;
}
auto RSP::writeWord(u32 address, u32 data) -> void {
auto RSP::writeWord(u32 address, u32 data, u32& cycles) -> void {
if(address <= 0x0403'ffff) {
if(address & 0x1000) return recompiler.invalidate(address & 0xfff), imem.write<Word>(address, data);
else return dmem.write<Word>(address, data);
@@ -156,7 +156,7 @@ auto RSP::ioWrite(u32 address, u32 data_) -> void {
debugger.ioSCC(Write, address, data);
}
auto RSP::Status::readWord(u32 address) -> u32 {
auto RSP::Status::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0x7ffff) >> 2;
n32 data;
@@ -177,7 +177,7 @@ auto RSP::Status::readWord(u32 address) -> u32 {
return data;
}
auto RSP::Status::writeWord(u32 address, u32 data_) -> void {
auto RSP::Status::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0x7ffff) >> 2;
n32 data = data_;
+6 -6
View File
@@ -1,6 +1,6 @@
//Reality Signal Processor
struct RSP : Thread, Memory::IO<RSP> {
struct RSP : Thread, Memory::RCP<RSP> {
Node::Object node;
Memory::Writable dmem;
Memory::Writable imem;
@@ -47,8 +47,8 @@ struct RSP : Thread, Memory::IO<RSP> {
auto dmaTransferStep() -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
auto ioRead(u32 address) -> u32;
auto ioWrite(u32 address, u32 data) -> void;
@@ -75,13 +75,13 @@ struct RSP : Thread, Memory::IO<RSP> {
} busy, full;
} dma;
struct Status : Memory::IO<Status> {
struct Status : Memory::RCP<Status> {
RSP& self;
Status(RSP& self) : self(self) {}
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
n1 semaphore;
n1 halted = 1;
+2 -2
View File
@@ -1,4 +1,4 @@
auto SI::readWord(u32 address) -> u32 {
auto SI::readWord(u32 address, u32& cycles) -> u32 {
if(address <= 0x048f'ffff) return ioRead(address);
if (unlikely(io.ioBusy)) {
@@ -54,7 +54,7 @@ auto SI::ioRead(u32 address) -> u32 {
return data;
}
auto SI::writeWord(u32 address, u32 data) -> void {
auto SI::writeWord(u32 address, u32 data, u32& cycles) -> void {
if(address <= 0x048f'ffff) return ioWrite(address, data);
if(io.ioBusy) return;
+3 -3
View File
@@ -1,6 +1,6 @@
//Serial Interface
struct SI : Memory::IO<SI> {
struct SI : Memory::RCP<SI> {
Node::Object node;
struct Debugger {
@@ -25,8 +25,8 @@ struct SI : Memory::IO<SI> {
//io.cpp
auto ioRead(u32 address) -> u32;
auto ioWrite(u32 address, u32 data) -> void;
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
auto writeFinished() -> void;
auto writeForceFinish() -> void;
+2 -2
View File
@@ -1,4 +1,4 @@
auto VI::readWord(u32 address) -> u32 {
auto VI::readWord(u32 address, u32& cycles) -> u32 {
address = (address & 0xfffff) >> 2;
n32 data;
@@ -95,7 +95,7 @@ auto VI::readWord(u32 address) -> u32 {
return data;
}
auto VI::writeWord(u32 address, u32 data_) -> void {
auto VI::writeWord(u32 address, u32 data_, u32& cycles) -> void {
address = (address & 0xfffff) >> 2;
n32 data = data_;
+2 -2
View File
@@ -147,7 +147,7 @@ auto VI::refresh() -> void {
auto line = screen->pixels(1).data() + (dy - vscan_start) * hscan_len;
u32 x0 = vi.io.xsubpixel + vi.io.xscale * (dx0 - vi.io.hstart);
for(i32 dx = dx0; dx < dx1; dx++) {
u16 data = bus.read<Half>(address + (x0 >> 10) * 2);
u16 data = rdram.ram.read<Half>(address + (x0 >> 10) * 2);
line[dx - hscan_start] = 1 << 24 | data >> 1;
x0 += vi.io.xscale;
}
@@ -165,7 +165,7 @@ auto VI::refresh() -> void {
auto line = screen->pixels(1).data() + (dy - vscan_start) * hscan_len;
u32 x0 = vi.io.xsubpixel + vi.io.xscale * (dx0 - vi.io.hstart);
for(i32 dx = dx0; dx < dx1; dx++) {
u32 data = bus.read<Word>(address + (x0 >> 10) * 4);
u32 data = rdram.ram.read<Word>(address + (x0 >> 10) * 4);
line[dx - hscan_start] = data >> 8;
x0 += vi.io.xscale;
}
+3 -3
View File
@@ -1,6 +1,6 @@
//Video Interface
struct VI : Thread, Memory::IO<VI> {
struct VI : Thread, Memory::RCP<VI> {
Node::Object node;
Node::Video::Screen screen;
@@ -24,8 +24,8 @@ struct VI : Thread, Memory::IO<VI> {
auto power(bool reset) -> void;
//io.cpp
auto readWord(u32 address) -> u32;
auto writeWord(u32 address, u32 data) -> void;
auto readWord(u32 address, u32& cycles) -> u32;
auto writeWord(u32 address, u32 data, u32& cycles) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;