diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 513d6555..50cb4589 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -52,7 +52,9 @@ target_include_directories(parallel-rdp PUBLIC parallel-rdp-standalone/spirv-cross parallel-rdp-standalone/vulkan parallel-rdp-standalone/vulkan-headers/include - parallel-rdp-standalone/util) + parallel-rdp-standalone/util + ../src/core/n64 + ) if(WIN32) target_compile_definitions(parallel-rdp PUBLIC VK_USE_PLATFORM_WIN32_KHR) diff --git a/src/core/n64/CMakeLists.txt b/src/core/n64/CMakeLists.txt index 47a9dc7b..916718ce 100644 --- a/src/core/n64/CMakeLists.txt +++ b/src/core/n64/CMakeLists.txt @@ -21,6 +21,7 @@ find_package(fmt REQUIRED) add_subdirectory(core) set(PARALLELRDP_INCLUDES + ../../frontend ../../../external/parallel-rdp-standalone/vulkan ../../../external/parallel-rdp-standalone/util ../../../external/parallel-rdp-standalone/parallel-rdp @@ -33,6 +34,6 @@ add_library(n64 memory_regions.hpp ../BaseCore.cpp ../BaseCore.hpp) -target_include_directories(n64 PRIVATE . .. ../../frontend/sdl ${PARALLELRDP_INCLUDES}) +target_include_directories(n64 PRIVATE . .. ${PARALLELRDP_INCLUDES}) target_include_directories(n64 PUBLIC ${mio_SOURCE_DIR}/include ${toml11_SOURCE_DIR}/include) target_link_libraries(n64 PRIVATE mio::mio toml11::toml11 fmt::fmt n64-core) diff --git a/src/core/n64/Core.cpp b/src/core/n64/Core.cpp index 150dc005..1adc569a 100644 --- a/src/core/n64/Core.cpp +++ b/src/core/n64/Core.cpp @@ -3,9 +3,9 @@ #include namespace natsukashii::n64::core { -Core::Core(const std::string& rom) { +Core::Core(Platform platform, const std::string& rom) { mem.LoadROM(rom); - LoadParallelRDP(mem.GetRDRAM()); + LoadParallelRDP(platform, mem.GetRDRAM()); } void Core::Run() { diff --git a/src/core/n64/Core.hpp b/src/core/n64/Core.hpp index bfa9e50b..32c6edc1 100644 --- a/src/core/n64/Core.hpp +++ b/src/core/n64/Core.hpp @@ -4,11 +4,13 @@ #include #include +enum class Platform : bool; + namespace natsukashii::n64::core { -using namespace natsukashii::core; -struct Core : BaseCore { + +struct Core : natsukashii::core::BaseCore { ~Core() override = default; - explicit Core(const std::string&); + explicit Core(Platform platform, const std::string&); void Run() override; void PollInputs(u32) override; private: diff --git a/src/core/n64/core/CMakeLists.txt b/src/core/n64/core/CMakeLists.txt index 322254a3..a4d3f036 100644 --- a/src/core/n64/core/CMakeLists.txt +++ b/src/core/n64/core/CMakeLists.txt @@ -11,7 +11,7 @@ add_library(n64-core RDP.cpp RDP.hpp mmio/VI.cpp - mmio/VI.hpp mmio/Interrupt.hpp mmio/MI.cpp mmio/MI.hpp mmio/Interrupt.cpp) + mmio/VI.hpp mmio/Interrupt.hpp mmio/MI.cpp mmio/MI.hpp mmio/Interrupt.cpp MMIO.cpp MMIO.hpp RSP.cpp RSP.hpp rsp/decode.cpp rsp/instructions.cpp) target_include_directories(n64-core PRIVATE . .. ../../ mmio) target_link_libraries(n64-core PUBLIC n64-cpu) diff --git a/src/core/n64/core/Cpu.cpp b/src/core/n64/core/Cpu.cpp index 44ff5d7a..8c45adc3 100644 --- a/src/core/n64/core/Cpu.cpp +++ b/src/core/n64/core/Cpu.cpp @@ -1 +1,102 @@ #include +#include +#include +#include + +namespace natsukashii::n64::core { +inline bool ShouldServiceInterrupt(Registers& regs) { + bool interrupts_pending = (regs.cop0.status.im & regs.cop0.cause.interruptPending) != 0; + bool interrupts_enabled = regs.cop0.status.ie == 1; + bool currently_handling_exception = regs.cop0.status.exl == 1; + bool currently_handling_error = regs.cop0.status.erl == 1; + + return interrupts_pending && interrupts_enabled && + !currently_handling_exception && !currently_handling_error; +} + +inline void CheckCompareInterrupt(MI& mi, Registers& regs) { + regs.cop0.count++; + regs.cop0.count &= 0x1FFFFFFFF; + if(regs.cop0.count == (u64)regs.cop0.compare << 1) { + regs.cop0.cause.ip7 = 1; + UpdateInterrupt(mi, regs); + } +} + +inline bool Is64BitAddressing(Cop0& cp0, u64 addr) { + u8 region = (addr >> 62) & 3; + switch(region) { + case 0b00: return cp0.status.ux; + case 0b01: return cp0.status.sx; + case 0b11: return cp0.status.kx; + default: return false; + } +} + +void FireException(Registers& regs, ExceptionCode code, int cop, s64 pc) { + bool old_exl = regs.cop0.status.exl; + + if(!regs.cop0.status.exl) { + if(regs.prevDelaySlot) { // TODO: cached value of delay_slot should be used, but Namco Museum breaks! + regs.cop0.cause.branchDelay = true; + pc -= 4; + } else { + regs.cop0.cause.branchDelay = false; + } + + regs.cop0.EPC = pc; + } + + regs.cop0.status.exl = true; + regs.cop0.cause.copError = cop; + regs.cop0.cause.exceptionCode = code; + + if(regs.cop0.status.bev) { + util::panic("BEV bit set!\n"); + } else { + switch(code) { + case Interrupt: case TLBModification: + case AddressErrorLoad: case AddressErrorStore: + case InstructionBusError: case DataBusError: + case Syscall: case Breakpoint: + case ReservedInstruction: case CoprocessorUnusable: + case Overflow: case Trap: + case FloatingPointError: case Watch: + regs.SetPC((s64)((s32)0x80000180)); + break; + case TLBLoad: case TLBStore: + if(old_exl || regs.cop0.tlbError == INVALID) { + regs.SetPC((s64)((s32)0x80000180)); + } else if(Is64BitAddressing(regs.cop0, regs.cop0.badVaddr)) { + regs.SetPC((s64)((s32)0x80000080)); + } else { + regs.SetPC((s64)((s32)0x80000000)); + } + break; + default: util::panic("Unhandled exception! {}\n", code); + } + } +} + +inline void HandleInterrupt(Registers& regs) { + if(ShouldServiceInterrupt(regs)) { + FireException(regs, Interrupt, 0, regs.pc); + } +} + +void Cpu::Step(Mem& mem) { + regs.gpr[0] = 0; + regs.prevDelaySlot = regs.delaySlot; + regs.delaySlot = false; + + CheckCompareInterrupt(mem.mmio.mi); + + u32 instruction = mem.Read(regs, regs.pc, regs.pc); + + HandleInterrupt(regs); + + regs.oldPC = regs.pc; + regs.pc = regs.nextPC; + regs.nextPC += 4; +} +} \ No newline at end of file diff --git a/src/core/n64/core/Cpu.hpp b/src/core/n64/core/Cpu.hpp index 055e6b4b..6502a11d 100644 --- a/src/core/n64/core/Cpu.hpp +++ b/src/core/n64/core/Cpu.hpp @@ -1,8 +1,32 @@ #pragma once #include +#include namespace natsukashii::n64::core { struct Cpu { + Cpu() = default; + void Step(Mem&); Registers regs; }; + +enum class ExceptionCode : u8 { + Interrupt, + TLBModification, + TLBLoad, + TLBStore, + AddressErrorLoad, + AddressErrorStore, + InstructionBusError, + DataBusError, + Syscall, + Breakpoint, + ReservedInstruction, + CoprocessorUnusable, + Overflow, + Trap, + FloatingPointError = 15, + Watch = 23 +}; + +void FireException(Registers& regs, ExceptionCode code, int cop, s64 pc); } diff --git a/src/core/n64/core/MMIO.cpp b/src/core/n64/core/MMIO.cpp new file mode 100644 index 00000000..35c48186 --- /dev/null +++ b/src/core/n64/core/MMIO.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +namespace natsukashii::n64::core { +u32 MMIO::Read(u32 addr) { + switch (addr) { + case 0x04040000 ... 0x040FFFFF: return rsp.Read(addr); + case 0x04100000 ... 0x041FFFFF: return rdp.Read(addr); + case 0x04300000 ... 0x043FFFFF: return mi.Read(addr); + case 0x04400000 ... 0x044FFFFF: return vi.Read(addr); + case 0x04500000 ... 0x045FFFFF: return ai.Read(addr); + case 0x04600000 ... 0x046FFFFF: return pi.Read(mi, addr); + case 0x04700000 ... 0x047FFFFF: return ri.Read(addr); + case 0x04800000 ... 0x048FFFFF: return si.Read(mi, addr); + default: util::panic("Unhandled mmio read at addr {:08X}\n", addr); + } +} + +void MMIO::Write(Mem& mem, Registers& regs, u32 addr, u32 val) { + switch (addr) { + case 0x04040000 ... 0x040FFFFF: rsp.Write(mem, regs, addr, val); break; + case 0x04100000 ... 0x041FFFFF: rdp.Write(addr, val); break; + case 0x04300000 ... 0x043FFFFF: mi.Write(regs, addr, val); break; + case 0x04400000 ... 0x044FFFFF: vi.Write(mi, regs, addr, val); break; + case 0x04500000 ... 0x045FFFFF: ai.Write(mem, regs, addr, val); break; + case 0x04600000 ... 0x046FFFFF: pi.Write(mem, regs, addr, val); break; + case 0x04700000 ... 0x047FFFFF: ri.Write(addr, val); break; + case 0x04800000 ... 0x048FFFFF: si.Write(mem, regs, addr, val); break; + default: util::panic("Unhandled mmio write at addr {:08X} with val {:08X}\n", addr, val); + } +} +} diff --git a/src/core/n64/core/MMIO.hpp b/src/core/n64/core/MMIO.hpp new file mode 100644 index 00000000..2f8fdff3 --- /dev/null +++ b/src/core/n64/core/MMIO.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +namespace natsukashii::n64::core { +struct Mem; +struct Registers; + +struct MMIO { + MMIO() = default; + VI vi; + MI mi; + + u32 Read(u32); + void Write(Mem&, Registers& regs, u32, u32); +}; +} diff --git a/src/core/n64/core/Mem.cpp b/src/core/n64/core/Mem.cpp index dbbb5021..bb92f79a 100644 --- a/src/core/n64/core/Mem.cpp +++ b/src/core/n64/core/Mem.cpp @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include namespace natsukashii::n64::core { Mem::Mem() { @@ -30,4 +33,87 @@ void Mem::LoadROM(const std::string& filename) { util::SwapN64Rom(size, rom.data()); memcpy(dmem, rom.data(), 0x1000); } + +template +inline bool MapVAddr(Registers& regs, TLBAccessType accessType, u32 vaddr, u32& paddr) { + paddr = vaddr & 0x1FFFFFFF; + if constexpr(!tlb) return true; + + switch(vaddr >> 29) { + case 0 ... 3: case 7: + return ProbeTLB(regs, accessType, s64(s32(vaddr)), paddr, nullptr); + case 4 ... 5: return true; + case 6: util::panic("Unimplemented virtual mapping in KSSEG! ({:08X})\n", vaddr); + default: + util::panic("Should never end up in default case in map_vaddr! ({:08X})\n", vaddr); + } +} + +template +T Mem::Read(Registers& regs, u32 vaddr, s64 pc) { + u32 paddr = vaddr; + if(!MapVAddr(regs, LOAD, vaddr, paddr)) { + HandleTLBException(regs, vaddr); + FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc); + } + + switch(paddr) { + case 0x00000000 ... 0x007FFFFF: return util::ReadAccess(rdram.data(), paddr & RDRAM_DSIZE); + case 0x04000000 ... 0x04000FFF: return util::ReadAccess(mmio.rsp.dmem, paddr & DMEM_DSIZE); + case 0x04001000 ... 0x04001FFF: return util::ReadAccess(mmio.rsp.imem, paddr & IMEM_DSIZE); + case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF: + case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: return mmio.Read(paddr); + case 0x10000000 ... 0x1FBFFFFF: return util::ReadAccess(cart.data(), paddr & romMask); + case 0x1FC00000 ... 0x1FC007BF: return util::ReadAccess(pifBootrom, paddr & PIF_BOOTROM_DSIZE); + case 0x1FC007C0 ... 0x1FC007FF: return util::ReadAccess(pifRam, paddr & PIF_RAM_DSIZE); + case 0x00800000 ... 0x03FFFFFF: case 0x04002000 ... 0x0403FFFF: + case 0x04200000 ... 0x042FFFFF: + case 0x04900000 ... 0x07FFFFFF: case 0x08000000 ... 0x0FFFFFFF: + case 0x80000000 ... 0xFFFFFFFF: case 0x1FC00800 ... 0x7FFFFFFF: return 0; + default: util::panic("Unimplemented {}-bit read at address {:08X} (PC = {:016X})\n", sizeof(T) * 8, paddr, regs.pc); + } +} + +template u8 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u16 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u32 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u64 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u8 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u16 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u32 Mem::Read(Registers& regs, u32 vaddr, s64 pc); +template u64 Mem::Read(Registers& regs, u32 vaddr, s64 pc); + +template +void Mem::Write(Registers& regs, u32 vaddr, T val, s64 pc) { + u32 paddr = vaddr; + if(!MapVAddr(regs, STORE, vaddr, paddr)) { + HandleTLBException(regs, vaddr); + FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, pc); + } + + switch(paddr) { + case 0x00000000 ... 0x007FFFFF: util::WriteAccess(rdram.data(), paddr & RDRAM_DSIZE, val); break; + case 0x04000000 ... 0x04000FFF: util::WriteAccess(mmio.rsp.dmem, paddr & DMEM_DSIZE, val); break; + case 0x04001000 ... 0x04001FFF: util::WriteAccess(mmio.rsp.imem, paddr & IMEM_DSIZE, val); break; + case 0x04040000 ... 0x040FFFFF: case 0x04100000 ... 0x041FFFFF: + case 0x04300000 ... 0x044FFFFF: case 0x04500000 ... 0x048FFFFF: mmio.Read(paddr); break; + case 0x10000000 ... 0x1FBFFFFF: util::WriteAccess(cart.data(), paddr & romMask, val); break; + case 0x1FC00000 ... 0x1FC007BF: util::WriteAccess(pifBootrom, paddr & PIF_BOOTROM_DSIZE, val); break; + case 0x1FC007C0 ... 0x1FC007FF: util::WriteAccess(pifRam, paddr & PIF_RAM_DSIZE, val); break; + case 0x00800000 ... 0x03FFFFFF: case 0x04002000 ... 0x0403FFFF: + case 0x04200000 ... 0x042FFFFF: + case 0x04900000 ... 0x07FFFFFF: case 0x08000000 ... 0x0FFFFFFF: + case 0x80000000 ... 0xFFFFFFFF: case 0x1FC00800 ... 0x7FFFFFFF: break; + default: util::panic("Unimplemented {}-bit write at address {:08X} with value {:0X} (PC = {:016X})\n", sizeof(T) * 8, paddr, val, regs.pc); + } +} + +template void Mem::Write(Registers& regs, u32 vaddr, u8 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u16 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u32 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u64 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u8 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u16 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u32 val, s64 pc); +template void Mem::Write(Registers& regs, u32 vaddr, u64 val, s64 pc); } \ No newline at end of file diff --git a/src/core/n64/core/Mem.hpp b/src/core/n64/core/Mem.hpp index 7e4b6c69..f6e39ff2 100644 --- a/src/core/n64/core/Mem.hpp +++ b/src/core/n64/core/Mem.hpp @@ -1,17 +1,25 @@ #pragma once #include #include +#include #include namespace natsukashii::n64::core { +struct Registers; struct Mem { ~Mem() = default; Mem(); void LoadROM(const std::string&); - [[nodiscard]] auto GetRDRAM() const -> const u8* { + [[nodiscard]] auto GetRDRAM() -> u8* { return rdram.data(); } + template + T Read(Registers&, u32, s64); + template + void Write(Registers&, u32, T, s64); private: + friend struct RSP; + MMIO mmio; std::vector cart, rdram, sram; u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{}, pifRam[PIF_RAM_SIZE]{}; u8 pifBootrom[PIF_BOOTROM_SIZE]{}; diff --git a/src/core/n64/core/RDP.cpp b/src/core/n64/core/RDP.cpp index bacad69e..a967366a 100644 --- a/src/core/n64/core/RDP.cpp +++ b/src/core/n64/core/RDP.cpp @@ -1 +1,113 @@ #include +#include +#include +#include <../../../frontend/ParallelRDPWrapper.hpp> +#include + +namespace natsukashii::n64::core { +static const int cmd_lens[64] = { + 2, 2, 2, 2, 2, 2, 2, 2, 8, 12, 24, 28, 24, 28, 40, 44, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 +}; + +u32 RDP::Read(u32 addr) { + switch(addr) { + case 0x0410000C: return dpc.status.raw; + default: util::panic("Unhandled DP Command Registers read (addr: {:08X})\n", addr); + } +} + +void RDP::Write(u32 addr, u32 val) { + switch(addr) { + case 0x0410000C: StatusWrite(val); break; + default: util::panic("Unhandled DP Command Registers read (addr: {:08X}, val: {:08X})\n", addr, val); + } +} + +void RDP::StatusWrite(u32 val) { + DPCStatusWrite temp{}; + temp.raw = val; + CLEAR_SET(dpc.status.xbusDmemDma, temp.clearXbusDmemDma, temp.setXbusDmemDma); + CLEAR_SET(dpc.status.freeze, temp.clearFreeze, false); // Setting it seems to break games? Avoid for now (TODO) + CLEAR_SET(dpc.status.flush, temp.clearFlush, temp.setFlush); +} + +void RDP::RunCommand(MI& mi, Registers& regs, RSP& rsp) { + static int remaining_cmds = 0; + dpc.status.freeze = true; + + const u32 current = dpc.current & 0xFFFFF8; + const u32 end = dpc.end & 0xFFFFF8; + + int len = end - current; + if(len <= 0) return; + + if(len + (remaining_cmds * 4) <= 0xFFFFF) { + if(dpc.status.xbusDmemDma) { + for(int i = 0; i < len; i += 4) { + u32 cmd = util::ReadAccess(rsp.dmem, current + i); + cmd_buf[remaining_cmds + (i >> 2)] = cmd; + } + } else { + if(end > 0x7FFFFF || current > 0x7FFFFF) { + return; + } + for(int i = 0; i < len; i += 4) { + u32 cmd = util::ReadAccess(rsp.dmem, current + i); + cmd_buf[remaining_cmds + (i >> 2)] = cmd; + } + } + + int word_len = (len >> 2) + remaining_cmds; + int buf_index = 0; + + bool processed_all = true; + + while(buf_index < word_len) { + u8 cmd = (cmd_buf[buf_index] >> 24) & 0x3F; + + int cmd_len = cmd_lens[cmd]; + if((buf_index + cmd_len) * 4 > len + (remaining_cmds * 4)) { + remaining_cmds = word_len - buf_index; + + u32 tmp[remaining_cmds]; + for(int i = 0; i < remaining_cmds; i++) { + tmp[i] = cmd_buf[buf_index + i]; + } + + for(int i = 0; i < remaining_cmds; i++) { + cmd_buf[buf_index + i] = tmp[i]; + } + + processed_all = false; + break; + } + + if(cmd >= 8) { + ParallelRdpEnqueueCommand(cmd_len, &cmd_buf[buf_index]); + } + + if (cmd == 0x29) { + OnFullSync(); + InterruptRaise(mi, regs, InterruptType::DP); + } + + buf_index += cmd_len; + } + + if(processed_all) { + remaining_cmds = 0; + } + + dpc.current = end; + dpc.status.freeze = false; + dpc.status.cbufReady = true; + } +} + +void RDP::OnFullSync() { + ParallelRdpOnFullSync(); +} +} diff --git a/src/core/n64/core/RDP.hpp b/src/core/n64/core/RDP.hpp index de7fd408..b86449bf 100644 --- a/src/core/n64/core/RDP.hpp +++ b/src/core/n64/core/RDP.hpp @@ -1,8 +1,62 @@ #pragma once +#include + namespace natsukashii::n64::core { -struct RDP { +struct RSP; +struct MI; +struct Registers; +union DPCStatusWrite { + u32 raw; + struct { + unsigned clearXbusDmemDma:1; + unsigned setXbusDmemDma:1; + unsigned clearFreeze:1; + unsigned setFreeze:1; + unsigned clearFlush:1; + unsigned setFlush:1; + unsigned clearTmem:1; + unsigned clearPipe:1; + unsigned clearCmd:1; + unsigned clearClock:1; + }; }; +union DPCStatus { + struct { + unsigned xbusDmemDma; + unsigned freeze; + unsigned flush; + unsigned startGclk; + unsigned tmemBusy; + unsigned pipeBusy; + unsigned cmdBusy; + unsigned cbufReady; + unsigned dmaBusy; + unsigned endValid; + unsigned startValid; + }; + u32 raw; +}; + +struct DPC { + DPCStatus status; + u32 start; + u32 current; + u32 end; +}; + +struct RDP { + DPC dpc{.status{.raw = 0x80}}; + u32 cmd_buf[0xFFFFF]{}; + + RDP() = default; + + u32 Read(u32 addr); + void Write(u32 addr, u32 val); + void StatusWrite(u32 val); + void RunCommand(MI& mi, Registers& regs, RSP& rsp); + void OnFullSync(); +}; } // natsukashii diff --git a/src/core/n64/core/RSP.cpp b/src/core/n64/core/RSP.cpp new file mode 100644 index 00000000..70294de7 --- /dev/null +++ b/src/core/n64/core/RSP.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include "Interrupt.hpp" + +namespace natsukashii::n64::core { +void RSP::StepRSP(MI& mi, Registers& regs, RDP& rdp) { + if(!spStatus.halt) { + gpr[0] = 0; + u32 instr = util::ReadAccess(imem, pc & IMEM_DSIZE); + oldPC = pc & 0xFFF; + pc = nextPC & 0xFFF; + nextPC += 4; + Exec(mi, regs, rdp, instr); + } +} + +u32 RSP::Read(u32 addr) { + switch (addr) { + case 0x04040000: return spDMASPAddr.raw & 0xFFFFF8; + case 0x04040004: return spDMADRAMAddr.raw & 0x1FF8; + case 0x04040008: return spDMARDLen.raw; + case 0x0404000C: return spDMAWRLen.raw; + case 0x04040010: return spStatus.raw; + case 0x04040018: return 0; + case 0x04080000: return pc & 0xFFF; + default: util::panic("Unimplemented SP register read %08X\n", addr); + } +} + +template +inline void DMA(SPDMALen len, RSP& rsp, u8* dst, u8* src) { + u32 length = len.len + 1; + + length = (length + 0x7) & ~0x7; + + u32 last_addr = rsp.spDMASPAddr.address + length; + if (last_addr > 0x1000) { + u32 overshoot = last_addr - 0x1000; + length -= overshoot; + } + + u32 dram_address = rsp.spDMADRAMAddr.address & 0xFFFFF8; + u32 mem_address = rsp.spDMASPAddr.address & 0x1FF8; + + for (int i = 0; i < len.count + 1; i++) { + if(isDRAMdest) { + memcpy(&dst[dram_address], &src[mem_address], length); + } else { + memcpy(&dst[mem_address], &src[dram_address], length); + } + + int skip = i == len.count ? 0 : len.skip; + + dram_address += (length + skip) & 0xFFFFF8; + mem_address += length; + } +} + +void RSP::Write(Mem& mem, Registers& regs, u32 addr, u32 value) { + MI& mi = mem.mmio.mi; + switch (addr) { + case 0x04040000: spDMASPAddr.raw = value & 0x1FF8; break; + case 0x04040004: spDMADRAMAddr.raw = value & 0xFFFFF8; break; + case 0x04040008: { + spDMARDLen.raw = value; + DMA(spDMARDLen, *this, spDMASPAddr.bank ? imem : dmem, mem.GetRDRAM()); + spDMARDLen.raw = 0xFF8 | (spDMARDLen.skip << 20); + } break; + case 0x0404000C: { + spDMAWRLen.raw = value; + DMA(spDMAWRLen, *this, mem.GetRDRAM(), spDMASPAddr.bank ? imem : dmem); + spDMAWRLen.raw = 0xFF8 | (spDMAWRLen.skip << 20); + } break; + case 0x04040010: { + SPStatusWrite write; + write.raw = value; + CLEAR_SET(spStatus.halt, write.clearHalt, write.setHalt); + CLEAR_SET(spStatus.broke, write.clearBroke, false); + if(write.clearIntr) InterruptLower(mi, regs, InterruptType::SP); + if(write.setIntr) InterruptRaise(mi, regs, InterruptType::SP); + CLEAR_SET(spStatus.singleStep, write.clearSstep, write.setSstep); + CLEAR_SET(spStatus.interruptOnBreak, write.clearIntrOnBreak, write.setIntrOnBreak); + CLEAR_SET(spStatus.signal0Set, write.clearSignal0, write.setSignal0); + CLEAR_SET(spStatus.signal1Set, write.clearSignal1, write.setSignal1); + CLEAR_SET(spStatus.signal2Set, write.clearSignal2, write.setSignal2); + CLEAR_SET(spStatus.signal3Set, write.clearSignal3, write.setSignal3); + CLEAR_SET(spStatus.signal4Set, write.clearSignal4, write.setSignal4); + CLEAR_SET(spStatus.signal5Set, write.clearSignal5, write.setSignal5); + CLEAR_SET(spStatus.signal6Set, write.clearSignal6, write.setSignal6); + CLEAR_SET(spStatus.signal7Set, write.clearSignal7, write.setSignal7); + } break; + case 0x04080000: + if(spStatus.halt) { + oldPC = pc; + pc = nextPC; + nextPC = value & 0xFFF; + } break; + default: util::panic("Unimplemented SP register write {:08X}, val: {:08X}\n", addr, value); + } +} + +} diff --git a/src/core/n64/core/RSP.hpp b/src/core/n64/core/RSP.hpp new file mode 100644 index 00000000..a83203f9 --- /dev/null +++ b/src/core/n64/core/RSP.hpp @@ -0,0 +1,196 @@ +#pragma once +#include +#include +#include + +namespace natsukashii::n64::core { +union SPStatus { + u32 raw; + struct { + unsigned halt:1; + unsigned broke:1; + unsigned dmaBusy:1; + unsigned dmaFull:1; + unsigned ioFull:1; + unsigned singleStep:1; + unsigned interruptOnBreak:1; + unsigned signal0Set:1; + unsigned signal1Set:1; + unsigned signal2Set:1; + unsigned signal3Set:1; + unsigned signal4Set:1; + unsigned signal5Set:1; + unsigned signal6Set:1; + unsigned signal7Set:1; + unsigned:17; + }; +}; + +union SPStatusWrite { + u32 raw; + struct { + unsigned clearHalt:1; + unsigned setHalt:1; + unsigned clearBroke:1; + unsigned clearIntr:1; + unsigned setIntr:1; + unsigned clearSstep:1; + unsigned setSstep:1; + unsigned clearIntrOnBreak:1; + unsigned setIntrOnBreak:1; + unsigned clearSignal0:1; + unsigned setSignal0:1; + unsigned clearSignal1:1; + unsigned setSignal1:1; + unsigned clearSignal2:1; + unsigned setSignal2:1; + unsigned clearSignal3:1; + unsigned setSignal3:1; + unsigned clearSignal4:1; + unsigned setSignal4:1; + unsigned clearSignal5:1; + unsigned setSignal5:1; + unsigned clearSignal6:1; + unsigned setSignal6:1; + unsigned clearSignal7:1; + unsigned setSignal7:1; + unsigned:7; + }; +}; + +union SPDMALen { + struct { + unsigned len:12; + unsigned count:8; + unsigned skip:12; + }; + u32 raw; +}; + +union SPDMASPAddr { + struct { + unsigned address:12; + unsigned bank:1; + unsigned: 19; + }; + u32 raw; +}; + +union SPDMADRAMAddr { + struct { + unsigned address:24; + unsigned:8; + }; + u32 raw; +}; + +union VPR { + s16 selement[8]; + u16 element[8]; + u8 byte[8]; + u32 word[4]; +}; + +struct Mem; +struct Registers; + +#define VT(x) (((x) >> 16) & 0x1F) +#define VS(x) (((x) >> 11) & 0x1F) +#define VD(x) (((x) >> 6) & 0x1F) +#define E(x) (((x) >> 21) & 0x1F) +#define DE(x) (((x) >> 11) & 0x1F) +#define CLEAR_SET(val, clear, set) do { \ + if(clear) (val) = 0; \ + if(set) (val) = 1; \ +} while(0) + +struct RSP { + RSP() = default; + void StepRSP(MI& mi, Registers& regs, RDP& rdp); + u32 Read(u32 addr); + void Write(Mem& mem, Registers& regs, u32 addr, u32 value); + void Exec(MI& mi, Registers& regs, RDP& rdp, u32 instr); + SPStatus spStatus{.raw = 1}; + u16 oldPC{}, pc{}, nextPC = 4; + SPDMASPAddr spDMASPAddr{}; + SPDMADRAMAddr spDMADRAMAddr{}; + SPDMALen spDMARDLen{}, spDMAWRLen{}; + u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{}; + VPR vpr[32]{}; + u32 gpr[32]{}; + u8 vce{}; + + struct { + VPR h{}, m{}, l{}; + } acc; + + struct { + VPR l{}, h{}; + } vcc, vco; + + bool semaphore = false; + + inline void SetPC(u16 val) { + pc = val; + nextPC = val += 4; + } + + inline u16 VCOasU16() { + u16 val = 0; + for(int i = 0; i < 8; i++) { + bool h = vco.h.element[7 - i] != 0; + bool l = vco.l.element[7 - i] != 0; + u32 mask = (l << i) | (h << (i + 8)); + val |= mask; + } + return val; + } + + inline u16 VCCasU16() { + u16 val = 0; + for(int i = 0; i < 8; i++) { + bool h = vcc.h.element[7 - i] != 0; + bool l = vcc.l.element[7 - i] != 0; + u32 mask = (l << i) | (h << (i + 8)); + val |= mask; + } + return val; + } +private: + void add(u32 instr); + void addi(u32 instr); + void and_(u32 instr); + void andi(u32 instr); + void cfc2(u32 instr); + void b(u32 instr, bool cond); + void lh(u32 instr); + void lw(u32 instr); + void lui(u32 instr); + void lqv(u32 instr); + void j(u32 instr); + void jal(u32 instr); + void jr(u32 instr); + void nor(u32 instr); + void or_(u32 instr); + void ori(u32 instr); + void sb(u32 instr); + void sh(u32 instr); + void sw(u32 instr); + void sqv(u32 instr); + void sllv(u32 instr); + void sll(u32 instr); + void vabs(u32 instr); + void vmov(u32 instr); + void veq(u32 instr); + void vne(u32 instr); + void vsar(u32 instr); + void mfc0(RDP& rdp, u32 instr); + void mtc0(MI& mi, Registers& regs, RDP& rdp, u32 instr); + + inline void branch(u16 address, bool cond) { + if(cond) { + nextPC = address & 0xFFF; + } + } +}; +} \ No newline at end of file diff --git a/src/core/n64/core/cpu/Registers.cpp b/src/core/n64/core/cpu/Registers.cpp index 2b45b8a1..8fe40cbf 100644 --- a/src/core/n64/core/cpu/Registers.cpp +++ b/src/core/n64/core/cpu/Registers.cpp @@ -1 +1,23 @@ #include + +namespace natsukashii::n64::core { +Registers::Registers() { + delaySlot = false; + prevDelaySlot = false; + memset(gpr, 0, 32*sizeof(s64)); + oldPC = (s64)0xFFFFFFFFA4000040; + pc = oldPC; + nextPC = pc + 4; + lo = 0; + hi = 0; + gpr[11] = (s64)0xFFFFFFFFA4000040; + gpr[20] = 0x0000000000000001; + gpr[22] = 0x000000000000003F; + gpr[29] = (s64)0xFFFFFFFFA4001FF0; +} + +void Registers::SetPC(s64 val) { + pc = val; + nextPC = pc + 4; +} +} diff --git a/src/core/n64/core/cpu/Registers.hpp b/src/core/n64/core/cpu/Registers.hpp index fbbd5231..578d6cc0 100644 --- a/src/core/n64/core/cpu/Registers.hpp +++ b/src/core/n64/core/cpu/Registers.hpp @@ -3,7 +3,21 @@ namespace natsukashii::n64::core { struct Registers { + Registers(); + void SetPC(s64); s64 gpr[32]; Cop0 cop0; + s64 oldPC, pc, nextPC; + s64 hi, lo; + bool LLBit; + bool prevDelaySlot, delaySlot; }; + +#define RD(x) (((x) >> 11) & 0x1F) +#define RT(x) (((x) >> 16) & 0x1F) +#define RS(x) (((x) >> 21) & 0x1F) +#define FD(x) (((x) >> 6) & 0x1F) +#define FT(x) RT(x) +#define FS(x) RD(x) +#define BASE(x) RS(x) } diff --git a/src/core/n64/core/cpu/registers/Cop0.cpp b/src/core/n64/core/cpu/registers/Cop0.cpp index 26d89d05..09cc64cd 100644 --- a/src/core/n64/core/cpu/registers/Cop0.cpp +++ b/src/core/n64/core/cpu/registers/Cop0.cpp @@ -1,17 +1,195 @@ #include +#include +#include +#include namespace natsukashii::n64::core { Cop0::Cop0() { - + cause.raw = 0xB000007C; + random = 0x0000001F; + status.raw = 0x241000E0; + wired = 64; + index = 64; + PRId = 0x00000B00; + Config = 0x7006E463; + EPC = 0xFFFFFFFFFFFFFFFF; + ErrorEPC = 0xFFFFFFFFFFFFFFFF; } template -T Cop0::GetReg(u8 index) { - +T Cop0::GetReg(u8 addr) { + switch(addr) { + case 0: return index & 0x8000001F; + case 1: return random & 0x1F; + case 2: return entryLo0.raw & 0x3FFFFFFF; + case 3: return entryLo1.raw & 0x3FFFFFFF; + case 4: return context.raw; + case 5: return pageMask.raw; + case 6: return wired & 0x3F; + case 7: return r7; + case 8: return badVaddr; + case 9: return count >> 1; + case 10: return entryHi.raw & 0xFFFFFFFFFFFFE0FF; + case 11: return compare; + case 12: return status.raw & STATUS_MASK; + case 13: return cause.raw; + case 14: return EPC; + case 15: return PRId & 0xFFFF; + case 16: return Config; + case 17: return LLAddr; + case 18: return WatchLo; + case 19: return WatchHi; + case 20: return xcontext.raw & 0xFFFFFFF0; + case 21: return r21; + case 22: return r22; + case 23: return r23; + case 24: return r24; + case 25: return r25; + case 26: return ParityError; + case 27: return CacheError; + case 28: return TagLo & 0xFFFFFFF; + case 29: return TagHi; + case 30: return ErrorEPC; + case 31: return r31; + default: + util::panic("Unsupported word read from COP0 register {}\n", index); + } } template -void Cop0::SetReg(u8 index, T val) { +void Cop0::SetReg(u8 addr, T value) { + switch(addr) { + case 0: index = value & 0x8000001F; break; + case 1: random = value & 0x1F; break; + case 2: entryLo0.raw = value & 0x3FFFFFFF; break; + case 3: entryLo1.raw = value & 0x3FFFFFFF; break; + case 4: context.raw = value; break; + case 5: pageMask.raw = value; break; + case 6: wired = value & 0x3F; break; + case 7: r7 = value; break; + case 9: count = value << 1; break; + case 10: entryHi.raw = value & 0xFFFFFFFFFFFFE0FF; break; + case 11: { + cause.ip7 = 0; + compare = value; + } break; + case 12: status.raw = value & STATUS_MASK; break; + case 13: { + Cop0Cause tmp{}; + tmp.raw = value; + cause.ip0 = tmp.ip0; + cause.ip1 = tmp.ip1; + } break; + case 14: EPC = value; break; + case 15: PRId = value & 0xFFFF; break; + case 16: Config = value; break; + case 17: LLAddr = value; break; + case 18: WatchLo = value; break; + case 19: WatchHi = value; break; + case 21: r21 = value; break; + case 22: r22 = value; break; + case 23: r23 = value; break; + case 24: r24 = value; break; + case 25: r25 = value; break; + case 26: ParityError = value; break; + case 27: CacheError = value; break; + case 28: TagLo = value & 0xFFFFFFF; break; + case 29: TagHi = value; break; + case 30: ErrorEPC = value; break; + case 31: r31 = value; break; + default: + util::panic("Unsupported word write to COP0 register {}\n", index); + } +} +#define vpn(addr, PageMask) (((((addr) & 0xFFFFFFFFFF) | (((addr) >> 22) & 0x30000000000)) & ~((PageMask) | 0x1FFF))) + +TLBEntry* TLBTryMatch(Registers& regs, u32 vaddr, int* match) { + for(int i = 0; i < 32; i++) { + TLBEntry *entry = ®s.cop0.tlb[i]; + u64 entry_vpn = vpn(entry->entryHi.raw, entry->pageMask.raw); + u64 vaddr_vpn = vpn(vaddr, entry->pageMask.raw); + + bool vpn_match = entry_vpn == vaddr_vpn; + bool asid_match = entry->global || (regs.cop0.entryHi.asid == entry->entryHi.asid); + + if(vpn_match && asid_match) { + if(match) { + *match = i; + } + return entry; + } + } + + return nullptr; +} + +bool ProbeTLB(Registers& regs, TLBAccessType access_type, u32 vaddr, u32& paddr, int* match) { + TLBEntry* entry = TLBTryMatch(regs, vaddr, match); + if(!entry) { + regs.cop0.tlbError = MISS; + return false; + } + + u32 mask = (entry->pageMask.mask << 12) | 0xFFF; + u32 odd = vaddr & (mask + 1); + u32 pfn; + + if(!odd) { + if(!(entry->entryLo0.v)) { + regs.cop0.tlbError = INVALID; + return false; + } + + if(access_type == STORE && !(entry->entryLo0.d)) { + regs.cop0.tlbError = MODIFICATION; + return false; + } + + pfn = entry->entryLo0.pfn; + } else { + if(!(entry->entryLo1.v)) { + regs.cop0.tlbError = INVALID; + return false; + } + + if(access_type == STORE && !(entry->entryLo1.d)) { + regs.cop0.tlbError = MODIFICATION; + return false; + } + + pfn = entry->entryLo1.pfn; + } + + paddr = (pfn << 12) | (vaddr & mask); + + return true; +} + +void HandleTLBException(Registers& regs, u64 vaddr) { + u64 vpn2 = (vaddr >> 13) & 0x7FFFF; + u64 xvpn2 = (vaddr >> 13) & 0x7FFFFFF; + regs.cop0.badVaddr = vaddr; + regs.cop0.context.badvpn2 = vpn2; + regs.cop0.xcontext.badvpn2 = xvpn2; + regs.cop0.xcontext.r = (vaddr >> 62) & 3; + regs.cop0.entryHi.vpn2 = xvpn2; + regs.cop0.entryHi.r = (vaddr >> 62) & 3; +} + +ExceptionCode GetTLBExceptionCode(TLBError error, TLBAccessType accessType) { + switch(error) { + case NONE: util::panic("Getting TLB exception with error NONE\n"); + case INVALID: case MISS: + return accessType == LOAD ? + ExceptionCode::TLBLoad : ExceptionCode::TLBStore; + case MODIFICATION: + return ExceptionCode::TLBModification; + case DISALLOWED_ADDRESS: + return accessType == LOAD ? + ExceptionCode::AddressErrorLoad : ExceptionCode::AddressErrorStore; + default: + util::panic("Getting TLB exception for unknown error code! ({})\n", error); + } } } \ No newline at end of file diff --git a/src/core/n64/core/cpu/registers/Cop0.hpp b/src/core/n64/core/cpu/registers/Cop0.hpp index 1bd0cf3b..ae969dac 100644 --- a/src/core/n64/core/cpu/registers/Cop0.hpp +++ b/src/core/n64/core/cpu/registers/Cop0.hpp @@ -71,23 +71,23 @@ union Cop0Status { union EntryLo { u32 raw; struct { - unsigned g: 1; - unsigned v: 1; - unsigned d: 1; - unsigned c: 3; - unsigned pfn: 20; - unsigned: 6; + unsigned g:1; + unsigned v:1; + unsigned d:1; + unsigned c:3; + unsigned pfn:20; + unsigned:6; }; }; union EntryHi { u64 raw; struct { - u64 asid: 8; - u64: 5; - u64 vpn2: 27; - u64 fill: 22; - u64 r: 2; + u64 asid:8; + u64:5; + u64 vpn2:27; + u64 fill:22; + u64 r:2; } __attribute__((__packed__)); }; @@ -101,9 +101,21 @@ union PageMask { }; struct TLBEntry { - EntryLo entryLo0, entryLo1; + union { + u32 raw; + struct { + unsigned:1; + unsigned v:1; + unsigned d:1; + unsigned c:3; + unsigned pfn:20; + unsigned:6; + }; + } entryLo0, entryLo1; EntryHi entryHi; PageMask pageMask; + + bool global; }; enum TLBError : u8 { @@ -118,7 +130,7 @@ enum TLBAccessType { LOAD, STORE }; -union Context { +union Cop0Context { u64 raw; struct { u64: 4; @@ -127,7 +139,7 @@ union Context { }; }; -union XContext { +union Cop0XContext { u64 raw; struct { u64: 4; @@ -141,28 +153,36 @@ struct Cop0 { Cop0(); template - T GetReg(u8 index); + T GetReg(u8); template - void SetReg(u8 index, T val); + void SetReg(u8, T); - PageMask pageMask; - EntryHi entryHi; - EntryLo entryLo0, entryLo1; + PageMask pageMask{}; + EntryHi entryHi{}; + EntryLo entryLo0{}, entryLo1{}; u32 index, random; - Context context; - u32 wired, r7; - u64 badVaddr, count; - u32 compare; - Cop0Status status; - Cop0Cause cause; + Cop0Context context{}; + u32 wired, r7{}; + u64 badVaddr{}, count{}; + u32 compare{}; + Cop0Status status{}; + Cop0Cause cause{}; u64 EPC; - u32 PRId, Config, LLAddr, WatchLo, WatchHi; - XContext xcontext; - u32 r21, r22, r23, r24, r25, ParityError, CacheError, TagLo, TagHi; + u32 PRId, Config, LLAddr{}, WatchLo{}, WatchHi{}; + Cop0XContext xcontext{}; + u32 r21{}, r22{}, r23{}, r24{}, r25{}, ParityError{}, CacheError{}, TagLo{}, TagHi{}; u64 ErrorEPC; - u32 r31; - TLBEntry tlb[32]; - TLBError tlbError; + u32 r31{}; + TLBEntry tlb[32]{}; + TLBError tlbError = NONE; }; + +struct Registers; +enum class ExceptionCode : u8; + +TLBEntry* TLBTryMatch(Registers& regs, u32 vaddr, int* match); +bool ProbeTLB(Registers& regs, TLBAccessType access_type, u32 vaddr, u32& paddr, int* match); +void HandleTLBException(Registers& regs, u64 vaddr); +ExceptionCode GetTLBExceptionCode(TLBError error, TLBAccessType access_type); } \ No newline at end of file diff --git a/src/core/n64/core/rsp/decode.cpp b/src/core/n64/core/rsp/decode.cpp new file mode 100644 index 00000000..021aaaba --- /dev/null +++ b/src/core/n64/core/rsp/decode.cpp @@ -0,0 +1,105 @@ +#include +#include + +namespace natsukashii::n64::core { +inline void special(RSP& rsp, u32 instr) { + u8 mask = instr & 0x3f; + switch(mask) { + case 0x00: rsp_sll(rsp, instr); break; + case 0x04: rsp_sllv(rsp, instr); break; + case 0x08: rsp_jr(rsp, instr); break; + case 0x0C: + case 0x0D: + rsp.spStatus.halt = true; + rsp.spStatus.broke = true; + break; + case 0x20: case 0x21: + rsp_add(rsp, instr); + break; + case 0x24: rsp_and_(rsp, instr); break; + case 0x25: rsp_or_(rsp, instr); break; + case 0x27: rsp_nor(rsp, instr); break; + default: util::panic("Unhandled RSP special instruction %d %d\n", (mask >> 3) & 7, mask & 7); + } +} + +inline void regimm(RSP& rsp, u32 instr) { + u8 mask = ((instr >> 16) & 0x1F); + switch(mask) { + case 0x00: rsp_b(rsp, instr, (s32)rsp.gpr[RS(instr)] < 0); break; + case 0x01: rsp_b(rsp, instr, (s32)rsp.gpr[RS(instr)] >= 0); break; + default: util::panic("Unhandled RSP regimm instruction %d %d\n", (mask >> 3) & 3, mask & 7); + } +} + +inline void lwc2(RSP& rsp, u32 instr) { + u8 mask = (instr >> 11) & 0x1F; + switch(mask) { + case 0x04: rsp_lqv(rsp, instr); break; + default: util::panic("Unhandled RSP LWC2 %d %d\n", (mask >> 3) & 3, mask & 7); + } +} + +inline void swc2(RSP& rsp, u32 instr) { + u8 mask = (instr >> 11) & 0x1F; + switch(mask) { + case 0x04: rsp_sqv(rsp, instr); break; + default: util::panic("Unhandled RSP SWC2 %d %d\n", (mask >> 3) & 3, mask & 7); + } +} + +inline void cop2(RSP& rsp, u32 instr) { + u8 mask = instr & 0x3F; + u8 mask_sub = (instr >> 21) & 0x1F; + switch(mask) { + case 0x00: + switch(mask_sub) { + case 0x02: rsp_cfc2(rsp, instr); break; + default: logfatal("Unhandled RSP COP2 sub %d %d\n", (mask_sub >> 3) & 3, mask_sub & 3); + } + break; + case 0x13: rsp_vabs(rsp, instr); break; + case 0x1D: rsp_vsar(rsp, instr); break; + case 0x21: rsp_veq(rsp, instr); break; + case 0x22: rsp_vne(rsp, instr); break; + case 0x33: rsp_vmov(rsp, instr); break; + default: util::panic("Unhandled RSP COP2 %d %d\n", (mask >> 3) & 7, mask & 7); + } +} + +inline void cop0(MI& mi, Registers& regs, RSP& rsp, RDP& rdp, u32 instr) { + u8 mask = (instr >> 21) & 0x1F; + switch(mask) { + case 0x00: rsp_mfc0(rsp, rdp, instr); break; + case 0x04: rsp_mtc0(mi, regs, rsp, rdp, instr); break; + default: util::panic("Unhandled RSP COP0 %d %d\n", (mask >> 3) & 3, mask & 7); + } +} + +void RSP::Exec(MI &mi, Registers ®s, RDP &rdp, u32 instr) { + u8 mask = (instr >> 26) & 0x3F; + switch(mask) { + case 0x00: special(*this, instr); break; + case 0x01: regimm(*this, instr); break; + case 0x02: rsp_j(instr); break; + case 0x03: rsp_jal(instr); break; + case 0x04: rsp_b(instr, gpr[RT(instr)] == gpr[RS(instr)]); break; + case 0x05: rsp_b(instr, gpr[RT(instr)] != gpr[RS(instr)]); break; + case 0x07: rsp_b(instr, gpr[RS(instr)] > 0); break; + case 0x08: case 0x09: rsp_addi(instr); break; + case 0x0C: rsp_andi(instr); break; + case 0x0D: rsp_ori(instr); break; + case 0x0F: rsp_lui(instr); break; + case 0x10: cop0(mi, regs, *this, rdp, instr); break; + case 0x12: cop2(*this, instr); break; + case 0x21: rsp_lh(instr); break; + case 0x23: rsp_lw(instr); break; + case 0x28: rsp_sb(instr); break; + case 0x29: rsp_sh(instr); break; + case 0x2B: rsp_sw(instr); break; + case 0x32: lwc2(*this, instr); break; + case 0x3A: swc2(*this, instr); break; + default: util::panic("Unhandled RSP instruction %d %d\n", (mask >> 3) & 7, mask & 7); + } +} +} \ No newline at end of file diff --git a/src/core/n64/core/rsp/instructions.cpp b/src/core/n64/core/rsp/instructions.cpp new file mode 100644 index 00000000..cb3699d7 --- /dev/null +++ b/src/core/n64/core/rsp/instructions.cpp @@ -0,0 +1,209 @@ +#include +#include +#include + +namespace natsukashii::n64::core { + +#define ELEMENT_INDEX(i) (7 - (i)) +#define BYTE_INDEX(i) (15 - (i)) + +inline bool AcquireSemaphore(RSP& rsp) { + if(rsp.semaphore) { + return true; + } else { + rsp.semaphore = true; + return false; + } +} + +inline void ReleaseSemaphore(RSP& rsp) { + rsp.semaphore = false; +} + +inline u32 GetCop0Reg(RSP& rsp, RDP& rdp, u8 index) { + switch(index) { + case 4: return rsp.spStatus.raw; + case 5: return rsp.spStatus.dmaFull; + case 6: return 0; + case 7: return AcquireSemaphore(rsp); + case 11: return rdp.dpc.status.raw; + default: util::panic("Unhandled RSP COP0 register read at index {}\n", index); + } +} + +inline void SetCop0Reg(MI& mi, Registers& regs, RSP& rsp, RDP& rdp, u8 index, u32 val) { + switch(index) { + case 0: rsp.spDMASPAddr.raw = val; break; + case 1: rsp.spDMADRAMAddr.raw = val; break; + case 2: rsp.spDMARDLen.raw = val; break; + case 3: rsp.spDMAWRLen.raw = val; break; + case 4: rsp.spStatus.raw = val; break; + case 7: + if(val == 0) { + ReleaseSemaphore(rsp); + } + break; + case 8: + rdp.dpc.start = val & 0xFFFFF8; + rdp.dpc.current = rdp.dpc.start; + break; + case 9: + rdp.dpc.end = val & 0xFFFFF8; + rdp.RunCommand(mi, regs, rdp, rsp); + break; + case 11: rdp.StatusWrite(val); break; + default: util::panic("Unhandled RSP COP0 register write at index {}\n", index); + } +} + +inline VPR Broadcast(VPR vt, int l0, int l1, int l2, int l3, int l4, int l5, int l6, int l7) { + VPR vte{}; + vte.element[ELEMENT_INDEX(0)] = vt.element[l0]; + vte.element[ELEMENT_INDEX(1)] = vt.element[l1]; + vte.element[ELEMENT_INDEX(2)] = vt.element[l2]; + vte.element[ELEMENT_INDEX(3)] = vt.element[l3]; + vte.element[ELEMENT_INDEX(4)] = vt.element[l4]; + vte.element[ELEMENT_INDEX(5)] = vt.element[l5]; + vte.element[ELEMENT_INDEX(6)] = vt.element[l6]; + vte.element[ELEMENT_INDEX(7)] = vt.element[l7]; + return vte; +} + +inline VPR GetVTE(VPR vt, u8 e) { + VPR vte{}; + switch(e & 0xf) { + case 0 ... 1: return vt; + case 2 ... 3: + vte = Broadcast(vt, e - 2, e - 2, e, e, e + 2, e + 2, e + 4, e + 4); + break; + case 4 ... 7: + vte = Broadcast(vt, e - 4, e - 4, e - 4, e - 4, e, e, e, e); + break; + case 8 ... 15: { + int index = e - 8; + for (u16& i : vte.element) { + i = vt.element[index]; + } + } break; + } + return vte; +} + +void RSP::add(u32 instr) { + +} + +void RSP::addi(u32 instr) { + +} + +void RSP::and_(u32 instr) { + +} + +void RSP::andi(u32 instr) { + +} + +void RSP::cfc2(u32 instr) { + +} + +void RSP::b(u32 instr, bool cond) { + s32 address = ((s32)((s16)(instr & 0xFFFF) << 2)) + pc; + branch(address, cond); +} + +void RSP::lh(u32 instr) { + +} + +void RSP::lw(u32 instr) { + +} + +void RSP::lui(u32 instr) { + +} + +void RSP::lqv(u32 instr) { + +} + +void RSP::j(u32 instr) { + +} + +void RSP::jal(u32 instr) { + +} + +void RSP::jr(u32 instr) { + +} + +void RSP::nor(u32 instr) { + +} + +void RSP::or_(u32 instr) { + +} + +void RSP::ori(u32 instr) { + +} + +void RSP::sb(u32 instr) { + +} + +void RSP::sh(u32 instr) { + +} + +void RSP::sw(u32 instr) { + +} + +void RSP::sqv(u32 instr) { + +} + +void RSP::sllv(u32 instr) { + u8 sa = gpr[RS(instr)] & 0x1F; + gpr[RD(instr)] = gpr[RT(instr)] << sa; +} + +void RSP::sll(u32 instr) { + +} + +void RSP::vabs(u32 instr) { + +} + +void RSP::vmov(u32 instr) { + +} + +void RSP::veq(u32 instr) { + +} + +void RSP::vne(u32 instr) { + +} + +void RSP::vsar(u32 instr) { + +} + +void RSP::mfc0(RDP& rdp, u32 instr) { + gpr[RT(instr)] = GetCop0Reg(*this, rdp, RD(instr)); +} + +void RSP::mtc0(MI& mi, Registers& regs, RDP& rdp, u32 instr) { + SetCop0Reg(mi, regs, *this, rdp, RD(instr), gpr[RT(instr)]); +} +} \ No newline at end of file diff --git a/src/core/util.hpp b/src/core/util.hpp index a407c8bd..f3301c29 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -59,7 +59,7 @@ auto GetSwapFunc(T num) -> T { template inline T ReadAccess(u8* data, u32 index) { - static_assert(sizeof(T) != 2 && sizeof(T) != 4 && sizeof(T) != 8); + static_assert(sizeof(T) != 2 || sizeof(T) != 4 || sizeof(T) != 8); T result = 0; memcpy(&result, &data[index], sizeof(T)); return GetSwapFunc(result); @@ -67,7 +67,7 @@ inline T ReadAccess(u8* data, u32 index) { template inline void WriteAccess(u8* data, u32 index, T val) { - static_assert(sizeof(T) != 2 && sizeof(T) != 4 && sizeof(T) != 8); + static_assert(sizeof(T) != 2 || sizeof(T) != 4 || sizeof(T) != 8); T temp = GetSwapFunc(val); memcpy(&data[index], &temp, sizeof(T)); } diff --git a/src/frontend/sdl/ParallelRDPWrapper.cpp b/src/frontend/ParallelRDPWrapper.cpp similarity index 89% rename from src/frontend/sdl/ParallelRDPWrapper.cpp rename to src/frontend/ParallelRDPWrapper.cpp index 36532479..6ef5313f 100644 --- a/src/frontend/sdl/ParallelRDPWrapper.cpp +++ b/src/frontend/ParallelRDPWrapper.cpp @@ -1,13 +1,14 @@ -#include -#include +#include "ParallelRDPWrapper.hpp" +#include "n64/core/RDP.hpp" #include -#include +#include "parallel-rdp-standalone/parallel-rdp/rdp_device.hpp" #include #include -#include +#include "util.hpp" -using namespace natsukashii; using namespace natsukashii::n64; +using namespace natsukashii::core; +using namespace natsukashii::util; using namespace Vulkan; using namespace RDP; using std::unique_ptr; @@ -64,7 +65,7 @@ void SetFramerateUnlocked(bool unlocked) { } } -class SDLWSIPlatform : public Vulkan::WSIPlatform { +class SDLWSIPlatform final : public Vulkan::WSIPlatform { public: SDLWSIPlatform() = default; @@ -73,7 +74,7 @@ public: unsigned int num_extensions = 64; if (!SDL_Vulkan_GetInstanceExtensions(window, &num_extensions, extensions)) { - util::panic("SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError()); + panic("SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError()); } auto vec = std::vector(); @@ -87,7 +88,7 @@ public: VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice gpu) override { VkSurfaceKHR vk_surface; if (!SDL_Vulkan_CreateSurface(window, instance, &vk_surface)) { - util::panic("Failed to create Vulkan window surface: %s", SDL_GetError()); + panic("Failed to create Vulkan window surface: %s", SDL_GetError()); } return vk_surface; } @@ -105,26 +106,27 @@ public: } void poll_input() override { - SDL_Event e; - while(SDL_PollEvent(&e)) { - - } + g_Core->PollInputs(windowID); } void event_frame_tick(double frame, double elapsed) override { - //n64_render_screen(); + } }; Program* fullscreen_quad_program; -void LoadParallelRDP(const u8* rdram) { +void LoadParallelRDP(Platform platform, const u8* rdram) { wsi = new WSI(); wsi->set_backbuffer_srgb(false); - wsi->set_platform(new SDLWSIPlatform()); + if (platform == Platform::SDL) { + wsi->set_platform(new SDLWSIPlatform()); + } else { + panic("WSI Qt platform not yet implemented!\n"); + } Context::SystemHandles handles; if (!wsi->init(1, handles)) { - util::panic("Failed to initialize WSI!"); + panic("Failed to initialize WSI!"); } ResourceLayout vertLayout; @@ -142,9 +144,9 @@ void LoadParallelRDP(const u8* rdram) { fragLayout.sets[0].fp_mask = 1; fragLayout.sets[0].array_size[0] = 1; - u32* fullscreenQuadVert, *fullscreenQuadFrag; - util::ReadFileBinary("external/vert.spv", fullscreenQuadVert); - util::ReadFileBinary("external/frag.spv", fullscreenQuadFrag); + u32* fullscreenQuadVert = nullptr, *fullscreenQuadFrag = nullptr; + ReadFileBinary("external/vert.spv", fullscreenQuadVert); + ReadFileBinary("external/frag.spv", fullscreenQuadFrag); fullscreen_quad_program = wsi->get_device().request_program(fullscreenQuadVert, sizeof(fullscreenQuadVert), fullscreenQuadFrag, sizeof(fullscreenQuadFrag), &vertLayout, &fragLayout); @@ -164,7 +166,7 @@ void LoadParallelRDP(const u8* rdram) { offset, 8 * 1024 * 1024, 4 * 1024 * 1024, flags); if (!command_processor->device_is_supported()) { - util::panic("This device probably does not support 8/16-bit storage. Make sure you're using up-to-date drivers!"); + panic("This device probably does not support 8/16-bit storage. Make sure you're using up-to-date drivers!"); } } @@ -241,7 +243,7 @@ void UpdateScreen(Util::IntrusivePtr image) { wsi->end_frame(); } -void UpdateScreenParallelRdp(const n64::core::VI& vi) { +void UpdateScreenParallelRdp(const core::VI& vi) { command_processor->set_vi_register(VIRegister::Control, vi.status.raw); command_processor->set_vi_register(VIRegister::Origin, vi.origin); command_processor->set_vi_register(VIRegister::Width, vi.width); diff --git a/src/frontend/sdl/ParallelRDPWrapper.hpp b/src/frontend/ParallelRDPWrapper.hpp similarity index 57% rename from src/frontend/sdl/ParallelRDPWrapper.hpp rename to src/frontend/ParallelRDPWrapper.hpp index d6a11322..9574aed8 100644 --- a/src/frontend/sdl/ParallelRDPWrapper.hpp +++ b/src/frontend/ParallelRDPWrapper.hpp @@ -1,10 +1,19 @@ #pragma once -#include -#include +#include "n64/Core.hpp" +#include "parallel-rdp-standalone/vulkan/wsi.hpp" #include -#include +#include "n64/core/mmio/VI.hpp" +#include "BaseCore.hpp" + +enum class Platform : bool { + SDL, Qt +}; + +using namespace natsukashii::n64; static SDL_Window* window; +static u32 windowID; +static std::unique_ptr g_Core; VkQueue GetGraphicsQueue(); VkInstance GetVkInstance(); VkPhysicalDevice GetVkPhysicalDevice(); @@ -13,8 +22,8 @@ uint32_t GetVkGraphicsQueueFamily(); VkFormat GetVkFormat(); VkCommandBuffer GetVkCommandBuffer(); void SubmitRequestedVkCommandBuffer(); -void LoadParallelRDP(const u8* rdram); -void UpdateScreenParallelRdp(natsukashii::n64::core::VI& vi); +void LoadParallelRDP(Platform platform, const u8* rdram); +void UpdateScreenParallelRdp(core::VI& vi); void ParallelRdpEnqueueCommand(int command_length, u32* buffer); void ParallelRdpOnFullSync(); void UpdateScreenParallelRdpNoGame(); diff --git a/src/frontend/qt/CMakeLists.txt b/src/frontend/qt/CMakeLists.txt index b158a30d..858534de 100644 --- a/src/frontend/qt/CMakeLists.txt +++ b/src/frontend/qt/CMakeLists.txt @@ -9,7 +9,7 @@ set(CMAKE_AUTOUIC ON) find_package(Qt5 COMPONENTS Widgets REQUIRED) -add_executable(natsukashii-qt Frontend.hpp Frontend.cpp main.cpp) +add_executable(natsukashii-qt Frontend.hpp Frontend.cpp main.cpp ../ParallelRDPWrapper.cpp ../ParallelRDPWrapper.hpp) target_include_directories(natsukashii-qt PRIVATE . ../../core ../../core/gb ../../core/n64) target_link_libraries(natsukashii-qt PRIVATE cores Qt5::Widgets) diff --git a/src/frontend/sdl/CMakeLists.txt b/src/frontend/sdl/CMakeLists.txt index 0a491789..9c550106 100644 --- a/src/frontend/sdl/CMakeLists.txt +++ b/src/frontend/sdl/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(SDL2 REQUIRED) -add_executable(natsukashii-sdl Frontend.cpp Frontend.hpp main.cpp ParallelRDPWrapper.cpp ParallelRDPWrapper.hpp) +add_executable(natsukashii-sdl Frontend.cpp Frontend.hpp main.cpp ../ParallelRDPWrapper.cpp ../ParallelRDPWrapper.hpp) include(FetchContent) FetchContent_Declare( diff --git a/src/frontend/sdl/Frontend.cpp b/src/frontend/sdl/Frontend.cpp index 49a18648..35117a41 100644 --- a/src/frontend/sdl/Frontend.cpp +++ b/src/frontend/sdl/Frontend.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include "../ParallelRDPWrapper.hpp" namespace natsukashii::frontend { using namespace natsukashii; @@ -19,24 +19,23 @@ App::App(const std::string& rom, const std::string& selectedCore) { window = SDL_CreateWindow("natukashii", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_RESIZABLE); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); SDL_RenderSetLogicalSize(renderer, 160, 144); - id = SDL_GetWindowID(window); - core = std::make_unique(rom); + windowID = SDL_GetWindowID(window); + g_Core = std::make_unique(rom); } else if(selectedCore == "n64") { window = SDL_CreateWindow("natukashii", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN); - id = SDL_GetWindowID(window); + windowID = SDL_GetWindowID(window); if(volkInitialize() != VK_SUCCESS) { util::panic("Failed to initialize Volk\n"); } - core = std::make_unique(rom); + g_Core = std::make_unique(Platform::SDL, rom); } else { util::panic("Unimplemented core!"); } } void App::Run() { - while(!core->ShouldQuit()) { - core->Run(); - core->PollInputs(id); + while(!g_Core->ShouldQuit()) { + g_Core->Run(); } } } diff --git a/src/frontend/sdl/Frontend.hpp b/src/frontend/sdl/Frontend.hpp index 844ea6b3..e8442219 100644 --- a/src/frontend/sdl/Frontend.hpp +++ b/src/frontend/sdl/Frontend.hpp @@ -16,7 +16,5 @@ struct App { void Run(); private: SDL_Renderer *renderer = nullptr; - u32 id; - std::unique_ptr core; }; }