diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index c8a6003..650260e 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -88,6 +88,9 @@ void Core::StepRSP(const u32 cpuCycles) { return; } + if (cpuType == CachedInterpreter) + return mmio.rsp.ExecuteCached(); + static constexpr u32 cpuRatio = 3, rspRatio = 2; regs.steps += cpuCycles; diff --git a/src/backend/core/Interpreter.cpp b/src/backend/core/Interpreter.cpp index 4232ded..9d57cbe 100644 --- a/src/backend/core/Interpreter.cpp +++ b/src/backend/core/Interpreter.cpp @@ -152,8 +152,6 @@ u32 Interpreter::ExecuteCached() { // 0, making so the emulator halts cause the outer loop won't advance const auto blockCycles = line->cycles; for (u32 i = 0; i < line->len; i++) { - addr += 4; - if (!MaybeAdvance()) return i + 1; diff --git a/src/backend/core/RSP.cpp b/src/backend/core/RSP.cpp index ac0a3cc..fa94f69 100644 --- a/src/backend/core/RSP.cpp +++ b/src/backend/core/RSP.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace n64 { RSP::RSP() { Reset(); } @@ -230,4 +231,70 @@ void RSP::Write(const u32 addr, const u32 val) { panic("Unimplemented SP register write {:08X}, val: {:08X}", addr, val); } } + +void RSP::CacheBlock(u16 addr) { + auto blockAddr = addr; + + CachedLine line; + u32 i; + bool fetchDelaySlot = false; + + for (i = 0; i < MAX_INSTR_PER_BLOCK; i++) { + Instruction instr = ircolib::ReadAccess(imem, addr & IMEM_DSIZE); + + addr += 4; + line.code[i] = instr; + + if (fetchDelaySlot) { + i++; + break; + } + + if (InstrEndsBlock(instr)) { + if (InstrHasDelaySlot(instr) && !fetchDelaySlot) { + fetchDelaySlot = true; + continue; + } + + if (i == 0) + i = 1; + + break; + } + } + + line.cycles = i; + line.len = i; + cachedState.blocks[CACHE_GET_BLOCK(blockAddr)]->lines[CACHE_GET_LINE(blockAddr)] = new CachedLine(line); + + return ExecuteCached(); +} + +void RSP::ExecuteCached() { + u16 addr = pc; + auto &blocks = cachedState.blocks; + if (!blocks[CACHE_GET_BLOCK(addr)]) { + blocks[CACHE_GET_BLOCK(addr)] = new CachedBlock(); + return CacheBlock(addr); + } + + const auto line = blocks[CACHE_GET_BLOCK(addr)]->lines[CACHE_GET_LINE(addr)]; + if (line) { + for (u32 i = 0; i < line->len; i++) { + prevDelaySlot = delaySlot; + delaySlot = false; + + oldPC = pc & 0xFFC; + pc = nextPC & 0xFFC; + nextPC += 4; + + Instruction instr = line->code[i]; + Exec(instr); + } + + return; + } + + return CacheBlock(addr); +} } // namespace n64 diff --git a/src/backend/core/RSP.hpp b/src/backend/core/RSP.hpp index 7849406..1d5eec5 100644 --- a/src/backend/core/RSP.hpp +++ b/src/backend/core/RSP.hpp @@ -5,110 +5,111 @@ #include #include #include +#include #define RSP_BYTE(addr) (dmem[BYTE_ADDRESS(addr) & 0xFFF]) #define GET_RSP_HALF(addr) ((RSP_BYTE(addr) << 8) | RSP_BYTE((addr) + 1)) #define SET_RSP_HALF(addr, value) \ - do { \ - RSP_BYTE(addr) = ((value) >> 8) & 0xFF; \ - RSP_BYTE((addr) + 1) = (value) & 0xFF; \ - } \ - while (0) + do { \ + RSP_BYTE(addr) = ((value) >> 8) & 0xFF; \ + RSP_BYTE((addr) + 1) = (value) & 0xFF; \ + } \ + while (0) #define GET_RSP_WORD(addr) ((GET_RSP_HALF(addr) << 16) | GET_RSP_HALF((addr) + 2)) #define SET_RSP_WORD(addr, value) \ - do { \ - SET_RSP_HALF(addr, ((value) >> 16) & 0xFFFF); \ - SET_RSP_HALF((addr) + 2, (value) & 0xFFFF); \ - } \ - while (0) + do { \ + SET_RSP_HALF(addr, ((value) >> 16) & 0xFFFF); \ + SET_RSP_HALF((addr) + 2, (value) & 0xFFFF); \ + } \ + while (0) namespace n64 { 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 signal0 : 1; - unsigned signal1 : 1; - unsigned signal2 : 1; - unsigned signal3 : 1; - unsigned signal4 : 1; - unsigned signal5 : 1; - unsigned signal6 : 1; - unsigned signal7 : 1; - unsigned : 17; - }; + 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 signal0 : 1; + unsigned signal1 : 1; + unsigned signal2 : 1; + unsigned signal3 : 1; + unsigned signal4 : 1; + unsigned signal5 : 1; + unsigned signal6 : 1; + unsigned signal7 : 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; - }; + 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; + 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; + struct { + unsigned address : 12; + unsigned bank : 1; + unsigned : 19; + }; + u32 raw; }; union SPDMADRAMAddr { - struct { - unsigned address : 24; - unsigned : 8; - }; - u32 raw; + struct { + unsigned address : 24; + unsigned : 8; + }; + u32 raw; }; union VPR { - s16 selement[8]; - u16 element[8]; - u8 byte[16]; - u32 word[4]; - m128i single; + s16 selement[8]; + u16 element[8]; + u8 byte[16]; + u32 word[4]; + m128i single; } __attribute__((packed)); static_assert(sizeof(VPR) == 16); @@ -119,277 +120,277 @@ struct Registers; #define DE(x) (((x) >> 11) & 0x1F) struct RSP { - bool divInLoaded = false; - bool semaphore = false; - std::array dmem{}; - std::array imem{}; - u16 oldPC{}, pc{}, nextPC{}; - s16 divIn{}, divOut{}; - u32 steps = 0; - SPStatus spStatus{}; - SPDMASPAddr spDMASPAddr{}; - SPDMADRAMAddr spDMADRAMAddr{}; - SPDMASPAddr lastSuccessfulSPAddr{}; - SPDMADRAMAddr lastSuccessfulDRAMAddr{}; - SPDMALen spDMALen{}; - s32 gpr[32]{}; - VPR vpr[32]{}; - VPR vte{}; - VPR vce{}; + bool divInLoaded = false; + bool semaphore = false; + std::array dmem{}; + std::array imem{}; + u16 oldPC{}, pc{}, nextPC{}; + s16 divIn{}, divOut{}; + u32 steps = 0; + SPStatus spStatus{}; + SPDMASPAddr spDMASPAddr{}; + SPDMADRAMAddr spDMADRAMAddr{}; + SPDMASPAddr lastSuccessfulSPAddr{}; + SPDMADRAMAddr lastSuccessfulDRAMAddr{}; + SPDMALen spDMALen{}; + s32 gpr[32]{}; + VPR vpr[32]{}; + VPR vte{}; + VPR vce{}; - struct { - VPR h{}, m{}, l{}; - } acc; + struct { + VPR h{}, m{}, l{}; + } acc; - struct { - VPR l{}, h{}; - } vcc, vco; + struct { + VPR l{}, h{}; + } vcc, vco; - RSP(); - void Reset(); + CachedState<4, 0xFFF> cachedState; + bool delaySlot = false, prevDelaySlot = false; - FORCE_INLINE void Step() { - gpr[0] = 0; - const u32 instr = ircolib::ReadAccess(imem, pc & IMEM_DSIZE); - oldPC = pc & 0xFFC; - pc = nextPC & 0xFFC; - nextPC += 4; + RSP(); + void Reset(); - Exec(instr); - } + void ExecuteCached(); + void CacheBlock(u16 addr); - void SetVTE(const VPR &vt, u8 e); - auto Read(u32 addr) -> u32; - void Write(u32 addr, u32 val); - void Exec(Instruction instr); - - FORCE_INLINE void SetPC(const u16 val) { - oldPC = pc & 0xFFC; - pc = val & 0xFFC; - nextPC = pc + 4; - } + FORCE_INLINE void Step() { + prevDelaySlot = delaySlot; + delaySlot = false; + gpr[0] = 0; + const u32 instr = ircolib::ReadAccess(imem, pc & IMEM_DSIZE); + oldPC = pc & 0xFFC; + pc = nextPC & 0xFFC; + nextPC += 4; - [[nodiscard]] FORCE_INLINE s64 GetACC(const int e) const { - s64 val = u64(acc.h.element[e]) << 32; - val |= u64(acc.m.element[e]) << 16; - val |= u64(acc.l.element[e]) << 00; - if ((val & 0x0000800000000000) != 0) { - val |= 0xFFFF000000000000; + Exec(instr); } - return val; - } - FORCE_INLINE void SetACC(const int e, const s64 val) { - acc.h.element[e] = val >> 32; - acc.m.element[e] = val >> 16; - acc.l.element[e] = val; - } + void SetVTE(const VPR &vt, u8 e); + auto Read(u32 addr) -> u32; + void Write(u32 addr, u32 val); + void Exec(Instruction instr); - [[nodiscard]] FORCE_INLINE u16 GetVCO() const { - u16 value = 0; - for (int i = 0; i < 8; i++) { - const bool h = vco.h.element[7 - i] != 0; - const bool l = vco.l.element[7 - i] != 0; - const u32 mask = (l << i) | (h << (i + 8)); - value |= mask; + FORCE_INLINE void SetPC(const u16 val) { + oldPC = pc & 0xFFC; + pc = val & 0xFFC; + nextPC = pc + 4; } - return value; - } - [[nodiscard]] FORCE_INLINE u16 GetVCC() const { - u16 value = 0; - for (int i = 0; i < 8; i++) { - const bool h = vcc.h.element[7 - i] != 0; - const bool l = vcc.l.element[7 - i] != 0; - const u32 mask = (l << i) | (h << (i + 8)); - value |= mask; + [[nodiscard]] FORCE_INLINE s64 GetACC(const int e) const { + s64 val = u64(acc.h.element[e]) << 32; + val |= u64(acc.m.element[e]) << 16; + val |= u64(acc.l.element[e]) << 00; + if ((val & 0x0000800000000000) != 0) { + val |= 0xFFFF000000000000; + } + return val; } - return value; - } - [[nodiscard]] FORCE_INLINE u8 GetVCE() const { - u8 value = 0; - for (int i = 0; i < 8; i++) { - const bool l = vce.element[ELEMENT_INDEX(i)] != 0; - value |= (l << i); + FORCE_INLINE void SetACC(const int e, const s64 val) { + acc.h.element[e] = val >> 32; + acc.m.element[e] = val >> 16; + acc.l.element[e] = val; } - return value; - } - [[nodiscard]] FORCE_INLINE u32 ReadWord(u32 addr) const { - addr &= 0xfff; - return GET_RSP_WORD(addr); - } - - FORCE_INLINE void WriteWord(u32 addr, const u32 val) { - addr &= 0xfff; - SET_RSP_WORD(addr, val); - } - - [[nodiscard]] FORCE_INLINE u16 ReadHalf(u32 addr) const { - addr &= 0xfff; - return GET_RSP_HALF(addr); - } - - FORCE_INLINE void WriteHalf(u32 addr, const u16 val) { - addr &= 0xfff; - SET_RSP_HALF(addr, val); - } - - [[nodiscard]] FORCE_INLINE u8 ReadByte(u32 addr) const { - addr &= 0xfff; - return RSP_BYTE(addr); - } - - FORCE_INLINE void WriteByte(u32 addr, const u8 val) { - addr &= 0xfff; - RSP_BYTE(addr) = val; - } - - FORCE_INLINE bool AcquireSemaphore() { - if (semaphore) { - return true; - } else { - semaphore = true; - return false; + [[nodiscard]] FORCE_INLINE u16 GetVCO() const { + u16 value = 0; + for (int i = 0; i < 8; i++) { + const bool h = vco.h.element[7 - i] != 0; + const bool l = vco.l.element[7 - i] != 0; + const u32 mask = (l << i) | (h << (i + 8)); + value |= mask; + } + return value; } - } - FORCE_INLINE void ReleaseSemaphore() { semaphore = false; } - - void special(Instruction instr); - void regimm(Instruction instr); - void lwc2(Instruction instr); - void swc2(Instruction instr); - void cop2(Instruction instr); - void cop0(Instruction instr); - - void add(Instruction instr); - void addi(Instruction instr); - void and_(Instruction instr); - void andi(Instruction instr); - void b(Instruction instr, bool cond); - void blink(Instruction instr, bool cond); - void cfc2(Instruction instr); - void ctc2(Instruction instr); - void lb(Instruction instr); - void lh(Instruction instr); - void lw(Instruction instr); - void lbu(Instruction instr); - void lhu(Instruction instr); - void lui(Instruction instr); - void luv(Instruction instr); - void lbv(Instruction instr); - void ldv(Instruction instr); - void lsv(Instruction instr); - void llv(Instruction instr); - void lrv(Instruction instr); - void lqv(Instruction instr); - void lfv(Instruction instr); - void lhv(Instruction instr); - void ltv(Instruction instr); - void lpv(Instruction instr); - void j(Instruction instr); - void jal(Instruction instr); - void jr(Instruction instr); - void jalr(Instruction instr); - void nor(Instruction instr); - void or_(Instruction instr); - void ori(Instruction instr); - void xor_(Instruction instr); - void xori(Instruction instr); - void sb(Instruction instr); - void sh(Instruction instr); - void sw(Instruction instr); - void swv(Instruction instr); - void sub(Instruction instr); - void sbv(Instruction instr); - void sdv(Instruction instr); - void stv(Instruction instr); - void sqv(Instruction instr); - void ssv(Instruction instr); - void suv(Instruction instr); - void slv(Instruction instr); - void shv(Instruction instr); - void sfv(Instruction instr); - void srv(Instruction instr); - void spv(Instruction instr); - void sllv(Instruction instr); - void srlv(Instruction instr); - void srav(Instruction instr); - void sll(Instruction instr); - void srl(Instruction instr); - void sra(Instruction instr); - void slt(Instruction instr); - void sltu(Instruction instr); - void slti(Instruction instr); - void sltiu(Instruction instr); - void vabs(Instruction instr); - void vadd(Instruction instr); - void vaddc(Instruction instr); - void vand(Instruction instr); - void vnand(Instruction instr); - void vch(Instruction instr); - void vcr(Instruction instr); - void vcl(Instruction instr); - void vmacf(Instruction instr); - void vmacu(Instruction instr); - void vmacq(Instruction instr); - void vmadh(Instruction instr); - void vmadl(Instruction instr); - void vmadm(Instruction instr); - void vmadn(Instruction instr); - void vmov(Instruction instr); - void vmulf(Instruction instr); - void vmulu(Instruction instr); - void vmulq(Instruction instr); - void vmudl(Instruction instr); - void vmudh(Instruction instr); - void vmudm(Instruction instr); - void vmudn(Instruction instr); - void vmrg(Instruction instr); - void vlt(Instruction instr); - void veq(Instruction instr); - void vne(Instruction instr); - void vge(Instruction instr); - void vrcp(Instruction instr); - void vrsq(Instruction instr); - void vrcpl(Instruction instr); - void vrsql(Instruction instr); - void vrndp(Instruction instr); - void vrndn(Instruction instr); - void vrcph(Instruction instr); - void vsar(Instruction instr); - void vsub(Instruction instr); - void vsubc(Instruction instr); - void vxor(Instruction instr); - void vnxor(Instruction instr); - void vor(Instruction instr); - void vnor(Instruction instr); - void vzero(Instruction instr); - void mfc0(const RDP &rdp, Instruction instr); - void mtc0(Instruction instr) const; - void mfc2(Instruction instr); - void mtc2(Instruction instr); - - template - void DMA(); - void WriteStatus(u32 value); - -private: - FORCE_INLINE void branch(const u16 address, const bool cond) { - if (cond) { - nextPC = address & 0xFFC; + [[nodiscard]] FORCE_INLINE u16 GetVCC() const { + u16 value = 0; + for (int i = 0; i < 8; i++) { + const bool h = vcc.h.element[7 - i] != 0; + const bool l = vcc.l.element[7 - i] != 0; + const u32 mask = (l << i) | (h << (i + 8)); + value |= mask; + } + return value; } - } - FORCE_INLINE void branch_likely(const u16 address, const bool cond) { - if (cond) { - nextPC = address & 0xFFC; - } else { - pc = nextPC & 0xFFC; - nextPC = pc + 4; + [[nodiscard]] FORCE_INLINE u8 GetVCE() const { + u8 value = 0; + for (int i = 0; i < 8; i++) { + const bool l = vce.element[ELEMENT_INDEX(i)] != 0; + value |= (l << i); + } + return value; + } + + [[nodiscard]] FORCE_INLINE u32 ReadWord(u32 addr) const { + addr &= 0xfff; + return GET_RSP_WORD(addr); + } + + FORCE_INLINE void WriteWord(u32 addr, const u32 val) { + addr &= 0xfff; + SET_RSP_WORD(addr, val); + } + + [[nodiscard]] FORCE_INLINE u16 ReadHalf(u32 addr) const { + addr &= 0xfff; + return GET_RSP_HALF(addr); + } + + FORCE_INLINE void WriteHalf(u32 addr, const u16 val) { + addr &= 0xfff; + SET_RSP_HALF(addr, val); + } + + [[nodiscard]] FORCE_INLINE u8 ReadByte(u32 addr) const { + addr &= 0xfff; + return RSP_BYTE(addr); + } + + FORCE_INLINE void WriteByte(u32 addr, const u8 val) { + addr &= 0xfff; + RSP_BYTE(addr) = val; + } + + FORCE_INLINE bool AcquireSemaphore() { + if (semaphore) { + return true; + } else { + semaphore = true; + return false; + } + } + + FORCE_INLINE void ReleaseSemaphore() { semaphore = false; } + + void special(Instruction instr); + void regimm(Instruction instr); + void lwc2(Instruction instr); + void swc2(Instruction instr); + void cop2(Instruction instr); + void cop0(Instruction instr); + + void add(Instruction instr); + void addi(Instruction instr); + void and_(Instruction instr); + void andi(Instruction instr); + void b(Instruction instr, bool cond); + void blink(Instruction instr, bool cond); + void cfc2(Instruction instr); + void ctc2(Instruction instr); + void lb(Instruction instr); + void lh(Instruction instr); + void lw(Instruction instr); + void lbu(Instruction instr); + void lhu(Instruction instr); + void lui(Instruction instr); + void luv(Instruction instr); + void lbv(Instruction instr); + void ldv(Instruction instr); + void lsv(Instruction instr); + void llv(Instruction instr); + void lrv(Instruction instr); + void lqv(Instruction instr); + void lfv(Instruction instr); + void lhv(Instruction instr); + void ltv(Instruction instr); + void lpv(Instruction instr); + void j(Instruction instr); + void jal(Instruction instr); + void jr(Instruction instr); + void jalr(Instruction instr); + void nor(Instruction instr); + void or_(Instruction instr); + void ori(Instruction instr); + void xor_(Instruction instr); + void xori(Instruction instr); + void sb(Instruction instr); + void sh(Instruction instr); + void sw(Instruction instr); + void swv(Instruction instr); + void sub(Instruction instr); + void sbv(Instruction instr); + void sdv(Instruction instr); + void stv(Instruction instr); + void sqv(Instruction instr); + void ssv(Instruction instr); + void suv(Instruction instr); + void slv(Instruction instr); + void shv(Instruction instr); + void sfv(Instruction instr); + void srv(Instruction instr); + void spv(Instruction instr); + void sllv(Instruction instr); + void srlv(Instruction instr); + void srav(Instruction instr); + void sll(Instruction instr); + void srl(Instruction instr); + void sra(Instruction instr); + void slt(Instruction instr); + void sltu(Instruction instr); + void slti(Instruction instr); + void sltiu(Instruction instr); + void vabs(Instruction instr); + void vadd(Instruction instr); + void vaddc(Instruction instr); + void vand(Instruction instr); + void vnand(Instruction instr); + void vch(Instruction instr); + void vcr(Instruction instr); + void vcl(Instruction instr); + void vmacf(Instruction instr); + void vmacu(Instruction instr); + void vmacq(Instruction instr); + void vmadh(Instruction instr); + void vmadl(Instruction instr); + void vmadm(Instruction instr); + void vmadn(Instruction instr); + void vmov(Instruction instr); + void vmulf(Instruction instr); + void vmulu(Instruction instr); + void vmulq(Instruction instr); + void vmudl(Instruction instr); + void vmudh(Instruction instr); + void vmudm(Instruction instr); + void vmudn(Instruction instr); + void vmrg(Instruction instr); + void vlt(Instruction instr); + void veq(Instruction instr); + void vne(Instruction instr); + void vge(Instruction instr); + void vrcp(Instruction instr); + void vrsq(Instruction instr); + void vrcpl(Instruction instr); + void vrsql(Instruction instr); + void vrndp(Instruction instr); + void vrndn(Instruction instr); + void vrcph(Instruction instr); + void vsar(Instruction instr); + void vsub(Instruction instr); + void vsubc(Instruction instr); + void vxor(Instruction instr); + void vnxor(Instruction instr); + void vor(Instruction instr); + void vnor(Instruction instr); + void vzero(Instruction instr); + void mfc0(const RDP &rdp, Instruction instr); + void mtc0(Instruction instr) const; + void mfc2(Instruction instr); + void mtc2(Instruction instr); + + template + void DMA(); + void WriteStatus(u32 value); + + private: + FORCE_INLINE void branch(const u16 address, const bool cond) { + if (cond) { + nextPC = address & 0xFFC; + delaySlot = true; + } } - } }; } // namespace n64