Skip to content

Commit

Permalink
n64: simulate SysAD freezes when accessing non-RDRAM areas via cache (a…
Browse files Browse the repository at this point in the history
…res-emulator#1299)

The CPU will freeze until power-off if a cached access is performed to a
non RDRAM area. This obviously never happens with games, but it is a not
so uncommon error when doing very low level development on the system.

This patch emulates the freeze for the data cache, and for the
instruction cache when using the interpreter. Implementation for
instruction cache and recompiler is left as a followup.
  • Loading branch information
rasky authored Nov 29, 2023
1 parent cd11421 commit 1c9d388
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 68 deletions.
3 changes: 3 additions & 0 deletions ares/n64/cpu/cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ auto CPU::instruction() -> void {
step(1 * 2);
return exception.nmi();
}
if (scc.sysadFrozen) {
return;
}

if constexpr(Accuracy::CPU::Recompiler) {
// Fast path: attempt to lookup previously compiled blocks with devirtualizeFast
Expand Down
22 changes: 5 additions & 17 deletions ares/n64/cpu/cpu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,26 +148,12 @@ struct CPU : Thread {
cpu.step(48 * 2);
valid = 1;
tag = address & ~0x0000'0fff;
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);
cpu.busReadBurst<ICache>(tag | index, words);
}

auto writeBack(CPU& cpu) -> void {
cpu.step(48 * 2);
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]);
cpu.busWriteBurst<ICache>(tag | index, words);
}

auto read(u32 address) const -> u32 { return words[address >> 2 & 7]; }
Expand All @@ -192,7 +178,6 @@ struct CPU : Thread {
//8KB
struct Line {
auto hit(u32 address) const -> bool;
template<u32 Size> auto fill(u32 address, u64 data) -> void;
auto fill(u32 address) -> void;
auto writeBack() -> void;
template<u32 Size> auto read(u32 address) const -> u64;
Expand Down Expand Up @@ -301,6 +286,8 @@ struct CPU : Thread {
auto fetch(u64 vaddr) -> maybe<u32>;
template<u32 Size> auto busWrite(u32 address, u64 data) -> void;
template<u32 Size> auto busRead(u32 address) -> u64;
template<u32 Size> auto busWriteBurst(u32 address, u32 *data) -> void;
template<u32 Size> auto busReadBurst(u32 address, u32 *data) -> void;
template<u32 Size> auto read(u64 vaddr) -> maybe<u64>;
template<u32 Size> auto write(u64 vaddr, u64 data, bool alignedError=true) -> bool;
template<u32 Size> auto vaddrAlignedError(u64 vaddr, bool write) -> bool;
Expand Down Expand Up @@ -638,6 +625,7 @@ struct CPU : Thread {
//other
n64 latch;
n1 nmiPending;
n1 sysadFrozen;
} scc;

//interpreter-scc.cpp
Expand Down
51 changes: 3 additions & 48 deletions ares/n64/cpu/dcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,18 @@ auto CPU::DataCache::Line::hit(u32 address) const -> bool {
return valid && tag == (address & ~0x0000'0fff);
}

template<u32 Size> auto CPU::DataCache::Line::fill(u32 address, u64 data) -> void {
cpu.step(40 * 2);
valid = 1;
dirty = 1;
tag = address & ~0x0000'0fff;
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
if constexpr(Size != Dual) {
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
}
write<Size>(address, data);
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] = cpu.busRead<Word>(tag | index | 0x8);
words[3] = cpu.busRead<Word>(tag | index | 0xc);
}
write<Size>(address, data);
words[0] = cpu.busRead<Word>(tag | index | 0x0);
words[1] = cpu.busRead<Word>(tag | index | 0x4);
break;
}
}

auto CPU::DataCache::Line::fill(u32 address) -> void {
cpu.step(40 * 2);
valid = 1;
dirty = 0;
tag = address & ~0x0000'0fff;
//read words according to critical doubleword first scheme
switch(address & 8) {
case 0:
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] = 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;
}
cpu.busReadBurst<DCache>(tag | index, words);
}

auto CPU::DataCache::Line::writeBack() -> void {
cpu.step(40 * 2);
dirty = 0;
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]);
cpu.busWriteBurst<DCache>(tag | index, words);
}

auto CPU::DataCache::line(u32 vaddr) -> Line& {
Expand Down Expand Up @@ -115,7 +70,7 @@ auto CPU::DataCache::write(u32 vaddr, u32 address, u64 data) -> void {
auto& line = this->line(vaddr);
if(!line.hit(address)) {
if(line.valid && line.dirty) line.writeBack();
return line.fill<Size>(address, data);
line.fill(address);
} else {
cpu.step(1 * 2);
}
Expand Down
10 changes: 10 additions & 0 deletions ares/n64/cpu/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,21 @@ inline auto CPU::busWrite(u32 address, u64 data) -> void {
bus.write<Size>(address, data, *this);
}

template<u32 Size>
inline auto CPU::busWriteBurst(u32 address, u32 *data) -> void {
bus.writeBurst<Size>(address, data, *this);
}

template<u32 Size>
inline auto CPU::busRead(u32 address) -> u64 {
return bus.read<Size>(address, *this);
}

template<u32 Size>
inline auto CPU::busReadBurst(u32 address, u32 *data) -> void {
return bus.readBurst<Size>(address, data, *this);
}

auto CPU::fetch(u64 vaddr) -> maybe<u32> {
if(vaddrAlignedError<Word>(vaddr, false)) return nothing;
switch(segment(vaddr)) {
Expand Down
1 change: 1 addition & 0 deletions ares/n64/cpu/serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ auto CPU::serialize(serializer& s) -> void {
s(scc.epcError);
s(scc.latch);
s(scc.nmiPending);
s(scc.sysadFrozen);

for(auto& r : fpu.r) s(r.u64);
s(fpu.csr.roundMode);
Expand Down
48 changes: 46 additions & 2 deletions ares/n64/memory/bus.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
template<u32 Size>
inline auto Bus::read(u32 address, Thread& thread) -> u64 {
static constexpr u64 unmapped = 0;
address &= 0x1fff'ffff - (Size - 1);
static_assert(Size == Byte || Size == Half || Size == Word || Size == Dual);

if(address <= 0x007f'ffff) return rdram.ram.read<Size>(address);
if(address <= 0x03ef'ffff) return unmapped;
Expand All @@ -23,9 +23,31 @@ inline auto Bus::read(u32 address, Thread& thread) -> u64 {
return unmapped;
}

template<u32 Size>
inline auto Bus::readBurst(u32 address, u32 *data, Thread& thread) -> void {
static_assert(Size == DCache || Size == ICache);

if(address <= 0x03ff'ffff) {
data[0] = read<Word>(address | 0x0, thread);
data[1] = read<Word>(address | 0x4, thread);
data[2] = read<Word>(address | 0x8, thread);
data[3] = read<Word>(address | 0xc, thread);
if constexpr(Size == ICache) {
data[4] = read<Word>(address | 0x10, thread);
data[5] = read<Word>(address | 0x14, thread);
data[6] = read<Word>(address | 0x18, thread);
data[7] = read<Word>(address | 0x1c, thread);
}
return;
}

debug(unusual, "[Bus::readBurst] CPU frozen because of cached read to non-RDRAM area: 0x", hex(address, 8L));
cpu.scc.sysadFrozen = true;
}

template<u32 Size>
inline auto Bus::write(u32 address, u64 data, Thread& thread) -> void {
address &= 0x1fff'ffff - (Size - 1);
static_assert(Size == Byte || Size == Half || Size == Word || Size == Dual);
if constexpr(Accuracy::CPU::Recompiler) {
cpu.recompiler.invalidate(address + 0); if constexpr(Size == Dual)
cpu.recompiler.invalidate(address + 4);
Expand All @@ -50,3 +72,25 @@ inline auto Bus::write(u32 address, u64 data, Thread& thread) -> void {
if(address <= 0x7fff'ffff) return pi.write<Size>(address, data, thread);
return;
}

template<u32 Size>
inline auto Bus::writeBurst(u32 address, u32 *data, Thread& thread) -> void {
static_assert(Size == DCache || Size == ICache);

if(address <= 0x03ff'ffff) {
write<Word>(address | 0x0, data[0], thread);
write<Word>(address | 0x4, data[1], thread);
write<Word>(address | 0x8, data[2], thread);
write<Word>(address | 0xc, data[3], thread);
if constexpr(Size == ICache) {
write<Word>(address | 0x10, data[4], thread);
write<Word>(address | 0x14, data[5], thread);
write<Word>(address | 0x18, data[6], thread);
write<Word>(address | 0x1c, data[7], thread);
}
return;
}

debug(unusual, "[Bus::readBurst] CPU frozen because of cached write to non-RDRAM area: 0x", hex(address, 8L));
cpu.scc.sysadFrozen = true;
}
3 changes: 3 additions & 0 deletions ares/n64/memory/memory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ struct Bus {
//bus.hpp
template<u32 Size> auto read(u32 address, Thread& thread) -> u64;
template<u32 Size> auto write(u32 address, u64 data, Thread& thread) -> void;

template<u32 Size> auto readBurst(u32 address, u32* data, Thread& thread) -> void;
template<u32 Size> auto writeBurst(u32 address, u32* data, Thread& thread) -> void;
};

extern Bus bus;
2 changes: 1 addition & 1 deletion ares/n64/n64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace ares::Nintendo64 {
auto option(string name, string value) -> bool;

enum : u32 { Read, Write };
enum : u32 { Byte = 1, Half = 2, Word = 4, Dual = 8 };
enum : u32 { Byte = 1, Half = 2, Word = 4, Dual = 8, DCache = 16, ICache = 32 };

struct Region {
static inline auto NTSC() -> bool;
Expand Down

0 comments on commit 1c9d388

Please sign in to comment.