Continue integrating n64 core

This commit is contained in:
CocoSimone
2022-06-30 21:07:58 +02:00
parent b2beeaf2a5
commit 474e30da73
28 changed files with 1381 additions and 85 deletions

View File

@@ -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)

View File

@@ -3,9 +3,9 @@
#include <ParallelRDPWrapper.hpp>
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() {

View File

@@ -4,11 +4,13 @@
#include <n64/core/Mem.hpp>
#include <string>
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:

View File

@@ -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)

View File

@@ -1 +1,102 @@
#include <Cpu.hpp>
#include <MI.hpp>
#include <Interrupt.hpp>
#include <util.hpp>
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;
}
}

View File

@@ -1,8 +1,32 @@
#pragma once
#include <n64/core/cpu/Registers.hpp>
#include <n64/core/Mem.hpp>
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);
}

View File

@@ -0,0 +1,34 @@
#include <MMIO.hpp>
#include <util.hpp>
#include <n64/core/Mem.hpp>
#include <n64/core/cpu/Registers.hpp>
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);
}
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include <mmio/VI.hpp>
#include <mmio/MI.hpp>
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);
};
}

View File

@@ -1,6 +1,9 @@
#include <Mem.hpp>
#include <fstream>
#include <util.hpp>
#include <n64/core/cpu/Registers.hpp>
#include <Cop0.hpp>
#include <n64/core/Cpu.hpp>
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 <bool tlb>
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 <class T, bool tlb>
T Mem::Read(Registers& regs, u32 vaddr, s64 pc) {
u32 paddr = vaddr;
if(!MapVAddr<tlb>(regs, LOAD, vaddr, paddr)) {
HandleTLBException(regs, vaddr);
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, pc);
}
switch(paddr) {
case 0x00000000 ... 0x007FFFFF: return util::ReadAccess<T>(rdram.data(), paddr & RDRAM_DSIZE);
case 0x04000000 ... 0x04000FFF: return util::ReadAccess<T>(mmio.rsp.dmem, paddr & DMEM_DSIZE);
case 0x04001000 ... 0x04001FFF: return util::ReadAccess<T>(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<T>(cart.data(), paddr & romMask);
case 0x1FC00000 ... 0x1FC007BF: return util::ReadAccess<T>(pifBootrom, paddr & PIF_BOOTROM_DSIZE);
case 0x1FC007C0 ... 0x1FC007FF: return util::ReadAccess<T>(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<u8, true>(Registers& regs, u32 vaddr, s64 pc);
template u16 Mem::Read<u16, true>(Registers& regs, u32 vaddr, s64 pc);
template u32 Mem::Read<u32, true>(Registers& regs, u32 vaddr, s64 pc);
template u64 Mem::Read<u64, true>(Registers& regs, u32 vaddr, s64 pc);
template u8 Mem::Read<u8, false>(Registers& regs, u32 vaddr, s64 pc);
template u16 Mem::Read<u16, false>(Registers& regs, u32 vaddr, s64 pc);
template u32 Mem::Read<u32, false>(Registers& regs, u32 vaddr, s64 pc);
template u64 Mem::Read<u64, false>(Registers& regs, u32 vaddr, s64 pc);
template <class T, bool tlb>
void Mem::Write(Registers& regs, u32 vaddr, T val, s64 pc) {
u32 paddr = vaddr;
if(!MapVAddr<tlb>(regs, STORE, vaddr, paddr)) {
HandleTLBException(regs, vaddr);
FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, pc);
}
switch(paddr) {
case 0x00000000 ... 0x007FFFFF: util::WriteAccess<T>(rdram.data(), paddr & RDRAM_DSIZE, val); break;
case 0x04000000 ... 0x04000FFF: util::WriteAccess<T>(mmio.rsp.dmem, paddr & DMEM_DSIZE, val); break;
case 0x04001000 ... 0x04001FFF: util::WriteAccess<T>(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<T>(cart.data(), paddr & romMask, val); break;
case 0x1FC00000 ... 0x1FC007BF: util::WriteAccess<T>(pifBootrom, paddr & PIF_BOOTROM_DSIZE, val); break;
case 0x1FC007C0 ... 0x1FC007FF: util::WriteAccess<T>(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<u8, true>(Registers& regs, u32 vaddr, u8 val, s64 pc);
template void Mem::Write<u16, true>(Registers& regs, u32 vaddr, u16 val, s64 pc);
template void Mem::Write<u32, true>(Registers& regs, u32 vaddr, u32 val, s64 pc);
template void Mem::Write<u64, true>(Registers& regs, u32 vaddr, u64 val, s64 pc);
template void Mem::Write<u8, false>(Registers& regs, u32 vaddr, u8 val, s64 pc);
template void Mem::Write<u16, false>(Registers& regs, u32 vaddr, u16 val, s64 pc);
template void Mem::Write<u32, false>(Registers& regs, u32 vaddr, u32 val, s64 pc);
template void Mem::Write<u64, false>(Registers& regs, u32 vaddr, u64 val, s64 pc);
}

View File

@@ -1,17 +1,25 @@
#pragma once
#include <common.hpp>
#include <n64/memory_regions.hpp>
#include <n64/core/MMIO.hpp>
#include <vector>
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 <class T, bool tlb>
T Read(Registers&, u32, s64);
template <class T, bool tlb>
void Write(Registers&, u32, T, s64);
private:
friend struct RSP;
MMIO mmio;
std::vector<u8> cart, rdram, sram;
u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{}, pifRam[PIF_RAM_SIZE]{};
u8 pifBootrom[PIF_BOOTROM_SIZE]{};

View File

@@ -1 +1,113 @@
#include <RDP.hpp>
#include <util.hpp>
#include <RSP.hpp>
#include <../../../frontend/ParallelRDPWrapper.hpp>
#include <Interrupt.hpp>
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<u32>(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<u32>(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();
}
}

View File

@@ -1,8 +1,62 @@
#pragma once
#include <common.hpp>
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

103
src/core/n64/core/RSP.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include <RSP.hpp>
#include <util.hpp>
#include <n64/core/Mem.hpp>
#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<u32>(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 <bool isDRAMdest>
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<false>(spDMARDLen, *this, spDMASPAddr.bank ? imem : dmem, mem.GetRDRAM());
spDMARDLen.raw = 0xFF8 | (spDMARDLen.skip << 20);
} break;
case 0x0404000C: {
spDMAWRLen.raw = value;
DMA<true>(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);
}
}
}

196
src/core/n64/core/RSP.hpp Normal file
View File

@@ -0,0 +1,196 @@
#pragma once
#include <mmio/MI.hpp>
#include <RDP.hpp>
#include <n64/memory_regions.hpp>
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;
}
}
};
}

View File

@@ -1 +1,23 @@
#include <Registers.hpp>
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;
}
}

View File

@@ -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)
}

View File

@@ -1,17 +1,195 @@
#include <Cop0.hpp>
#include <util.hpp>
#include <n64/core/cpu/Registers.hpp>
#include <n64/core/Cpu.hpp>
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<class T>
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<class T>
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 = &regs.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);
}
}
}

View File

@@ -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<class T>
T GetReg(u8 index);
T GetReg(u8);
template<class T>
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);
}

View File

@@ -0,0 +1,105 @@
#include <RSP.hpp>
#include <util.hpp>
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 &regs, 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);
}
}
}

View File

@@ -0,0 +1,209 @@
#include <RSP.hpp>
#include <util.hpp>
#include <n64/core/cpu/Registers.hpp>
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)]);
}
}

View File

@@ -59,7 +59,7 @@ auto GetSwapFunc(T num) -> T {
template <typename T>
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<T>(result);
@@ -67,7 +67,7 @@ inline T ReadAccess(u8* data, u32 index) {
template <typename T>
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<T, true>(val);
memcpy(&data[index], &temp, sizeof(T));
}

View File

@@ -1,13 +1,14 @@
#include <ParallelRDPWrapper.hpp>
#include <RDP.hpp>
#include "ParallelRDPWrapper.hpp"
#include "n64/core/RDP.hpp"
#include <memory>
#include <rdp_device.hpp>
#include "parallel-rdp-standalone/parallel-rdp/rdp_device.hpp"
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_vulkan.h>
#include <util.hpp>
#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<const char*>();
@@ -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> 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);

View File

@@ -1,10 +1,19 @@
#pragma once
#include <n64/Core.hpp>
#include <wsi.hpp>
#include "n64/Core.hpp"
#include "parallel-rdp-standalone/vulkan/wsi.hpp"
#include <SDL2/SDL.h>
#include <n64/core/mmio/VI.hpp>
#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<natsukashii::core::BaseCore> 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();

View File

@@ -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)

View File

@@ -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(

View File

@@ -2,7 +2,7 @@
#include <gb/Core.hpp>
#include <n64/Core.hpp>
#include <volk.h>
#include <ParallelRDPWrapper.hpp>
#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<gb::core::Core>(rom);
windowID = SDL_GetWindowID(window);
g_Core = std::make_unique<gb::core::Core>(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<n64::core::Core>(rom);
g_Core = std::make_unique<n64::core::Core>(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();
}
}
}

View File

@@ -16,7 +16,5 @@ struct App {
void Run();
private:
SDL_Renderer *renderer = nullptr;
u32 id;
std::unique_ptr<BaseCore> core;
};
}