diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc index 0770a1a1..e5801e02 100644 --- a/src/app/emu/cpu.cc +++ b/src/app/emu/cpu.cc @@ -67,6 +67,22 @@ uint8_t CPU::FetchByteDirectPage(uint8_t operand) { return fetchedByte; } +void CPU::Run() { + while (true) { + // Fetch the next opcode from memory at the current program counter + uint8_t opcode = memory.ReadByte(PC); + + // Increment the program counter to point to the next instruction + PC++; + + // Execute the instruction corresponding to the fetched opcode + ExecuteInstruction(opcode); + + // Optionally, handle interrupts or other external events + HandleInterrupts(); + } +} + void CPU::ExecuteInstruction(uint8_t opcode) { // Update the PC based on the Program Bank Register PC |= (static_cast(PB) << 16); @@ -262,11 +278,11 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x50: // BVC Branch if overflow clear - // BVC(); + BVC(ReadByte(PC)); break; case 0x70: // BVS Branch if overflow set - // BVS(); + BVS(ReadByte(PC)); break; case 0x18: // CLC Clear carry @@ -286,49 +302,49 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xC1: // CMP DP Indexed Indirect, X - // CMP(); + CMP(DirectPageIndexedIndirectX()); break; case 0xC3: // CMP Stack Relative - // CMP(); + CMP(StackRelative()); break; case 0xC5: // CMP Direct Page - // CMP(); + CMP(DirectPage()); break; case 0xC7: // CMP DP Indirect Long - // CMP(); + CMP(DirectPageIndirectLong()); break; case 0xC9: // CMP Immediate - // CMP(); + CMP(Immediate(), true); break; case 0xCD: // CMP Absolute - // CMP(); + CMP(Absolute()); break; case 0xCF: // CMP Absolute Long - // CMP(); + CMP(AbsoluteLong()); break; case 0xD1: // CMP DP Indirect Indexed, Y - // CMP(); + CMP(DirectPageIndirectIndexedY()); break; case 0xD2: // CMP DP Indirect - // CMP(); + CMP(DirectPageIndirect()); break; case 0xD3: // CMP SR Indirect Indexed, Y - // CMP(); + CMP(StackRelativeIndirectIndexedY()); break; case 0xD5: // CMP DP Indexed, X - // CMP(); + CMP(DirectPageIndexedX()); break; case 0xD7: // CMP DP Indirect Long Indexed, Y - // CMP(); + CMP(DirectPageIndirectLongIndexedY()); break; case 0xD9: // CMP Absolute Indexed, Y - // CMP(); + CMP(AbsoluteIndexedY()); break; case 0xDD: // CMP Absolute Indexed, X - // CMP(); + CMP(AbsoluteIndexedX()); break; case 0xDF: // CMP Absolute Long Indexed, X - // CMP(); + CMP(AbsoluteLongIndexedX()); break; case 0x02: // COP Coprocessor @@ -356,19 +372,19 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x3A: // DEC Accumulator - // DEC(); + DEC(A); break; case 0xC6: // DEC Direct Page - // DEC(); + DEC(DirectPage()); break; case 0xCE: // DEC Absolute - // DEC(); + DEC(Absolute()); break; case 0xD6: // DEC DP Indexed, X - // DEC(); + DEC(DirectPageIndexedX()); break; case 0xDE: // DEC Absolute Indexed, X - // DEC(); + DEC(AbsoluteIndexedX()); break; case 0xCA: // DEX Decrement X register @@ -380,49 +396,49 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x41: // EOR DP Indexed Indirect, X - // EOR(); + EOR(DirectPageIndexedIndirectX()); break; case 0x43: // EOR Stack Relative - // EOR(); + EOR(StackRelative()); break; case 0x45: // EOR Direct Page - // EOR(); + EOR(DirectPage()); break; case 0x47: // EOR DP Indirect Long - // EOR(); + EOR(DirectPageIndirectLong()); break; case 0x49: // EOR Immediate - // EOR(); + EOR(Immediate(), true); break; case 0x4D: // EOR Absolute - // EOR(); + EOR(Absolute()); break; case 0x4F: // EOR Absolute Long - // EOR(); + EOR(AbsoluteLong()); break; case 0x51: // EOR DP Indirect Indexed, Y - // EOR(); + EOR(DirectPageIndirectIndexedY()); break; case 0x52: // EOR DP Indirect - // EOR(); + EOR(DirectPageIndirect()); break; case 0x53: // EOR SR Indirect Indexed, Y - // EOR(); + EOR(StackRelativeIndirectIndexedY()); break; case 0x55: // EOR DP Indexed, X - // EOR(); + EOR(DirectPageIndexedX()); break; case 0x57: // EOR DP Indirect Long Indexed, Y - // EOR(); + EOR(DirectPageIndirectLongIndexedY()); break; case 0x59: // EOR Absolute Indexed, Y - // EOR(); + EOR(AbsoluteIndexedY()); break; case 0x5D: // EOR Absolute Indexed, X - // EOR(); + EOR(AbsoluteIndexedX()); break; case 0x5F: // EOR Absolute Long Indexed, X - // EOR(); + EOR(AbsoluteLongIndexedX()); break; case 0x1A: // INC Accumulator @@ -524,51 +540,51 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xA2: // LDX Immediate - // LDX(); + LDX(Immediate(), true); break; case 0xA6: // LDX Direct Page - // LDX(); + LDX(DirectPage()); break; case 0xAE: // LDX Absolute - // LDX(); + LDX(Absolute()); break; case 0xB6: // LDX DP Indexed, Y - // LDX(); + LDX(DirectPageIndexedY()); break; case 0xBE: // LDX Absolute Indexed, Y - // LDX(); + LDX(AbsoluteIndexedY()); break; case 0xA0: // LDY Immediate - // LDY(); + LDY(Immediate(), true); break; case 0xA4: // LDY Direct Page - // LDY(); + LDY(DirectPage()); break; case 0xAC: // LDY Absolute - // LDY(); + LDY(Absolute()); break; case 0xB4: // LDY DP Indexed, X - // LDY(); + LDY(DirectPageIndexedX()); break; case 0xBC: // LDY Absolute Indexed, X - // LDY(); + LDY(AbsoluteIndexedX()); break; case 0x46: // LSR Direct Page - // LSR(); + LSR(DirectPage()); break; case 0x4A: // LSR Accumulator - // LSR(); + LSR(A); break; case 0x4E: // LSR Absolute - // LSR(); + LSR(Absolute()); break; case 0x56: // LSR DP Indexed, X - // LSR(); + LSR(DirectPageIndexedX()); break; case 0x5E: // LSR Absolute Indexed, X - // LSR(); + LSR(AbsoluteIndexedX()); break; case 0x54: // MVN Block Move Next @@ -580,61 +596,61 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x01: // ORA DP Indexed Indirect, X - // ORA(); + ORA(DirectPageIndexedIndirectX()); break; case 0x03: // ORA Stack Relative - // ORA(); + ORA(StackRelative()); break; case 0x05: // ORA Direct Page - // ORA(); + ORA(DirectPage()); break; case 0x07: // ORA DP Indirect Long - // ORA(); + ORA(DirectPageIndirectLong()); break; case 0x09: // ORA Immediate - // ORA(); + ORA(Immediate(), true); break; case 0x0D: // ORA Absolute - // ORA(); + ORA(Absolute()); break; case 0x0F: // ORA Absolute Long - // ORA(); + ORA(AbsoluteLong()); break; case 0x11: // ORA DP Indirect Indexed, Y - // ORA(); + ORA(DirectPageIndirectIndexedY()); break; case 0x12: // ORA DP Indirect - // ORA(); + ORA(DirectPageIndirect()); break; case 0x13: // ORA SR Indirect Indexed, Y - // ORA(); + ORA(StackRelativeIndirectIndexedY()); break; case 0x15: // ORA DP Indexed, X - // ORA(); + ORA(DirectPageIndexedX()); break; case 0x17: // ORA DP Indirect Long Indexed, Y - // ORA(); + ORA(DirectPageIndirectLongIndexedY()); break; case 0x19: // ORA Absolute Indexed, Y - // ORA(); + ORA(AbsoluteIndexedY()); break; case 0x1D: // ORA Absolute Indexed, X - // ORA(); + ORA(AbsoluteIndexedX()); break; case 0x1F: // ORA Absolute Long Indexed, X - // ORA(); + ORA(AbsoluteLongIndexedX()); break; case 0xF4: // PEA Push Effective Absolute address - // PEA(); + PEA(); break; case 0xD4: // PEI Push Effective Indirect address - // PEI(); + PEI(); break; case 0x62: // PER Push Effective PC Relative Indirect address - // PER(); + PER(); break; case 0x48: // PHA Push Accumulator @@ -694,94 +710,93 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x26: // ROL Direct Page - // ROL(); + ROL(DirectPage()); break; - case 0x2A: // ROL Accumulator - // ROL(); + ROL(A); break; case 0x2E: // ROL Absolute - // ROL(); + ROL(Absolute()); break; case 0x36: // ROL DP Indexed, X - // ROL(); + ROL(DirectPageIndexedX()); break; case 0x3E: // ROL Absolute Indexed, X - // ROL(); + ROL(AbsoluteIndexedX()); break; case 0x66: // ROR Direct Page - // ROR(); + ROR(DirectPage()); break; case 0x6A: // ROR Accumulator - // ROR(); + ROR(A); break; case 0x6E: // ROR Absolute - // ROR(); + ROR(Absolute()); break; case 0x76: // ROR DP Indexed, X - // ROR(); + ROR(DirectPageIndexedX()); break; case 0x7E: // ROR Absolute Indexed, X - // ROR(); + ROR(AbsoluteIndexedX()); break; case 0x40: // RTI Return from interrupt - // RTI(); + RTI(); break; case 0x6B: // RTL Return from subroutine long - // RTL(); + RTL(); break; case 0x60: // RTS Return from subroutine - // RTS(); + RTS(); break; case 0xE1: // SBC DP Indexed Indirect, X - // SBC(); + SBC(DirectPageIndexedIndirectX()); break; case 0xE3: // SBC Stack Relative - // SBC(); + SBC(StackRelative()); break; case 0xE5: // SBC Direct Page - // SBC(); + SBC(DirectPage()); break; case 0xE7: // SBC DP Indirect Long - // SBC(); + SBC(DirectPageIndirectLong()); break; case 0xE9: // SBC Immediate - // SBC(); + SBC(Immediate(), true); break; case 0xED: // SBC Absolute - // SBC(); + SBC(Absolute()); break; case 0xEF: // SBC Absolute Long - // SBC(); + SBC(AbsoluteLong()); break; case 0xF1: // SBC DP Indirect Indexed, Y - // SBC(); + SBC(DirectPageIndirectIndexedY()); break; case 0xF2: // SBC DP Indirect - // SBC(); + SBC(DirectPageIndirect()); break; case 0xF3: // SBC SR Indirect Indexed, Y - // SBC(); + SBC(StackRelativeIndirectIndexedY()); break; case 0xF5: // SBC DP Indexed, X - // SBC(); + SBC(DirectPageIndexedX()); break; case 0xF7: // SBC DP Indirect Long Indexed, Y - // SBC(); + SBC(DirectPageIndirectLongIndexedY()); break; case 0xF9: // SBC Absolute Indexed, Y - // SBC(); + SBC(AbsoluteIndexedY()); break; case 0xFD: // SBC Absolute Indexed, X - // SBC(); + SBC(AbsoluteIndexedX()); break; case 0xFF: // SBC Absolute Long Indexed, X - // SBC(); + SBC(AbsoluteLongIndexedX()); break; case 0x38: // SEC Set carry @@ -801,90 +816,90 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x81: // STA DP Indexed Indirect, X - // STA(); + STA(DirectPageIndexedIndirectX()); break; case 0x83: // STA Stack Relative - // STA(); + STA(StackRelative()); break; case 0x85: // STA Direct Page - // STA(); + STA(DirectPage()); break; case 0x87: // STA DP Indirect Long - // STA(); + STA(DirectPageIndirectLong()); break; case 0x8D: // STA Absolute - // STA(); + STA(Absolute()); break; case 0x8F: // STA Absolute Long - // STA(); + STA(AbsoluteLong()); break; case 0x91: // STA DP Indirect Indexed, Y - // STA(); + STA(DirectPageIndirectIndexedY()); break; case 0x92: // STA DP Indirect - // STA(); + STA(DirectPageIndirect()); break; case 0x93: // STA SR Indirect Indexed, Y - // STA(); + STA(StackRelativeIndirectIndexedY()); break; case 0x95: // STA DP Indexed, X - // STA(); + STA(DirectPageIndexedX()); break; case 0x97: // STA DP Indirect Long Indexed, Y - // STA(); + STA(DirectPageIndirectLongIndexedY()); break; case 0x99: // STA Absolute Indexed, Y - // STA(); + STA(AbsoluteIndexedY()); break; case 0x9D: // STA Absolute Indexed, X - // STA(); + STA(AbsoluteIndexedX()); break; case 0x9F: // STA Absolute Long Indexed, X - // STA(); + STA(AbsoluteLongIndexedX()); break; case 0xDB: // STP Stop the clock - // STP(); + STP(); break; case 0x86: // STX Direct Page - // STX(); + STX(DirectPage()); break; case 0x8E: // STX Absolute - // STX(); + STX(Absolute()); break; case 0x96: // STX DP Indexed, Y - // STX(); + STX(DirectPageIndexedY()); break; case 0x84: // STY Direct Page - // STY(); + STY(DirectPage()); break; case 0x8C: // STY Absolute - // STY(); + STY(Absolute()); break; case 0x94: // STY DP Indexed, X - // STY(); + STY(DirectPageIndexedX()); break; case 0x64: // STZ Direct Page - // STZ(); + STZ(DirectPage()); break; case 0x74: // STZ DP Indexed, X - // STZ(); + STZ(DirectPageIndexedX()); break; case 0x9C: // STZ Absolute - // STZ(); + STZ(Absolute()); break; case 0x9E: // STZ Absolute Indexed, X - // STZ(); + STZ(AbsoluteIndexedX()); break; - case 0xAA: // TAX + case 0xAA: // TAX Transfer accumulator to X TAX(); break; - case 0xA8: // TAY + case 0xA8: // TAY Transfer accumulator to Y TAY(); break; @@ -901,58 +916,56 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x14: // TRB Direct Page - // TRB(); + TRB(DirectPage()); break; - case 0x1C: // TRB Absolute - // TRB(); + TRB(Absolute()); break; case 0x04: // TSB Direct Page - // TSB(); + TSB(DirectPage()); break; - case 0x0C: // TSB Absolute - // TSB(); + TSB(Absolute()); break; case 0x3B: // TSC TSC(); break; - case 0xBA: // TSX + case 0xBA: // TSX Transfer stack pointer to X TSX(); break; - case 0x8A: // TXA + case 0x8A: // TXA Transfer X to accumulator TXA(); break; - case 0x9A: // TXS + case 0x9A: // TXS Transfer X to stack pointer TXS(); break; - case 0x9B: // TXY + case 0x9B: // TXY Transfer X to Y TXY(); break; - case 0x98: // TYA + case 0x98: // TYA Transfer Y to accumulator TYA(); break; - case 0xBB: // TYX + case 0xBB: // TYX Transfer Y to X TYX(); break; - case 0xCB: // WAI - // WAI(); + case 0xCB: // WAI Wait for interrupt + WAI(); break; - case 0xEB: // XBA - // XBA(); + case 0xEB: // XBA Exchange B and A + XBA(); break; - case 0xFB: // XCE + case 0xFB: // XCE Exchange carry and emulation bits XCE(); break; default: @@ -961,29 +974,26 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; } } - void CPU::ADC(uint8_t operand) { - auto C = GetCarryFlag(); - if (!E) { // 8-bit mode - uint16_t result = (A & 0xFF) + (operand & 0xFF); // + (C ? 1 : 0); - SetCarryFlag(!(result > 0xFF)); // Update the carry flag + bool C = GetCarryFlag(); + if (GetAccumulatorSize()) { // 8-bit mode + uint16_t result = static_cast(A & 0xFF) + + static_cast(operand) + (C ? 1 : 0); + SetCarryFlag(result > 0xFF); // Update the carry flag // Update the overflow flag bool overflow = (~(A ^ operand) & (A ^ result) & 0x80) != 0; - if (overflow) { - status |= 0x40; // Set overflow flag - } else { - status &= ~0x40; // Clear overflow flag - } + SetOverflowFlag(overflow); - // Update the accumulator + // Update the accumulator with proper wrap-around A = (A & 0xFF00) | (result & 0xFF); - SetZeroFlag(A == 0); + SetZeroFlag((A & 0xFF) == 0); SetNegativeFlag(A & 0x80); } else { - uint32_t result = A + operand; // + (C ? 1 : 0); - SetCarryFlag(!(result > 0xFFFF)); // Update the carry flag + uint32_t result = + static_cast(A) + static_cast(operand) + (C ? 1 : 0); + SetCarryFlag(result > 0xFFFF); // Update the carry flag // Update the overflow flag bool overflow = (~(A ^ operand) & (A ^ result) & 0x8000) != 0; @@ -997,6 +1007,8 @@ void CPU::ADC(uint8_t operand) { } } +void CPU::HandleInterrupts() {} + void CPU::AND(uint16_t value, bool isImmediate) { uint16_t operand; if (E == 0) { // 16-bit mode diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h index f8b19021..78003e82 100644 --- a/src/app/emu/cpu.h +++ b/src/app/emu/cpu.h @@ -51,7 +51,9 @@ class CPU : public Memory, public Clock { uint8_t FetchByteDirectPage(uint8_t operand); + void Run(); void ExecuteInstruction(uint8_t opcode); + void HandleInterrupts(); // ========================================================================== // Addressing Modes @@ -274,12 +276,11 @@ class CPU : public Memory, public Clock { // ========================================================================== // Registers - uint8_t A = 0; // Accumulator - uint8_t B = 0; // Accumulator (High) + uint16_t A = 0; // Accumulator uint16_t X = 0; // X index register uint16_t Y = 0; // Y index register uint16_t D = 0; // Direct Page register - uint16_t DB = 0; // Data Bank register + uint8_t DB = 0; // Data Bank register uint8_t PB = 0; // Program Bank register uint16_t PC = 0; // Program Counter uint8_t E = 1; // Emulation mode flag @@ -435,29 +436,76 @@ class CPU : public Memory, public Clock { // CLV: Clear overflow flag void CLV() { status &= ~0x40; } - // CMP: Compare ``` - // COP: Coprocessor ``` + // CMP: Compare TESTME + // n Set if MSB of result is set; else cleared + // z Set if result is zero; else cleared + // c Set if no borrow; else cleared + void CMP(uint8_t value, bool isImmediate = false) { + if (GetAccumulatorSize()) { // 8-bit + uint8_t result = isImmediate ? A - value : A - memory.ReadByte(value); + SetZeroFlag(result == 0); + SetNegativeFlag(result & 0x80); + SetCarryFlag(A >= value); + } else { // 16-bit + uint16_t result = isImmediate ? A - value : A - memory.ReadWord(value); + SetZeroFlag(result == 0); + SetNegativeFlag(result & 0x8000); + SetCarryFlag(A >= value); + } + } + + // COP: Coprocessor TESTME + void COP() { + PC += 2; // Increment the program counter by 2 + memory.PushWord(PC); + memory.PushByte(status); + SetInterruptFlag(true); + if (E) { + PC = memory.ReadWord(0xFFF4); + } else { + PC = memory.ReadWord(0xFFE4); + } + SetDecimalFlag(false); + } - // CPX: Compare X register // CPX: Compare X register void CPX(uint16_t value, bool isImmediate = false) { - uint16_t memory_value = isImmediate - ? value - : (GetIndexSize() ? memory.ReadByte(value) - : memory.ReadWord(value)); - compare(X, memory_value); + if (GetIndexSize()) { // 8-bit + uint8_t memory_value = isImmediate ? value : memory.ReadByte(value); + compare(X, memory_value); + } else { // 16-bit + uint16_t memory_value = isImmediate ? value : memory.ReadWord(value); + compare(X, memory_value); + } } // CPY: Compare Y register void CPY(uint16_t value, bool isImmediate = false) { - uint16_t memory_value = isImmediate - ? value - : (GetIndexSize() ? memory.ReadByte(value) - : memory.ReadWord(value)); - compare(Y, memory_value); + if (GetIndexSize()) { // 8-bit + uint8_t memory_value = isImmediate ? value : memory.ReadByte(value); + compare(Y, memory_value); + } else { // 16-bit + uint16_t memory_value = isImmediate ? value : memory.ReadWord(value); + compare(Y, memory_value); + } } - // DEC: Decrement ``` + // DEC: Decrement TESTME + void DEC(uint16_t address) { + if (GetAccumulatorSize()) { + uint8_t value = memory.ReadByte(address); + value--; + memory.WriteByte(address, value); + SetZeroFlag(value == 0); + SetNegativeFlag(value & 0x80); + } else { + uint16_t value = memory.ReadWord(address); + value--; + memory.WriteWord(address, value); + SetZeroFlag(value == 0); + SetNegativeFlag(value & 0x8000); + } + } // DEX: Decrement X register void DEX() { @@ -485,7 +533,18 @@ class CPU : public Memory, public Clock { } } - // EOR: Exclusive OR ``` + // EOR: Exclusive OR TESTMEs + void EOR(uint16_t address, bool isImmediate = false) { + if (GetAccumulatorSize()) { + A ^= isImmediate ? address : memory.ReadByte(address); + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x80); + } else { + A ^= isImmediate ? address : memory.ReadWord(address); + SetZeroFlag(A == 0); + SetNegativeFlag(A & 0x8000); + } + } // INC: Increment void INC(uint16_t address) { @@ -779,7 +838,12 @@ class CPU : public Memory, public Clock { } } - // STP: Stop the clock ``` + // TODO: Make this work with the Clock class of the CPU + // STP: Stop the clock + void STP() { + // During the next phase 2 clock cycle, stop the processors oscillator input + // The processor is effectively shut down until a reset occurs (RES` pin). + } // STX: Store X register void STX(uint16_t address) { @@ -900,15 +964,20 @@ class CPU : public Memory, public Clock { SetNegativeFlag(Y & 0x80); } - // WAI: Wait for interrupt ``` + // TODO: Make this communicate with the SNES class + // WAI: Wait for interrupt TESTME + void WAI() { + // Pull the RDY pin low + // Power consumption is reduced(?) + // RDY remains low until an external hardware interupt + // (NMI, IRQ, ABORT, or RESET) is received from the SNES class + } // XBA: Exchange B and A accumulator void XBA() { - uint8_t temp = A; - A = B; - B = temp; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); + uint8_t lowByte = A & 0xFF; + uint8_t highByte = (A >> 8) & 0xFF; + A = (lowByte << 8) | highByte; } // XCE: Exchange Carry and Emulation Flags diff --git a/src/app/emu/mem.h b/src/app/emu/mem.h index 8f49e9b8..00a94269 100644 --- a/src/app/emu/mem.h +++ b/src/app/emu/mem.h @@ -5,6 +5,43 @@ #include #include +// LoROM (Mode 20): + +// Banks Offset Purpose +// 00-3F 0000-1FFF LowRAM (shadowed from 7E) +// 2000-2FFF PPU1, APU +// 3000-3FFF SFX, DSP, etc. +// 4000-41FF Controller +// 4200-5FFF PPU2, DMA, etc. +// 6000-7FFF Expansion RAM (reserved) +// 8000-FFFF 32k ROM Chunk +// 40-7C 0000-7FFF 32k ROM Chunk +// 8000-FFFF 32k ROM Chunk +// 7D 0000-FFFF SRAM +// 7E 0000-1FFF LowRAM +// 2000-FFFF System RAM +// 7F 0000-FFFF System RAM + +// HiROM (Mode 21): + +// Banks Offset Purpose +// 00-3F 0000-1FFF LowRAM (shadowed from 7E) +// 2000-2FFF PPU1, APU +// 3000-3FFF SFX, DSP, etc. +// 4000-41FF Controller +// 4200-5FFF PPU2, DMA, etc. +// 6000-7FFF SRAM (256KB) +// 8000-FFFF 32k ROM Chunk +// 40-6F 0000-FFFF 64k ROM Chunk +// 70-77 0000-FFFF SRAM (256KB) +// 78-7D 0000-FFFF Never Used +// 7E 0000-1FFF LowRAM +// 2000-7FFF HighRAM +// 8000-FFFF Expanded RAM +// 7F 0000-FFFF More Expanded RAM +// 80-EF 0000-FFFF Mirror of 00-6F +// F0-FF 0000-FFFF 64k ROM Chunk + namespace yaze { namespace app { namespace emu { @@ -103,6 +140,86 @@ class Memory { class MemoryImpl : public Memory { public: + void Initialize(const std::vector& romData) { + const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB + const size_t SRAM_SIZE = 0x10000; // 64 KB + const size_t SYSTEM_RAM_SIZE = 0x20000; // 128 KB + const size_t EXPANSION_RAM_SIZE = 0x2000; // 8 KB + const size_t HARDWARE_REGISTERS_SIZE = 0x4000; // 16 KB + + // Clear memory + memory_.clear(); + memory_.resize(0x1000000, 0); // 24-bit address space + + // Load ROM data into memory based on LoROM mapping + size_t romSize = romData.size(); + size_t romAddress = 0; + for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { + for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) { + if (romAddress < romSize) { + std::copy(romData.begin() + romAddress, + romData.begin() + romAddress + ROM_CHUNK_SIZE, + memory_.begin() + (bank << 16) + offset); + romAddress += ROM_CHUNK_SIZE; + } + } + } + + // Initialize SRAM at banks 0x7D and 0xFD + std::fill(memory_.begin() + (0x7D << 16), memory_.begin() + (0x7E << 16), + 0); + std::fill(memory_.begin() + (0xFD << 16), memory_.begin() + (0xFE << 16), + 0); + + // Initialize System RAM at banks 0x7E and 0x7F + std::fill(memory_.begin() + (0x7E << 16), + memory_.begin() + (0x7E << 16) + SYSTEM_RAM_SIZE, 0); + + // Initialize Shadow RAM at banks 0x00-0x3F and 0x80-0xBF + for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { + std::fill(memory_.begin() + (bank << 16), + memory_.begin() + (bank << 16) + 0x2000, 0); + } + + // Initialize Hardware Registers at banks 0x00-0x3F and 0x80-0xBF + for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { + std::fill( + memory_.begin() + (bank << 16) + 0x2000, + memory_.begin() + (bank << 16) + 0x2000 + HARDWARE_REGISTERS_SIZE, 0); + } + + // Initialize Expansion RAM at banks 0x00-0x3F and 0x80-0xBF + for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { + std::fill(memory_.begin() + (bank << 16) + 0x6000, + memory_.begin() + (bank << 16) + 0x6000 + EXPANSION_RAM_SIZE, + 0); + } + + // Initialize Reset and NMI Vectors at bank 0xFF + std::fill(memory_.begin() + (0xFF << 16) + 0xFF00, + memory_.begin() + (0xFF << 16) + 0xFFFF + 1, 0); + + // Copy data into rom_ vector + rom_.resize(kROMSize); + std::copy(memory_.begin() + kROMStart, + memory_.begin() + kROMStart + kROMSize, rom_.begin()); + + // Copy data into ram_ vector + ram_.resize(kRAMSize); + std::copy(memory_.begin() + kRAMStart, + memory_.begin() + kRAMStart + kRAMSize, ram_.begin()); + + // Copy data into vram_ vector + vram_.resize(kVRAMSize); + std::copy(memory_.begin() + kVRAMStart, + memory_.begin() + kVRAMStart + kVRAMSize, vram_.begin()); + + // Copy data into oam_ vector + oam_.resize(kOAMSize); + std::copy(memory_.begin() + kOAMStart, + memory_.begin() + kOAMStart + kOAMSize, oam_.begin()); + } + uint8_t ReadByte(uint16_t address) const override { uint32_t mapped_address = GetMappedAddress(address); return memory_.at(mapped_address); @@ -174,6 +291,7 @@ class MemoryImpl : public Memory { (static_cast(mid) << 8) | low; } + // Stack Pointer access. int16_t SP() const override { return SP_; } void SetSP(int16_t value) override { SP_ = value; } diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index 5e221697..c5c1538f 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -14,6 +14,30 @@ namespace yaze { namespace app { namespace emu { +namespace { +uint8_t GetHeaderOffset(const Memory& memory) { + uint8_t mapMode = memory[(0x00 << 16) + 0xFFD5]; + uint8_t offset; + + switch (mapMode & 0x07) { + case 0: // LoROM + offset = 0x7F; + break; + case 1: // HiROM + offset = 0xFF; + break; + case 5: // ExHiROM + offset = 0x40; + break; + default: + throw std::runtime_error("Unsupported map mode"); + } + + return offset; +} + +} // namespace + void DMA::StartDMATransfer(uint8_t channelMask) { for (int i = 0; i < 8; ++i) { if ((channelMask & (1 << i)) != 0) { @@ -78,7 +102,63 @@ void DMA::EnableHDMATransfers(uint8_t channelMask) { HDMAEN = channelMask; // Set the HDMAEN register to the channel mask } +ROMInfo SNES::ReadRomHeader(uint32_t offset) { + ROMInfo romInfo; + + // Read cartridge title + char title[22]; + for (int i = 0; i < 21; ++i) { + title[i] = cpu.ReadByte(offset + i); + } + title[21] = '\0'; // Null-terminate the string + romInfo.title = std::string(title); + + // Read ROM speed and memory map mode + uint8_t romSpeedAndMapMode = cpu.ReadByte(offset + 0x15); + romInfo.romSpeed = (ROMSpeed)(romSpeedAndMapMode & 0x07); + romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01); + + // Read ROM type + romInfo.romType = (ROMType)cpu.ReadByte(offset + 0x16); + + // Read ROM size + romInfo.romSize = (ROMSize)cpu.ReadByte(offset + 0x17); + + // Read RAM size + romInfo.sramSize = (SRAMSize)cpu.ReadByte(offset + 0x18); + + // Read country code + romInfo.countryCode = (CountryCode)cpu.ReadByte(offset + 0x19); + + // Read license + romInfo.license = (License)cpu.ReadByte(offset + 0x1A); + + // Read ROM version + romInfo.version = cpu.ReadByte(offset + 0x1B); + + // Read checksum complement + romInfo.checksumComplement = cpu.ReadWord(offset + 0x1E); + + // Read checksum + romInfo.checksum = cpu.ReadWord(offset + 0x1C); + + // Read NMI VBL vector + romInfo.nmiVblVector = cpu.ReadWord(offset + 0x3E); + + // Read reset vector + romInfo.resetVector = cpu.ReadWord(offset + 0x3C); + + return romInfo; +} + void SNES::Init(ROM& rom) { + // Load the ROM into memory and set up the memory mapping + memory_.Initialize(rom.vector()); + + // Read the ROM header + auto header_offset = GetHeaderOffset(memory_); + rom_info_ = ReadRomHeader(header_offset); + // Perform a long jump into a FastROM bank (if the ROM speed is FastROM) // Disable the emulation flag (switch to 65816 native mode)s cpu.Init(); @@ -237,7 +317,7 @@ void SNES::Run() { // Enable NMI Interrupts void SNES::EnableVBlankInterrupts() { - vBlankFlag = 0; + vBlankFlag = false; // Clear the RDNMI VBlank flag memory_.ReadByte(0x4210); // RDNMI @@ -248,7 +328,7 @@ void SNES::EnableVBlankInterrupts() { // Wait until the VBlank routine has been processed void SNES::WaitForVBlank() { - vBlankFlag = 1; + vBlankFlag = true; // Loop until `vBlankFlag` is clear while (vBlankFlag) { diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index 240ac9bb..5ded5a6f 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -62,6 +62,8 @@ class SNES : public DMA { SNES() = default; ~SNES() = default; + ROMInfo ReadRomHeader(uint32_t offset); + // Initialization void Init(ROM& rom); @@ -110,6 +112,7 @@ class SNES : public DMA { APU apu{memory_}; // Helper classes + ROMInfo rom_info_; Debugger debugger; std::vector rom_data; diff --git a/test/cpu_test.cc b/test/cpu_test.cc index fda4849e..530cf57b 100644 --- a/test/cpu_test.cc +++ b/test/cpu_test.cc @@ -168,7 +168,7 @@ TEST_F(CPUTest, CheckMemoryContents) { TEST_F(CPUTest, ADC_Immediate_TwoPositiveNumbers) { cpu.A = 0x01; - cpu.status = 0xFF; // 8-bit mode + cpu.SetAccumulatorSize(true); std::vector data = {0x01}; mock_memory.SetMemoryContents(data); @@ -180,7 +180,7 @@ TEST_F(CPUTest, ADC_Immediate_TwoPositiveNumbers) { TEST_F(CPUTest, ADC_Immediate_PositiveAndNegativeNumbers) { cpu.A = 10; - cpu.status = 0xFF; // 8-bit mode + cpu.SetAccumulatorSize(true); std::vector data = {0x69, static_cast(-20)}; mock_memory.SetMemoryContents(data); @@ -270,7 +270,7 @@ TEST_F(CPUTest, ADC_DirectPageIndexedIndirectX) { TEST_F(CPUTest, ADC_CheckCarryFlag) { cpu.A = 0xFF; - cpu.status = 0xFF; // 8-bit mode + cpu.SetAccumulatorSize(true); std::vector data = {0x15, 0x01}; // Operand at address 0x15 mock_memory.SetMemoryContents(data); @@ -286,6 +286,8 @@ TEST_F(CPUTest, ADC_AbsoluteIndexedX) { cpu.A = 0x03; cpu.X = 0x02; // X register cpu.PC = 0x0001; + cpu.SetCarryFlag(false); + cpu.SetAccumulatorSize(false); // 16-bit mode std::vector data = {0x7D, 0x03, 0x00, 0x00, 0x05, 0x00}; mock_memory.SetMemoryContents(data); @@ -612,6 +614,24 @@ TEST_F(CPUTest, BCS_WhenCarryFlagClear) { EXPECT_EQ(cpu.PC, 0x1000); } +// ============================================================================ +// BEQ - Branch if Equal + +TEST_F(CPUTest, BEQ) { + cpu.SetZeroFlag(true); + cpu.PC = 0x1000; + std::vector data = {0xF0, 0x03, 0x02}; // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x03)); + + cpu.ExecuteInstruction(0xF0); // BEQ + EXPECT_EQ(cpu.PC, 0x1003); +} + +// ============================================================================ +// BIT - Bit Test + TEST_F(CPUTest, BIT_Immediate) { cpu.A = 0x01; cpu.PC = 0x0001; @@ -665,6 +685,9 @@ TEST_F(CPUTest, BIT_AbsoluteIndexedX) { EXPECT_FALSE(cpu.GetZeroFlag()); } +// ============================================================================ +// BMI - Branch if Minus + TEST_F(CPUTest, BMI_BranchTaken) { cpu.PC = 0x0000; cpu.SetNegativeFlag(true); @@ -685,6 +708,9 @@ TEST_F(CPUTest, BMI_BranchNotTaken) { EXPECT_EQ(cpu.PC, 0x0000); } +// ============================================================================ +// BNE - Branch if Not Equal + TEST_F(CPUTest, BNE_BranchTaken) { cpu.PC = 0x0000; cpu.SetZeroFlag(false); @@ -705,6 +731,9 @@ TEST_F(CPUTest, BNE_BranchNotTaken) { EXPECT_EQ(cpu.PC, 0x0000); } +// ============================================================================ +// BPL - Branch if Positive + TEST_F(CPUTest, BPL_BranchTaken) { cpu.PC = 0x0000; cpu.SetNegativeFlag(false); @@ -725,6 +754,9 @@ TEST_F(CPUTest, BPL_BranchNotTaken) { EXPECT_EQ(cpu.PC, 0x0000); } +// ============================================================================ +// BRA - Branch Always + TEST_F(CPUTest, BRA) { cpu.PC = 0x0000; std::vector data = {0x02}; // BRA @@ -761,8 +793,124 @@ TEST_F(CPUTest, BRL) { EXPECT_EQ(cpu.PC, 0x1004); } +// ============================================================================ +// BVC - Branch if Overflow Clear + +TEST_F(CPUTest, BVC_BranchTaken) { + cpu.PC = 0x0000; + cpu.SetOverflowFlag(false); + std::vector data = {0x02}; // BVC + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x50); // BVC + EXPECT_EQ(cpu.PC, 0x0002); +} + +// ============================================================================ +// BVS - Branch if Overflow Set + +TEST_F(CPUTest, BVS_BranchTaken) { + cpu.PC = 0x0000; + cpu.SetOverflowFlag(true); + std::vector data = {0x02}; // BVS + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x70); // BVS + EXPECT_EQ(cpu.PC, 0x0002); +} + +// ============================================================================ +// CLC - Clear Carry Flag + +TEST_F(CPUTest, CLC) { + cpu.SetCarryFlag(true); + cpu.PC = 0x0000; + std::vector data = {0x18}; // CLC + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x18); // CLC + EXPECT_FALSE(cpu.GetCarryFlag()); +} + +// ============================================================================ +// CLD - Clear Decimal Mode Flag + +TEST_F(CPUTest, CLD) { + cpu.SetDecimalFlag(true); + cpu.PC = 0x0000; + std::vector data = {0xD8}; // CLD + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xD8); // CLD + EXPECT_FALSE(cpu.GetDecimalFlag()); +} + +// ============================================================================ +// CLI - Clear Interrupt Disable Flag + +TEST_F(CPUTest, CLI) { + cpu.SetInterruptFlag(true); + cpu.PC = 0x0000; + std::vector data = {0x58}; // CLI + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x58); // CLI + EXPECT_FALSE(cpu.GetInterruptFlag()); +} + +// ============================================================================ +// CLV - Clear Overflow Flag + +TEST_F(CPUTest, CLV) { + cpu.SetOverflowFlag(true); + cpu.PC = 0x0000; + std::vector data = {0xB8}; // CLV + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xB8); // CLV + EXPECT_FALSE(cpu.GetOverflowFlag()); +} + +// ============================================================================ +// CMP - Compare Accumulator + +TEST_F(CPUTest, CMP_Immediate_8Bit) { + // Set the accumulator to 8-bit mode + cpu.status = 0x00; + cpu.SetAccumulatorSize(true); + cpu.A = 0x80; // Set the accumulator to 0x80 + mock_memory.InsertMemory(0x0000, {0x40}); + + // Set up the memory to return 0x40 when the Immediate addressing mode is used + EXPECT_CALL(mock_memory, ReadByte(0x00)).WillOnce(::testing::Return(0x40)); + + // Execute the CMP Immediate instruction + cpu.ExecuteInstruction(0xC9); + + // Check the status flags + EXPECT_TRUE(cpu.GetCarryFlag()); // Carry flag should be set + EXPECT_FALSE(cpu.GetZeroFlag()); // Zero flag should not be set + EXPECT_FALSE(cpu.GetNegativeFlag()); // Negative flag should be set +} + +TEST_F(CPUTest, CMP_Absolute_16Bit) { + // Set the accumulator to 16-bit mode + cpu.SetAccumulatorSize(false); + cpu.A = 0x8000; // Set the accumulator to 0x8000 + mock_memory.InsertMemory(0x0000, {0x34, 0x12}); + + // Execute the CMP Absolute instruction + cpu.ExecuteInstruction(0xCD); + + // Check the status flags + EXPECT_TRUE(cpu.GetCarryFlag()); // Carry flag should be set + EXPECT_FALSE(cpu.GetZeroFlag()); // Zero flag should not be set + EXPECT_TRUE(cpu.GetNegativeFlag()); // Negative flag should be set +} + // ============================================================================ // Test for CPX instruction + TEST_F(CPUTest, CPX_CarryFlagSet) { cpu.X = 0x1000; cpu.CPX(0x0FFF);