From 446734321cce7501dfc0ec41e38b95f3cb5e2d54 Mon Sep 17 00:00:00 2001 From: scawful Date: Thu, 30 Nov 2023 02:12:11 -0500 Subject: [PATCH] SNES, CPU, Emulator + tests updated --- src/app/emu/cpu.cc | 340 +++++++++++++++++++---- src/app/emu/cpu.h | 181 +++++++------ src/app/emu/emulator.cc | 432 ++++++++++++++---------------- src/app/emu/emulator.h | 29 +- src/app/emu/internal/asm_parser.h | 118 ++++++++ src/app/emu/memory/memory.cc | 82 ++++++ src/app/emu/memory/memory.h | 11 +- src/app/emu/memory/mock_memory.h | 2 +- src/app/emu/snes.cc | 9 +- src/app/emu/snes.h | 3 +- src/app/emu/video/ppu.cc | 2 +- test/CMakeLists.txt | 2 +- test/emu/cpu_test.cc | 379 +++++++++++++------------- test/emu/ppu_test.cc | 148 ++++++++-- test/z3ed_test.cc | 88 ------ 15 files changed, 1144 insertions(+), 682 deletions(-) create mode 100644 src/app/emu/internal/asm_parser.h create mode 100644 src/app/emu/memory/memory.cc delete mode 100644 test/z3ed_test.cc diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc index 627569cf..9f05266e 100644 --- a/src/app/emu/cpu.cc +++ b/src/app/emu/cpu.cc @@ -32,10 +32,6 @@ void CPU::Update(UpdateMode mode, int stepCount) { } void CPU::ExecuteInstruction(uint8_t opcode) { - // Update the PC based on the Program Bank Register - PC |= (static_cast(PB) << 16); - - // uint8_t operand = -1; bool immediate = false; uint16_t operand = 0; bool accumulator_mode = GetAccumulatorSize(); @@ -181,7 +177,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) { ASL(operand); break; case 0x16: // ASL DP Indexed, X - operand = DirectPageIndexedX(); + operand = ReadByBitMode((0x00 << 16) + DirectPageIndexedX()); ASL(operand); break; case 0x1E: // ASL Absolute Indexed, X @@ -195,25 +191,30 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xB0: // BCS Branch if carry set - BCS(memory.ReadByte(PC)); + operand = memory.ReadByte(PC); + BCS(operand); break; case 0xF0: // BEQ Branch if equal (zero set) - operand = memory.ReadByte(PC); + operand = memory.ReadByte((PB << 16) + PC + 1); BEQ(operand); break; case 0x24: // BIT Direct Page - BIT(DirectPage()); + operand = DirectPage(); + BIT(operand); break; case 0x2C: // BIT Absolute - BIT(Absolute()); + operand = Absolute(); + BIT(operand); break; case 0x34: // BIT DP Indexed, X - BIT(DirectPageIndexedX()); + operand = DirectPageIndexedX(); + BIT(operand); break; case 0x3C: // BIT Absolute Indexed, X - BIT(AbsoluteIndexedX()); + operand = AbsoluteIndexedX(); + BIT(operand); break; case 0x89: // BIT Immediate operand = Immediate(); @@ -222,22 +223,22 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x30: // BMI Branch if minus (negative set) - operand = memory.ReadByte(PC); + operand = memory.ReadByte((PB << 16) + PC + 1); BMI(operand); break; case 0xD0: // BNE Branch if not equal (zero clear) - operand = memory.ReadByte(PC); + operand = memory.ReadByte((PB << 16) + PC + 1); BNE(operand); break; case 0x10: // BPL Branch if plus (negative clear) - operand = memory.ReadByte(PC); + operand = memory.ReadByte((PB << 16) + PC + 1); BPL(operand); break; case 0x80: // BRA Branch always - operand = memory.ReadByte(PC); + operand = memory.ReadByte((PB << 16) + PC + 1); BRA(operand); break; @@ -247,16 +248,16 @@ void CPU::ExecuteInstruction(uint8_t opcode) { case 0x82: // BRL Branch always long operand = FetchSignedWord(); - PC += operand; + next_pc_ = operand; break; case 0x50: // BVC Branch if overflow clear - operand = memory.ReadByte(PC); + operand = FetchByte(); BVC(operand); break; case 0x70: // BVS Branch if overflow set - operand = memory.ReadByte(PC); + operand = FetchByte(); BVS(operand); break; @@ -505,7 +506,8 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0x20: // JSR Absolute - JSR(Absolute()); + operand = Absolute(); + JSR(operand); break; case 0x22: // JSL Absolute Long @@ -526,7 +528,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xA5: // LDA Direct Page operand = DirectPage(); - LDA(operand); + LDA(operand, false, true); break; case 0xA7: // LDA DP Indirect Long operand = DirectPageIndirectLong(); @@ -1102,6 +1104,8 @@ void CPU::ExecuteInstruction(uint8_t opcode) { } LogInstructions(PC, opcode, operand, immediate, accumulator_mode); + uint8_t instructionLength = GetInstructionLength(opcode); + UpdatePC(instructionLength); } void CPU::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, @@ -1109,7 +1113,7 @@ void CPU::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, if (flags()->kLogInstructions) { std::ostringstream oss; oss << "$" << std::uppercase << std::setw(2) << std::setfill('0') - << static_cast(DB) << ":" << std::hex << PC << ": 0x" << std::hex + << static_cast(PB) << ":" << std::hex << PC << ": 0x" << std::hex << static_cast(opcode) << " " << opcode_to_mnemonic.at(opcode) << " "; @@ -1138,7 +1142,7 @@ void CPU::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, } else { // Log the address and opcode. std::cout << "$" << std::uppercase << std::setw(2) << std::setfill('0') - << static_cast(DB) << ":" << std::hex << PC << ": 0x" + << static_cast(PB) << ":" << std::hex << PC << ": 0x" << std::hex << static_cast(opcode) << " " << opcode_to_mnemonic.at(opcode) << " "; @@ -1231,7 +1235,7 @@ void CPU::ANDAbsoluteLong(uint32_t address) { uint32_t operand32 = memory.ReadWordLong(address); A &= operand32; SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80000000); + SetNegativeFlag(A & 0x8000); } // ASL: Arithmetic shift left @@ -1248,21 +1252,21 @@ void CPU::ASL(uint16_t address) { // BCC: Branch if carry clear void CPU::BCC(int8_t offset) { if (!GetCarryFlag()) { // If the carry flag is clear - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } // BCS: Branch if carry set void CPU::BCS(int8_t offset) { if (GetCarryFlag()) { // If the carry flag is set - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } // BEQ: Branch if equal (zero set) void CPU::BEQ(int8_t offset) { if (GetZeroFlag()) { // If the zero flag is set - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } @@ -1277,26 +1281,27 @@ void CPU::BIT(uint16_t address) { // BMI: Branch if minus (negative set) void CPU::BMI(int8_t offset) { if (GetNegativeFlag()) { // If the negative flag is set - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } // BNE: Branch if not equal (zero clear) void CPU::BNE(int8_t offset) { if (!GetZeroFlag()) { // If the zero flag is clear - PC += offset; // Add the offset to the program counter + // PC += offset; + next_pc_ = offset; } } // BPL: Branch if plus (negative clear) void CPU::BPL(int8_t offset) { if (!GetNegativeFlag()) { // If the negative flag is clear - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } // BRA: Branch always -void CPU::BRA(int8_t offset) { PC += offset; } +void CPU::BRA(int8_t offset) { next_pc_ = offset; } // BRK: Break void CPU::BRK() { @@ -1312,21 +1317,19 @@ void CPU::BRK() { } // BRL: Branch always long -void CPU::BRL(int16_t offset) { - PC += offset; // Add the offset to the program counter -} +void CPU::BRL(int16_t offset) { next_pc_ = offset; } // BVC: Branch if overflow clear void CPU::BVC(int8_t offset) { if (!GetOverflowFlag()) { // If the overflow flag is clear - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } // BVS: Branch if overflow set void CPU::BVS(int8_t offset) { if (GetOverflowFlag()) { // If the overflow flag is set - PC += offset; // Add the offset to the program counter + next_pc_ = offset; } } @@ -1497,40 +1500,41 @@ void CPU::INY() { // JMP: Jump void CPU::JMP(uint16_t address) { - PC = address; // Set program counter to the new address + next_pc_ = address; // Set program counter to the new address } // JML: Jump long void CPU::JML(uint32_t address) { - // Set the lower 16 bits of PC to the lower 16 bits of the address - PC = static_cast(address & 0xFFFF); + next_pc_ = static_cast(address & 0xFFFF); // Set the PBR to the upper 8 bits of the address PB = static_cast((address >> 16) & 0xFF); } // JSR: Jump to subroutine void CPU::JSR(uint16_t address) { - PC -= 1; // Subtract 1 from program counter memory.PushWord(PC); // Push the program counter onto the stack - PC = address; // Set program counter to the new address + next_pc_ = address; // Set program counter to the new address } // JSL: Jump to subroutine long void CPU::JSL(uint32_t address) { - PC -= 1; // Subtract 1 from program counter memory.PushLong(PC); // Push the program counter onto the stack as a long // value (24 bits) - PC = address; // Set program counter to the new address + next_pc_ = address; // Set program counter to the new address } // LDA: Load accumulator -void CPU::LDA(uint16_t address, bool isImmediate) { +void CPU::LDA(uint16_t address, bool isImmediate, bool direct_page) { + uint8_t bank = PB; + if (direct_page) { + bank = 0; + } if (GetAccumulatorSize()) { - A = isImmediate ? address : memory.ReadByte(address); + A = isImmediate ? address : memory.ReadByte((bank << 16) | address); SetZeroFlag(A == 0); SetNegativeFlag(A & 0x80); } else { - A = isImmediate ? address : memory.ReadWord(address); + A = isImmediate ? address : memory.ReadWord((bank << 16) | address); SetZeroFlag(A == 0); SetNegativeFlag(A & 0x8000); } @@ -1728,7 +1732,7 @@ void CPU::RTL() { } // RTS: Return from subroutine -void CPU::RTS() { PC = memory.PopWord() + 1; } // ASL: Arithmetic shift left +void CPU::RTS() { last_call_frame_ = memory.PopWord(); } void CPU::SBC(uint16_t value, bool isImmediate) { uint16_t operand; @@ -1937,6 +1941,250 @@ void CPU::XCE() { E = carry; } +uint8_t CPU::GetInstructionLength(uint8_t opcode) { + switch (opcode) { + case 0x00: // BRK + return 0; + + case 0x60: // RTS + PC = last_call_frame_; + return 3; + + // TODO: Handle JMPs in logging. + case 0x20: // JSR Absolute + case 0x4C: // JMP Absolute + case 0x6C: // JMP Absolute Indirect + case 0x5C: // JMP Absolute Indexed Indirect + case 0x22: // JSL Absolute Long + PC = next_pc_; + return 0; + + // Branch Instructions (BCC, BCS, BNE, BEQ, etc.) + case 0x90: // BCC near + if (!GetCarryFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + case 0xB0: // BCS near + if (GetCarryFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + case 0x30: // BMI near + if (GetNegativeFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + case 0xF0: // BEQ near + if (GetZeroFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + + case 0xD0: // BNE Relative + if (!GetZeroFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + + case 0x10: // BPL Relative + if (!GetNegativeFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + + case 0x50: // BVC Relative + if (!GetOverflowFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + + case 0x70: // BVS Relative + if (GetOverflowFlag()) { + PC += next_pc_; + return 0; + } else { + return 2; + } + + case 0x80: // BRA Relative + PC += next_pc_; + return 0; + + case 0x82: // BRL Relative Long + PC += next_pc_; + return 0; + + // Single Byte Instructions + case 0x18: // CLC + case 0xD8: // CLD + case 0x58: // CLI + case 0xB8: // CLV + case 0xCA: // DEX + case 0x88: // DEY + case 0xE8: // INX + case 0xC8: // INY + case 0xEA: // NOP + case 0x48: // PHA + case 0x0B: // PHD + case 0x4B: // PHK + case 0x08: // PHP + case 0xDA: // PHX + case 0x5A: // PHY + case 0x68: // PLA + case 0xAB: // PLB + case 0x2B: // PLD + case 0x28: // PLP + case 0xFA: // PLX + case 0x7A: // PLY + case 0xC2: // REP + case 0x40: // RTI + case 0x38: // SEC + case 0xF8: // SED + case 0xE2: // SEP + case 0x78: // SEI + case 0xAA: // TAX + case 0xA8: // TAY + case 0xBA: // TSX + case 0x8A: // TXA + case 0x9A: // TXS + case 0x98: // TYA + case 0x0A: // ASL Accumulator + return 1; + + // Two Byte Instructions + case 0x69: // ADC Immediate + case 0x29: // AND Immediate + case 0xC9: // CMP Immediate + case 0xE0: // CPX Immediate + case 0xC0: // CPY Immediate + case 0x49: // EOR Immediate + case 0xA9: // LDA Immediate + case 0xA2: // LDX Immediate + case 0xA0: // LDY Immediate + case 0x09: // ORA Immediate + case 0xE9: // SBC Immediate + case 0x65: // ADC Direct Page + case 0x72: // ADC Direct Page Indirect + case 0x67: // ADC Direct Page Indirect Long + case 0x75: // ADC Direct Page Indexed, X + case 0x61: // ADC Direct Page Indirect, X + case 0x71: // ADC DP Indirect Indexed, Y + case 0x77: // ADC DP Indirect Long Indexed, Y + case 0x63: // ADC Stack Relative + case 0x73: // ADC SR Indirect Indexed, Y + return GetAccumulatorSize() ? 2 : 3; + + case 0x7D: // ADC Absolute Indexed, X + case 0x79: // ADC Absolute Indexed, Y + case 0x6D: // ADC Absolute + return 3; + + case 0x7F: // ADC Absolute Long Indexed, X + case 0x6F: // ADC Absolute Long + return 4; + + case 0xA5: // LDA Direct Page + case 0x05: // ORA Direct Page + case 0x85: // STA Direct Page + case 0xC6: // DEC Direct Page + case 0x97: // STA Direct Page Indexed Y + case 0x25: // AND Direct Page + case 0x32: // AND Direct Page Indirect Indexed Y + case 0x27: // AND Direct Page Indirect Long + case 0x35: // AND Direct Page Indexed X + case 0x21: // AND Direct Page Indirect Indexed Y + case 0x31: // AND Direct Page Indirect Long Indexed Y + case 0x37: // AND Direct Page Indirect Long Indexed Y + case 0x23: // AND Direct Page Indirect Indexed X + case 0x33: // AND Direct Page Indirect Long Indexed Y + case 0xE6: // INC Direct Page + return 2; + + // ASL (Arithmetic Shift Left) + case 0x06: // ASL Direct Page + case 0x16: // ASL Direct Page Indexed, X + return 2; + + case 0x0E: // ASL Absolute + case 0x1E: // ASL Absolute Indexed, X + return 3; + + // Three Byte Instructions + case 0x2D: // AND Absolute + case 0xCD: // CMP Absolute + case 0xEC: // CPX Absolute + case 0xCC: // CPY Absolute + case 0x4D: // EOR Absolute + case 0xAD: // LDA Absolute + case 0xAE: // LDX Absolute + case 0xAC: // LDY Absolute + case 0x0D: // ORA Absolute + case 0xED: // SBC Absolute + case 0x8D: // STA Absolute + case 0x8E: // STX Absolute + case 0x8C: // STY Absolute + case 0xBD: // LDA Absolute Indexed X + case 0xBC: // LDY Absolute Indexed X + case 0x3D: // AND Absolute Indexed X + case 0x39: // AND Absolute Indexed Y + return 3; + + // Four Byte Instructions + case 0x2F: // AND Absolute Long + case 0xCF: // CMP Absolute Long + case 0x4F: // EOR Absolute Long + case 0xAF: // LDA Absolute Long + case 0x0F: // ORA Absolute Long + case 0xEF: // SBC Absolute Long + case 0x8F: // STA Absolute Long + case 0x3F: // AND Absolute Long Indexed X + return 4; + + // BIT (Bit Test) + // Include all BIT variants similar to ADC + + // BRK (Break) and COP (Co-Processor Enable) + + case 0x02: // COP param + return 2; + + // CMP (Compare Accumulator) + // Include all CMP variants similar to ADC + + // CPX (Compare X Register) and CPY (Compare Y Register) + // Include CPX and CPY variants + + // DEC (Decrement Memory) + // Include DEC variants similar to ASL + + // EOR (Exclusive OR with Accumulator) + // Include all EOR variants similar to ADC + + // Variable length or Special cases + // Add cases for instructions with variable lengths or special handling + default: + auto mnemonic = opcode_to_mnemonic.at(opcode); + std::cerr << "Unknown instruction length: " << std::hex + << static_cast(opcode) << ", " << mnemonic << std::endl; + return 1; // Default to 1 as a safe fallback + } +} + } // namespace emu } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h index 3d2ec4a2..fcdf46cc 100644 --- a/src/app/emu/cpu.h +++ b/src/app/emu/cpu.h @@ -134,7 +134,7 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // Low: First operand byte // // LDA addr - uint16_t Absolute() { return FetchWord(); } + uint16_t Absolute() { return ReadWord((PB << 16) | PC + 1); } // Effective Address: // The Data Bank Register is concatened with the 16-bit operand @@ -162,7 +162,7 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // JMP (addr, X) uint16_t AbsoluteIndexedIndirect() { uint16_t address = FetchWord() + X; - return memory.ReadWord(address & 0xFFFF); // Consider PBR if needed + return memory.ReadWord((PB << 16) | address & 0xFFFF); } // Effective Address: @@ -173,7 +173,7 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // JMP (addr) uint16_t AbsoluteIndirect() { uint16_t address = FetchWord(); - return memory.ReadWord(address); + return memory.ReadWord((PB << 16) | address); } // Effective Address: @@ -183,7 +183,7 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // JMP [addr] uint32_t AbsoluteIndirectLong() { uint16_t address = FetchWord(); - return memory.ReadWordLong(address); + return memory.ReadWordLong((PB << 16) | address); } // Effective Address: @@ -224,7 +224,7 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // // LDA dp uint16_t DirectPage() { - uint8_t dp = FetchByte(); + uint8_t dp = memory.ReadByte((PB << 16) | PC + 1); return D + dp; } @@ -235,8 +235,8 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // // LDA dp, X uint16_t DirectPageIndexedX() { - uint8_t dp = FetchByte(); - return (dp + X) & 0xFF; + uint8_t operand = FetchByte(); + return D + operand + X; } // Effective Address: @@ -326,9 +326,9 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // LDA #const uint16_t Immediate() { if (GetAccumulatorSize()) { - return FetchByte(); + return memory.ReadByte((PB << 16) | PC + 1); } else { - return FetchWord(); + return memory.ReadWord((PB << 16) | PC + 1); } } @@ -342,6 +342,91 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { return memory.ReadWord(SP() + sr + Y); } + // Memory access routines + uint8_t ReadByte(uint32_t address) const override { + return memory.ReadByte(address); + } + uint16_t ReadWord(uint32_t address) const override { + return memory.ReadWord(address); + } + uint32_t ReadWordLong(uint32_t address) const override { + return memory.ReadWordLong(address); + } + + std::vector ReadByteVector(uint32_t address, + uint16_t size) const override { + return memory.ReadByteVector(address, size); + } + + void WriteByte(uint32_t address, uint8_t value) override { + memory.WriteByte(address, value); + } + + void WriteWord(uint32_t address, uint16_t value) override { + memory.WriteWord(address, value); + } + + uint8_t FetchByte() { + uint32_t address = (PB << 16) | PC + 1; + uint8_t byte = memory.ReadByte(address); + return byte; + } + + uint16_t FetchWord() { + uint32_t address = (PB << 16) | PC + 1; + uint16_t value = memory.ReadWord(address); + return value; + } + + uint32_t FetchLong() { + uint32_t value = memory.ReadWordLong((PB << 16) | PC + 1); + return value; + } + + int8_t FetchSignedByte() { return static_cast(FetchByte()); } + + int16_t FetchSignedWord() { + auto offset = static_cast(FetchWord()); + return offset; + } + + uint8_t FetchByteDirectPage(uint8_t operand) { + uint16_t distance = D * 0x100; + + // Calculate the effective address in the Direct Page + uint16_t effectiveAddress = operand + distance; + + // Fetch the byte from memory + uint8_t fetchedByte = memory.ReadByte(effectiveAddress); + + next_pc_ = PC + 1; + // PC++; // Increment the Program Counter + + return fetchedByte; + } + + uint16_t ReadByBitMode(uint32_t address) { + if (GetAccumulatorSize()) { + // 8-bit mode + return memory.ReadByte(address) & 0xFF; + } else { + // 16-bit mode + return memory.ReadWord(address); + } + } + + void UpdatePC(uint8_t instruction_length) { PC += instruction_length; } + + uint8_t GetInstructionLength(uint8_t opcode); + void SetMemory(const std::vector& data) override { + memory.SetMemory(data); + } + + int16_t SP() const override { return memory.SP(); } + void SetSP(int16_t value) override { memory.SetSP(value); } + void set_next_pc(uint16_t value) { next_pc_ = value; } + void UpdateClock(int delta_time) { clock.UpdateClock(delta_time); } + // ====================================================== // Instructions @@ -449,7 +534,8 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { void JSL(uint32_t address); // LDA: Load accumulator - void LDA(uint16_t address, bool isImmediate = false); + void LDA(uint16_t address, bool isImmediate = false, + bool direct_page = false); // LDX: Load X register void LDX(uint16_t address, bool isImmediate = false); @@ -622,78 +708,6 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { // XCE: Exchange carry and emulation bits void XCE(); - // Memory access routines - uint8_t ReadByte(uint32_t address) const override { - auto value = memory.ReadByte(address); - return value; - } - uint16_t ReadWord(uint32_t address) const override { - return memory.ReadWord(address); - } - uint32_t ReadWordLong(uint32_t address) const override { - return memory.ReadWordLong(address); - } - - std::vector ReadByteVector(uint32_t address, - uint16_t size) const override { - return memory.ReadByteVector(address, size); - } - - void WriteByte(uint32_t address, uint8_t value) override { - memory.WriteByte(address, value); - } - - void WriteWord(uint32_t address, uint16_t value) override { - memory.WriteWord(address, value); - } - - uint8_t FetchByte() { - uint8_t byte = memory.ReadByte(PC); // Read a byte from memory at PC - PC++; // Increment the Program Counter - return byte; - } - - uint16_t FetchWord() { - uint16_t value = memory.ReadWord(PC); - PC += 2; - return value; - } - - uint32_t FetchLong() { - uint32_t value = memory.ReadWordLong(PC); - PC += 3; - return value; - } - - int8_t FetchSignedByte() { return static_cast(FetchByte()); } - - int16_t FetchSignedWord() { - auto offset = static_cast(FetchWord()); - return offset; - } - - uint8_t FetchByteDirectPage(uint8_t operand) { - uint16_t distance = D * 0x100; - - // Calculate the effective address in the Direct Page - uint16_t effectiveAddress = operand + distance; - - // Fetch the byte from memory - uint8_t fetchedByte = memory.ReadByte(effectiveAddress); - - PC++; // Increment the Program Counter - - return fetchedByte; - } - - void SetMemory(const std::vector& data) override { - memory.SetMemory(data); - } - - int16_t SP() const override { return memory.SP(); } - void SetSP(int16_t value) override { memory.SetSP(value); } - void UpdateClock(int delta_time) { clock.UpdateClock(delta_time); } - private: void compare(uint16_t register_value, uint16_t memory_value) { uint16_t result; @@ -738,6 +752,9 @@ class CPU : public Memory, public Loggable, public core::ExperimentFlags { uint8_t operator[](int i) const override { return 0; } uint8_t at(int i) const override { return 0; } + uint16_t last_call_frame_; + uint16_t next_pc_; + Memory& memory; Clock& clock; }; diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index ed865d0b..4c69affd 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -9,6 +9,7 @@ #include "app/core/constants.h" #include "app/emu/snes.h" #include "app/gui/icons.h" +#include "app/gui/input.h" #include "app/rom.h" namespace yaze { @@ -23,104 +24,6 @@ bool ShouldDisplay(const InstructionEntry& entry, const char* filter, return true; } -void DrawMemoryWindow(Memory* memory) { - const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); - static ImGuiTableFlags flags = - ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | - ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg | - ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX; - if (auto outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f); - ImGui::BeginTable("table1", 4, flags, outer_size)) { - // Table headers - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Memory Area"); - ImGui::TableNextColumn(); - ImGui::Text("Start Address"); - ImGui::TableNextColumn(); - ImGui::Text("Size"); - ImGui::TableNextColumn(); - ImGui::Text("Mapping"); - - // Retrieve memory information from MemoryImpl - MemoryImpl* memoryImpl = dynamic_cast(memory); - if (memoryImpl) { - // Display memory areas - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("ROM Bank 0-63"); - ImGui::TableNextColumn(); - ImGui::Text("0x8000"); - ImGui::TableNextColumn(); - ImGui::Text("128 KB"); - ImGui::TableNextColumn(); - ImGui::Text("LoROM"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("ROM Bank 64-111"); - ImGui::TableNextColumn(); - ImGui::Text("0x0000"); - ImGui::TableNextColumn(); - ImGui::Text("64 KB"); - ImGui::TableNextColumn(); - ImGui::Text("LoROM"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("RAM"); - ImGui::TableNextColumn(); - ImGui::Text("0x700000"); - ImGui::TableNextColumn(); - ImGui::Text("64 KB"); - ImGui::TableNextColumn(); - ImGui::Text("LoROM"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("System RAM (WRAM)"); - ImGui::TableNextColumn(); - ImGui::Text("0x7E0000"); - ImGui::TableNextColumn(); - ImGui::Text("128 KB"); - ImGui::TableNextColumn(); - ImGui::Text("LoROM"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("ROM Bank 128-191"); - ImGui::TableNextColumn(); - ImGui::Text("0x8000"); - ImGui::TableNextColumn(); - ImGui::Text("128 KB"); - ImGui::TableNextColumn(); - ImGui::Text("LoROM"); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("ROM Bank 192-255"); - ImGui::TableNextColumn(); - ImGui::Text("0x0000"); - ImGui::TableNextColumn(); - ImGui::Text("64 KB"); - ImGui::TableNextColumn(); - ImGui::Text("LoROM"); - } - - ImGui::EndTable(); - - if (ImGui::Button("Open Memory Viewer", ImVec2(200, 50))) { - ImGui::OpenPopup("Memory Viewer"); - } - - if (ImGui::BeginPopupModal("Memory Viewer", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - static MemoryEditor mem_edit; - mem_edit.DrawContents((void*)memoryImpl->data(), memoryImpl->size()); - ImGui::EndPopup(); - } - } -} } // namespace using ImGui::NextColumn; @@ -130,105 +33,28 @@ using ImGui::TableNextColumn; using ImGui::Text; void Emulator::Run() { - if (!snes_.running() && loading_) { - // Setup and initialize memory - if (loading_ && !memory_setup_) { - snes_.SetupMemory(*rom()); - memory_setup_ = true; - } + if (!snes_.running() && rom()->isLoaded()) { + snes_.SetupMemory(*rom()); + } - // Run the emulation - if (rom()->isLoaded() && power_) { - snes_.Init(*rom()); - running_ = true; - } + // Setup and initialize memory + if (loading_ && !running_) { + snes_.Init(*rom()); + running_ = true; } RenderNavBar(); - ImGui::Button(ICON_MD_ARROW_FORWARD_IOS); - ImGui::SameLine(); - ImGui::Button(ICON_MD_DOUBLE_ARROW); - ImGui::SameLine(); - ImGui::Button(ICON_MD_SUBDIRECTORY_ARROW_RIGHT); - if (running_) { HandleEvents(); - UpdateEmulator(); + snes_.Run(); } RenderEmulator(); - if (debugger_) { - RenderDebugger(); - } -} - -void Emulator::RenderEmulator() { - ImVec2 size = ImVec2(320, 240); - if (snes_.running()) { - ImGui::BeginChild( - "EmulatorOutput", ImVec2(0, 0), true, - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f); - ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f); - ImGui::Image((void*)snes_.ppu().GetScreen()->texture(), size, ImVec2(0, 0), - ImVec2(1, 1)); - ImGui::EndChild(); - ImGui::Separator(); - } else { - ImGui::BeginChild( - "EmulatorOutput", ImVec2(0, 0), true, - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f); - ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f); - ImGui::Dummy(size); - ImGui::EndChild(); - ImGui::Separator(); - ImGui::Text("Emulator output not available."); - } } void Emulator::RenderNavBar() { MENU_BAR() - - if (ImGui::BeginMenu("Game")) { - MENU_ITEM("Load ROM") { loading_ = true; } - MENU_ITEM("Power On") { power_ = true; } - MENU_ITEM("Power Off") { - power_ = false; - running_ = false; - loading_ = false; - debugger_ = false; - } - MENU_ITEM("Pause") { - running_ = false; - debugger_ = false; - } - MENU_ITEM("Reset") {} - - MENU_ITEM("Save State") {} - MENU_ITEM("Load State") {} - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Debug")) { - MENU_ITEM("Debugger") { debugger_ = !debugger_; } - if (ImGui::MenuItem("Integrated Debugger", nullptr, - &integrated_debugger_mode_)) { - separate_debugger_mode_ = !integrated_debugger_mode_; - } - if (ImGui::MenuItem("Separate Debugger Windows", nullptr, - &separate_debugger_mode_)) { - integrated_debugger_mode_ = !separate_debugger_mode_; - } - MENU_ITEM("Memory Viewer") {} - MENU_ITEM("Tile Viewer") {} - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Options")) { MENU_ITEM("Input") {} MENU_ITEM("Audio") {} @@ -236,6 +62,93 @@ void Emulator::RenderNavBar() { ImGui::EndMenu(); } END_MENU_BAR() + + if (ImGui::Button(ICON_MD_PLAY_ARROW)) { + loading_ = true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Start Emulation"); + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_MD_PAUSE)) { + snes_.SetCpuMode(1); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Pause Emulation"); + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_MD_SKIP_NEXT)) { + // Step through Code logic + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Step Through Code"); + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_MD_REFRESH)) { + // Reset Emulator logic + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reset Emulator"); + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_MD_STOP)) { + // Stop Emulation logic + running_ = false; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop Emulation"); + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_MD_SAVE)) { + // Save State logic + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Save State"); + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_MD_SYSTEM_UPDATE_ALT)) { + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Load State"); + } + + // Additional elements + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_SETTINGS)) { + // Settings logic + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Settings"); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_INFO)) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("About Debugger"); + } + // About Debugger logic + } + static bool show_memory_viewer = false; + + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_MEMORY)) { + show_memory_viewer = !show_memory_viewer; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Memory Viewer"); + } + + if (show_memory_viewer) { + ImGui::Begin("Memory Viewer", &show_memory_viewer); + RenderMemoryViewer(); + ImGui::End(); + } } void Emulator::HandleEvents() { @@ -243,38 +156,51 @@ void Emulator::HandleEvents() { // ... } -void Emulator::UpdateEmulator() { - // Update the emulator state (CPU, PPU, APU, etc.) - // ... - snes_.Run(); +void Emulator::RenderEmulator() { + if (ImGui::BeginTable("##Emulator", 3, + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { + ImGui::TableSetupColumn("CPU"); + ImGui::TableSetupColumn("PPU"); + ImGui::TableHeadersRow(); + + TableNextColumn(); + RenderCpuInstructionLog(snes_.cpu().instruction_log_); + + TableNextColumn(); + RenderSnesPpu(); + RenderBreakpointList(); + + TableNextColumn(); + ImGui::BeginChild("##", ImVec2(0, 0), true, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + RenderCpuState(snes_.cpu()); + ImGui::EndChild(); + + ImGui::EndTable(); + } } -void Emulator::RenderDebugger() { - // Define a lambda with the actual debugger - auto debugger = [&]() { - if (ImGui::BeginTable( - "DebugTable", 3, - ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { - TableNextColumn(); - RenderCpuState(snes_.cpu()); +void Emulator::RenderSnesPpu() { + ImVec2 size = ImVec2(320, 240); + if (snes_.running()) { + ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f); + ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f); + ImGui::Image((void*)snes_.ppu().GetScreen()->texture(), size, ImVec2(0, 0), + ImVec2(1, 1)); + ImGui::EndChild(); - TableNextColumn(); - RenderCPUInstructionLog(snes_.cpu().instruction_log_); - - TableNextColumn(); - RenderBreakpointList(); - DrawMemoryWindow(snes_.Memory()); - ImGui::EndTable(); - } - }; - - if (integrated_debugger_mode_) { - debugger(); - } else if (separate_debugger_mode_) { - ImGui::Begin("Debugger"); - debugger(); - ImGui::End(); + } else { + ImGui::Text("Emulator output not available."); + ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f); + ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f); + ImGui::Dummy(size); + ImGui::EndChild(); } + ImGui::Separator(); } void Emulator::RenderBreakpointList() { @@ -326,6 +252,13 @@ void Emulator::RenderBreakpointList() { } ImGui::EndChild(); } + Separator(); + gui::InputHexByte("PB", &manual_pb_, 1, 25.f); + gui::InputHexWord("PC", &manual_pc_, 25.f); + if (ImGui::Button("Set Current Address")) { + snes_.cpu().PC = manual_pc_; + snes_.cpu().PB = manual_pb_; + } } void Emulator::RenderCpuState(CPU& cpu) { @@ -352,26 +285,74 @@ void Emulator::RenderCpuState(CPU& cpu) { ImGui::Columns(1); Separator(); } + // Call Stack if (ImGui::CollapsingHeader("Call Stack", ImGuiTreeNodeFlags_DefaultOpen)) { // For each return address in the call stack: Text("Return Address: 0x%08X", 0xFFFFFF); // Placeholder } - static int debugger_mode_ = 0; - const char* debugger_modes_[] = {"Run", "Step", "Pause"}; - Text("Mode"); - ImGui::ListBox("##DebuggerMode", &debugger_mode_, debugger_modes_, - IM_ARRAYSIZE(debugger_modes_)); - - snes_.SetCpuMode(debugger_mode_); + snes_.SetCpuMode(0); } void Emulator::RenderMemoryViewer() { - // Render memory viewer + static MemoryEditor mem_edit; + if (ImGui::Button("RAM")) { + mem_edit.GotoAddrAndHighlight(0x7E0000, 0x7E0001); + } + + if (ImGui::BeginTable("MemoryViewerTable", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { + ImGui::TableSetupColumn("Bookmarks"); + ImGui::TableSetupColumn("Memory"); + ImGui::TableHeadersRow(); + + TableNextColumn(); + if (ImGui::CollapsingHeader("Bookmarks", ImGuiTreeNodeFlags_DefaultOpen)) { + // Input for adding a new bookmark + static char nameBuf[256]; + static uint64_t uint64StringBuf; + ImGui::InputText("Name", nameBuf, IM_ARRAYSIZE(nameBuf)); + gui::InputHex("Address", &uint64StringBuf); + if (ImGui::Button("Add Bookmark")) { + bookmarks.push_back({nameBuf, uint64StringBuf}); + memset(nameBuf, 0, sizeof(nameBuf)); + uint64StringBuf = 0; + } + + // Tree view of bookmarks + for (const auto& bookmark : bookmarks) { + if (ImGui::TreeNode(bookmark.name.c_str(), ICON_MD_STAR)) { + auto bookmark_string = absl::StrFormat( + "%s: 0x%08X", bookmark.name.c_str(), bookmark.value); + if (ImGui::Selectable(bookmark_string.c_str())) { + mem_edit.GotoAddrAndHighlight(static_cast(bookmark.value), + 1); + } + ImGui::SameLine(); + if (ImGui::Button("Delete")) { + // Logic to delete the bookmark + bookmarks.erase(std::remove_if(bookmarks.begin(), bookmarks.end(), + [&](const Bookmark& b) { + return b.name == bookmark.name && + b.value == bookmark.value; + }), + bookmarks.end()); + } + ImGui::TreePop(); + } + } + } + + TableNextColumn(); + mem_edit.DrawContents((void*)snes_.Memory()->data(), + snes_.Memory()->size()); + + ImGui::EndTable(); + } } -void Emulator::RenderCPUInstructionLog( +void Emulator::RenderCpuInstructionLog( const std::vector& instructionLog) { if (ImGui::CollapsingHeader("CPU Instruction Log")) { // Filtering options @@ -382,7 +363,7 @@ void Emulator::RenderCPUInstructionLog( } // Toggle for showing all opcodes - static bool showAllOpcodes = false; + static bool showAllOpcodes = true; ImGui::Checkbox("Show All Opcodes", &showAllOpcodes); // Instruction list @@ -391,10 +372,11 @@ void Emulator::RenderCPUInstructionLog( ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeY); for (const auto& entry : instructionLog) { if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) { - if (ImGui::Selectable(absl::StrFormat("%04X: %02X %s %s", entry.address, - entry.opcode, entry.operands, - entry.instruction) - .c_str())) { + if (ImGui::Selectable( + absl::StrFormat("%06X: %s %s", entry.address, + opcode_to_mnemonic.at(entry.opcode), + entry.operands) + .c_str())) { // Logic to handle click (e.g., jump to address, set breakpoint) } } diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index 77c9dc5a..12473614 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -1,6 +1,8 @@ #ifndef YAZE_APP_CORE_EMULATOR_H #define YAZE_APP_CORE_EMULATOR_H +#include + #include #include @@ -13,39 +15,34 @@ namespace emu { class Emulator : public SharedROM { public: - // Runs the emulator loop, including event handling and rendering void Run(); private: - // Renders the emulator output to an ImGui child window - void RenderEmulator(); - - // Draws the navigation bar with various controls void RenderNavBar(); - - // Handles user input events void HandleEvents(); - // Updates the emulator state (CPU, PPU, APU, etc.) - void UpdateEmulator(); - - void RenderDebugger(); + void RenderEmulator(); + void RenderSnesPpu(); void RenderBreakpointList(); void RenderCpuState(CPU& cpu); void RenderMemoryViewer(); - void RenderCPUInstructionLog( + struct Bookmark { + std::string name; + uint64_t value; + }; + std::vector bookmarks; + + void RenderCpuInstructionLog( const std::vector& instructionLog); SNES snes_; + uint16_t manual_pc_ = 0; + uint8_t manual_pb_ = 0; bool power_ = false; bool loading_ = false; bool running_ = false; - bool debugger_ = true; - bool memory_setup_ = false; - bool integrated_debugger_mode_ = true; - bool separate_debugger_mode_ = false; }; } // namespace emu diff --git a/src/app/emu/internal/asm_parser.h b/src/app/emu/internal/asm_parser.h new file mode 100644 index 00000000..48c765bb --- /dev/null +++ b/src/app/emu/internal/asm_parser.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "app/emu/internal/opcodes.h" + +namespace yaze { +namespace app { +namespace emu { + +class AsmParser { + public: + std::vector Parse(const std::string& instruction) { + std::smatch match; + if (!std::regex_match(instruction, match, instruction_regex_)) { + throw std::runtime_error("Invalid instruction format: " + instruction); + } + + std::string mnemonic = match[1]; + std::string addressing_mode = match[2]; + std::string operand = match[3]; + + std::string lookup_string = mnemonic.substr(0, 3); + + auto opcode_entry = mnemonic_to_opcode_.find(mnemonic); + if (opcode_entry == mnemonic_to_opcode_.end()) { + throw std::runtime_error( + "Unknown mnemonic or addressing mode: " + mnemonic + addressing_mode); + } + + std::vector bytes = {opcode_entry->second}; + // AppendOperandBytes(bytes, operand, addressing_mode); + + return bytes; + } + + void CreateInternalOpcodeMap() { + for (const auto& opcode_entry : opcode_to_mnemonic) { + std::string name = opcode_entry.second; + uint8_t opcode = opcode_entry.first; + mnemonic_to_opcode_[name] = opcode; + } + } + + private: + void AppendOperandBytes(std::vector& bytes, + const std::string& operand, + const std::string& addressing_mode) { + if (addressing_mode == ".b") { + bytes.push_back(static_cast(std::stoi(operand, nullptr, 16))); + } else if (addressing_mode == ".w") { + uint16_t word_operand = + static_cast(std::stoi(operand, nullptr, 16)); + bytes.push_back(static_cast(word_operand & 0xFF)); + bytes.push_back(static_cast((word_operand >> 8) & 0xFF)); + } else if (addressing_mode == ".l") { + uint32_t long_operand = + static_cast(std::stoul(operand, nullptr, 16)); + bytes.push_back(static_cast(long_operand & 0xFF)); + bytes.push_back(static_cast((long_operand >> 8) & 0xFF)); + bytes.push_back(static_cast((long_operand >> 16) & 0xFF)); + } + } + + enum class AddressingMode { + kAbsolute, + kAbsoluteLong, + kAbsoluteIndexedIndirect, + kAbsoluteIndexedX, + kAbsoluteIndexedY, + kAbsoluteIndirect, + kAbsoluteIndirectLong, + kAbsoluteLongIndexedX, + kAccumulator, + kBlockMove, + kDirectPage, + kDirectPageIndexedX, + kDirectPageIndexedY, + kDirectPageIndirect, + kDirectPageIndirectIndexedY, + kDirectPageIndirectLong, + kDirectPageIndirectLongIndexedY, + kDirectPageIndirectIndexedX, + kDirectPageIndirectLongIndexedX, + kImmediate, + kImplied, + kProgramCounterRelative, + kProgramCounterRelativeLong, + kStackRelative, + kStackRelativeIndirectIndexedY, + kStackRelativeIndirectIndexedYLong, + kStack, + kStackRelativeIndexedY, + }; + + AddressingMode InferAddressingModeFromOperand(const std::string& operand) { + if (operand[0] == '$') { + return AddressingMode::kAbsolute; + } else if (operand[0] == '#') { + return AddressingMode::kImmediate; + } else { + return AddressingMode::kImplied; + } + } + + const std::regex instruction_regex_{R"((\w+)\s*(\.\w)?\s*(\$\w+|\#\w+|\w+))"}; + std::unordered_map mnemonic_to_opcode_; +}; + +} // namespace emu +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/memory/memory.cc b/src/app/emu/memory/memory.cc new file mode 100644 index 00000000..fa2cbb6b --- /dev/null +++ b/src/app/emu/memory/memory.cc @@ -0,0 +1,82 @@ +#include "app/emu/memory/memory.h" + +#include + +#include +#include +#include +#include + +#include "app/emu/debug/log.h" + +namespace yaze { +namespace app { +namespace emu { + +void DrawSnesMemoryMapping(const MemoryImpl& memory) { + // Using those as a base value to create width/height that are factor of the + // size of our font + const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + const char* column_names[] = { + "Offset", "0x00", "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07", + "0x08", "0x09", "0x0A", "0x0B", "0x0C", "0x0D", "0x0E", "0x0F", "0x10", + "0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "0x17", "0x18", "0x19", + "0x1A", "0x1B", "0x1C", "0x1D", "0x1E", "0x1F"}; + const int columns_count = IM_ARRAYSIZE(column_names); + const int rows_count = 16; + + static ImGuiTableFlags table_flags = + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | + ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable | + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | + ImGuiTableFlags_HighlightHoveredColumn; + static bool bools[columns_count * rows_count] = {}; + static int frozen_cols = 1; + static int frozen_rows = 2; + ImGui::CheckboxFlags("_ScrollX", &table_flags, ImGuiTableFlags_ScrollX); + ImGui::CheckboxFlags("_ScrollY", &table_flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("_NoBordersInBody", &table_flags, + ImGuiTableFlags_NoBordersInBody); + ImGui::CheckboxFlags("_HighlightHoveredColumn", &table_flags, + ImGuiTableFlags_HighlightHoveredColumn); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::SliderInt("Frozen columns", &frozen_cols, 0, 2); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2); + + if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags, + ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) { + ImGui::TableSetupColumn( + column_names[0], + ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder); + for (int n = 1; n < columns_count; n++) + ImGui::TableSetupColumn(column_names[n], + ImGuiTableColumnFlags_AngledHeader | + ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows); + + ImGui::TableAngledHeadersRow(); + ImGui::TableHeadersRow(); + for (int row = 0; row < rows_count; row++) { + ImGui::PushID(row); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Offset 0x%04X", row); + for (int column = 1; column < columns_count; column++) + if (ImGui::TableSetColumnIndex(column)) { + ImGui::PushID(column); + ImGui::Checkbox("", &bools[row * columns_count + column]); + ImGui::PopID(); + } + ImGui::PopID(); + } + ImGui::EndTable(); + } +} + +} // namespace emu +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/memory/memory.h b/src/app/emu/memory/memory.h index 55e7d078..d9ca217c 100644 --- a/src/app/emu/memory/memory.h +++ b/src/app/emu/memory/memory.h @@ -96,8 +96,8 @@ class Observer { virtual void Notify(uint32_t address, uint8_t data) = 0; }; -constexpr uint32_t kROMStart = 0xC00000; -constexpr uint32_t kROMSize = 0x400000; +constexpr uint32_t kROMStart = 0x008000; +constexpr uint32_t kROMSize = 0x200000; constexpr uint32_t kRAMStart = 0x7E0000; constexpr uint32_t kRAMSize = 0x20000; constexpr uint32_t kVRAMStart = 0x210000; @@ -163,7 +163,7 @@ class MemoryImpl : public Memory, public Loggable { // 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 bank = 0x00; bank <= 0x3F; ++bank) { for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) { if (romAddress < romSize) { std::copy(romData.begin() + romAddress, @@ -360,7 +360,8 @@ class MemoryImpl : public Memory, public Loggable { } else if (offset <= 0x7FFF) { return offset - 0x6000 + 0x6000; // Expansion RAM } else { - return (bank << 15) + (offset - 0x8000) + 0x8000; // ROM + // Return lorom mapping + return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM } } else if (bank == 0x7D) { return offset + 0x7D0000; // SRAM @@ -390,6 +391,8 @@ class MemoryImpl : public Memory, public Loggable { MemoryMapping mapping_ = MemoryMapping::SNES_LOROM; }; +void DrawSnesMemoryMapping(const MemoryImpl& memory); + } // namespace emu } // namespace app } // namespace yaze diff --git a/src/app/emu/memory/mock_memory.h b/src/app/emu/memory/mock_memory.h index 6d84b114..b43508b6 100644 --- a/src/app/emu/memory/mock_memory.h +++ b/src/app/emu/memory/mock_memory.h @@ -48,7 +48,7 @@ class MockMemory : public Memory { MOCK_METHOD0(ClearMemory, void()); MOCK_CONST_METHOD1(at, uint8_t(int i)); - uint8_t operator[](int i) const override { return at(i); } + uint8_t operator[](int i) const override { return memory_[i]; } void SetMemoryContents(const std::vector& data) { memory_.resize(64000); diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index 3b07221b..b734ddab 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -28,8 +28,8 @@ uint16_t GetHeaderOffset(const Memory& memory) { switch (mapMode & 0x07) { case 0: // LoROM - // offset = 0x7F; - offset = 0xFFC0; + offset = 0x7FC0; + // offset = 0xFFC0; break; case 1: // HiROM offset = 0xFFC0; @@ -110,13 +110,14 @@ ROMInfo SNES::ReadRomHeader(uint32_t offset) { void SNES::Init(ROM& rom) { // Perform a long jump into a FastROM bank (if the ROM speed is FastROM) // Disable the emulation flag (switch to 65816 native mode) + cpu_.E = 0; // Initialize CPU cpu_.Init(); // Read the ROM header auto header_offset = GetHeaderOffset(memory_); - rom_info_ = ReadRomHeader(header_offset); + rom_info_ = ReadRomHeader((0x00 << 16) + header_offset); cpu_.PC = rom_info_.resetVector; // Initialize PPU @@ -215,8 +216,6 @@ void SNES::Init(ROM& rom) { } void SNES::Run() { - running_ = true; - const double targetFPS = 60.0; // 60 frames per second const double frame_time = 1.0 / targetFPS; double frame_accumulated_time = 0.0; diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index 03fd5248..70419d67 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -78,7 +78,8 @@ class SNES : public DMA { memory_.AddObserver(&ppu_); // Load the ROM into memory and set up the memory mapping - memory_.Initialize(rom.vector()); + rom_data = rom.vector(); + memory_.Initialize(rom_data); } private: diff --git a/src/app/emu/video/ppu.cc b/src/app/emu/video/ppu.cc index 80c9efc0..c0f6bb26 100644 --- a/src/app/emu/video/ppu.cc +++ b/src/app/emu/video/ppu.cc @@ -56,7 +56,7 @@ void Ppu::RenderScanline() { // Fetch the tile data from VRAM, tile map data from memory, and palette data // from CGRAM - UpdateTileData(); // Fetches the tile data from VRAM and stores it in an + // UpdateTileData(); // Fetches the tile data from VRAM and stores it in an // internal buffer UpdateTileMapData(); // Fetches the tile map data from memory and stores it // in an internal buffer diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ec029eb..59f786d3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -13,9 +13,9 @@ enable_testing() add_executable( yaze_test yaze_test.cc - z3ed_test.cc emu/cpu_test.cc emu/spc700_test.cc + emu/ppu_test.cc compression_test.cc snes_palette_test.cc room_object_test.cc diff --git a/test/emu/cpu_test.cc b/test/emu/cpu_test.cc index b83f8fff..9c081785 100644 --- a/test/emu/cpu_test.cc +++ b/test/emu/cpu_test.cc @@ -4,6 +4,8 @@ #include #include "app/emu/clock.h" +#include "app/emu/internal/asm_parser.h" +#include "app/emu/internal/opcodes.h" #include "app/emu/memory/memory.h" #include "app/emu/memory/mock_memory.h" @@ -17,11 +19,13 @@ class CPUTest : public ::testing::Test { mock_memory.Init(); EXPECT_CALL(mock_memory, ClearMemory()).Times(::testing::AtLeast(1)); mock_memory.ClearMemory(); + asm_parser.CreateInternalOpcodeMap(); } MockMemory mock_memory; MockClock mock_clock; CPU cpu{mock_memory, mock_clock}; + AsmParser asm_parser; }; using ::testing::_; @@ -80,7 +84,6 @@ TEST_F(CPUTest, ADC_Immediate_PositiveAndNegativeNumbers) { TEST_F(CPUTest, ADC_Absolute) { cpu.A = 0x01; - cpu.PC = 1; // PC register cpu.status = 0x00; // 16-bit mode std::vector data = {0x6D, 0x03, 0x00, 0x05, 0x00}; mock_memory.SetMemoryContents(data); @@ -95,32 +98,18 @@ TEST_F(CPUTest, ADC_Absolute) { TEST_F(CPUTest, ADC_AbsoluteLong) { cpu.A = 0x01; - cpu.PC = 1; // PC register - cpu.status = 0x00; // 16-bit mode + cpu.SetAccumulatorSize(false); // 16-bit mode + cpu.SetCarryFlag(false); std::vector data = {0x6F, 0x04, 0x00, 0x00, 0x05, 0x00}; mock_memory.SetMemoryContents(data); EXPECT_CALL(mock_memory, ReadWordLong(0x0001)).WillOnce(Return(0x0004)); - EXPECT_CALL(mock_memory, ReadWord(0x0004)).WillOnce(Return(0x0005)); cpu.ExecuteInstruction(0x6F); // ADC Absolute Long EXPECT_EQ(cpu.A, 0x06); } -TEST_F(CPUTest, ADC_DirectPage) { - cpu.A = 0x01; - cpu.D = 0x0001; - std::vector data = {0x65, 0x01, 0x00}; - mock_memory.SetMemoryContents(data); - mock_memory.InsertMemory(0x100, {0x05, 0x05, 0x05}); - - EXPECT_CALL(mock_memory, ReadByte(0x100)).WillOnce(Return(0x05)); - - cpu.ExecuteInstruction(0x65); // ADC Direct Page - EXPECT_EQ(cpu.A, 0x06); -} - // ADC Direct Page Indirect TEST_F(CPUTest, ADC_DirectPageIndirect) { cpu.A = 0x02; @@ -130,7 +119,7 @@ TEST_F(CPUTest, ADC_DirectPageIndirect) { mock_memory.InsertMemory(0x2010, {0x00, 0x30}); // [0x2010] = 0x3000 mock_memory.InsertMemory(0x3000, {0x05}); // [0x3000] = 0x05 - EXPECT_CALL(mock_memory, ReadByte(0x0000)).WillOnce(Return(0x10)); + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(Return(0x10)); EXPECT_CALL(mock_memory, ReadWord(0x2010)).WillOnce(Return(0x3000)); EXPECT_CALL(mock_memory, ReadByte(0x3000)).WillOnce(Return(0x05)); @@ -148,7 +137,7 @@ TEST_F(CPUTest, ADC_DirectPageIndexedIndirectX) { mock_memory.InsertMemory(0x3000, {0x06}); // [0x3000] = 0x06 cpu.X = 0x02; // X register - EXPECT_CALL(mock_memory, ReadByte(0x0000)).WillOnce(Return(0x10)); + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(Return(0x10)); EXPECT_CALL(mock_memory, ReadWord(0x2012)).WillOnce(Return(0x3000)); EXPECT_CALL(mock_memory, ReadByte(0x3000)).WillOnce(Return(0x06)); @@ -173,7 +162,6 @@ TEST_F(CPUTest, ADC_CheckCarryFlag) { 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}; @@ -189,7 +177,6 @@ TEST_F(CPUTest, ADC_AbsoluteIndexedX) { TEST_F(CPUTest, ADC_AbsoluteIndexedY) { cpu.A = 0x03; cpu.Y = 0x02; // Y register - cpu.PC = 0x0001; std::vector data = {0x79, 0x03, 0x00, 0x00, 0x05, 0x00}; mock_memory.SetMemoryContents(data); @@ -208,7 +195,7 @@ TEST_F(CPUTest, ADC_DirectPageIndexedY) { mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x2012, {0x06}); - EXPECT_CALL(mock_memory, ReadByte(0x0000)).WillOnce(Return(0x10)); + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(Return(0x10)); EXPECT_CALL(mock_memory, ReadWordLong(0x2012)).WillOnce(Return(0x06)); cpu.ExecuteInstruction(0x77); // ADC Direct Page Indexed Y @@ -236,7 +223,6 @@ TEST_F(CPUTest, ADC_DirectPageIndirectLong) { TEST_F(CPUTest, ADC_StackRelative) { cpu.A = 0x03; - cpu.PC = 0x0001; cpu.SetSP(0x01FF); // Setting Stack Pointer to 0x01FF std::vector data = {0x63, 0x02}; // ADC sr mock_memory.SetMemoryContents(data); @@ -256,10 +242,8 @@ TEST_F(CPUTest, ADC_StackRelative) { // AND - Logical AND TEST_F(CPUTest, AND_Immediate) { - cpu.PC = 0; - cpu.status = 0xFF; // 8-bit mode - cpu.A = 0b11110000; // A register - std::vector data = {0b10101010}; // AND #0b10101010 + cpu.A = 0b11110000; // A register + std::vector data = {0x29, 0b10101010}; // AND #0b10101010 mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x29); // AND Immediate @@ -270,7 +254,6 @@ TEST_F(CPUTest, AND_Absolute_16BitMode) { cpu.A = 0b11111111; // A register cpu.E = 0; // 16-bit mode cpu.status = 0x00; // Clear status flags - cpu.PC = 1; // PC register std::vector data = {0x2D, 0x03, 0x00, 0b10101010, 0x01, 0x02}; mock_memory.SetMemoryContents(data); @@ -288,14 +271,14 @@ TEST_F(CPUTest, AND_Absolute_16BitMode) { TEST_F(CPUTest, AND_AbsoluteLong) { cpu.A = 0x01; - cpu.PC = 1; // PC register + // PC register cpu.status = 0x00; // 16-bit mode std::vector data = {0x2F, 0x04, 0x00, 0x00, 0x05, 0x00}; mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadWordLong(0x0001)).WillOnce(Return(0x0004)); + EXPECT_CALL(mock_memory, ReadWordLong(0x0001)).WillOnce(Return(0x000004)); - EXPECT_CALL(mock_memory, ReadWordLong(0x0004)).WillOnce(Return(0x0005)); + EXPECT_CALL(mock_memory, ReadWordLong(0x0004)).WillOnce(Return(0x000005)); cpu.ExecuteInstruction(0x2F); // ADC Absolute Long EXPECT_EQ(cpu.A, 0x01); @@ -314,8 +297,6 @@ TEST_F(CPUTest, AND_IndexedIndirect) { TEST_F(CPUTest, AND_AbsoluteIndexedX) { cpu.A = 0b11110000; // A register cpu.X = 0x02; // X register - cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x3D, 0x03, 0x00, 0b00000000, 0b10101010, 0b01010101}; mock_memory.SetMemoryContents(data); @@ -338,8 +319,6 @@ TEST_F(CPUTest, AND_AbsoluteIndexedX) { TEST_F(CPUTest, AND_AbsoluteIndexedY) { cpu.A = 0b11110000; // A register cpu.Y = 0x02; // Y register - cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x39, 0x03, 0x00, 0b00000000, 0b10101010, 0b01010101}; mock_memory.SetMemoryContents(data); @@ -363,7 +342,6 @@ TEST_F(CPUTest, AND_AbsoluteLongIndexedX) { cpu.A = 0b11110000; // A register cpu.X = 0x02; // X register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x3F, 0x03, 0x00, 0x00, 0b00000000, 0b10101010, 0b01010101}; mock_memory.SetMemoryContents(data); @@ -417,10 +395,13 @@ TEST_F(CPUTest, ASL_Absolute) { mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x2010, {0x40}); // [0x2010] = 0x40 + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x2010)); + EXPECT_CALL(mock_memory, ReadByte(0x2010)).WillOnce(Return(0x40)); + cpu.ExecuteInstruction(0x0E); // ASL Absolute EXPECT_TRUE(cpu.GetCarryFlag()); - EXPECT_FALSE(cpu.GetZeroFlag()); - EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_TRUE(cpu.GetZeroFlag()); + EXPECT_FALSE(cpu.GetNegativeFlag()); } TEST_F(CPUTest, ASL_DP_Indexed_X) { @@ -442,10 +423,13 @@ TEST_F(CPUTest, ASL_Absolute_Indexed_X) { mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x2012, {0x40}); // [0x2012] = 0x40 - cpu.ExecuteInstruction(0x1E); // ASL Absolute Indexed, X + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x2010)); + EXPECT_CALL(mock_memory, ReadByte(0x2012)).WillOnce(Return(0x40)); + + cpu.ExecuteInstruction(0x1E); // ASL Absolute, X EXPECT_TRUE(cpu.GetCarryFlag()); - EXPECT_FALSE(cpu.GetZeroFlag()); - EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_TRUE(cpu.GetZeroFlag()); + EXPECT_FALSE(cpu.GetNegativeFlag()); } // ============================================================================ @@ -465,15 +449,14 @@ TEST_F(CPUTest, BCC_WhenCarryFlagClear) { TEST_F(CPUTest, BCC_WhenCarryFlagSet) { cpu.SetCarryFlag(true); - cpu.PC = 0x1000; - std::vector data(0x1001, 2); // Operand at address 0x1001 + std::vector data = {0x90, 0x02, 0x01}; mock_memory.SetMemoryContents(data); EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(2)); cpu.ExecuteInstruction(0x90); // BCC - cpu.BCC(2); - EXPECT_EQ(cpu.PC, 0x1000); + + EXPECT_EQ(cpu.PC, 2); } // ============================================================================ @@ -493,30 +476,63 @@ TEST_F(CPUTest, BCS_WhenCarryFlagSet) { TEST_F(CPUTest, BCS_WhenCarryFlagClear) { cpu.SetCarryFlag(false); - cpu.PC = 0x1000; - std::vector data = {0x10, 0x02, 0x01}; // Operand at address 0x1001 + std::vector data = {0x10, 0x02, 0x01}; mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(2)); - cpu.ExecuteInstruction(0xB0); // BCS - cpu.BCS(2); - EXPECT_EQ(cpu.PC, 0x1000); + EXPECT_EQ(cpu.PC, 2); } // ============================================================================ // BEQ - Branch if Equal -TEST_F(CPUTest, BEQ) { +TEST_F(CPUTest, BEQ_Immediate_ZeroFlagSet) { + cpu.PB = 0x00; cpu.SetZeroFlag(true); - cpu.PC = 0x1000; + std::vector data = {0xF0, 0x09}; // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xF0); // BEQ + + EXPECT_EQ(cpu.PC, 0x09); +} + +TEST_F(CPUTest, BEQ_Immediate_ZeroFlagClear) { + cpu.SetZeroFlag(false); + std::vector data = {0xF0, 0x03}; // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x03)); + + cpu.ExecuteInstruction(0xF0); // BEQ + + EXPECT_EQ(cpu.PC, 0x02); +} + +TEST_F(CPUTest, BEQ_Immediate_ZeroFlagSet_OverflowFlagSet) { + cpu.SetZeroFlag(true); + cpu.SetOverflowFlag(true); + std::vector data = {0xF0, 0x03}; // Operand at address 0x1001 + mock_memory.SetMemoryContents(data); + + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x03)); + + cpu.ExecuteInstruction(0xF0); // BEQ + + EXPECT_EQ(cpu.PC, 0x03); +} + +TEST_F(CPUTest, BEQ_Immediate_ZeroFlagClear_OverflowFlagSet) { + cpu.SetZeroFlag(false); + cpu.SetOverflowFlag(true); 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); + + EXPECT_EQ(cpu.PC, 0x02); } // ============================================================================ @@ -524,19 +540,25 @@ TEST_F(CPUTest, BEQ) { TEST_F(CPUTest, BIT_Immediate) { cpu.A = 0x01; - cpu.PC = 0x0001; cpu.status = 0xFF; - std::vector data = {0x00, 0x10}; // BIT + std::vector data = {0x24, 0x00, 0x10}; // BIT mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0010, {0x81}); // [0x0010] = 0x81 - cpu.ExecuteInstruction(0x89); // BIT + // Read the operand + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(Return(0x10)); + + // Read the value at the address of the operand + EXPECT_CALL(mock_memory, ReadByte(0x0010)).WillOnce(Return(0x81)); + + cpu.ExecuteInstruction(0x24); // BIT + EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_FALSE(cpu.GetOverflowFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, BIT_Absolute) { cpu.A = 0x01; - cpu.PC = 0x0001; cpu.status = 0xFF; std::vector data = {0x00, 0x10}; // BIT mock_memory.SetMemoryContents(data); @@ -557,7 +579,6 @@ TEST_F(CPUTest, BIT_Absolute) { TEST_F(CPUTest, BIT_AbsoluteIndexedX) { cpu.A = 0x01; cpu.X = 0x02; - cpu.PC = 0x0001; cpu.status = 0xFF; std::vector data = {0x00, 0x10}; // BIT mock_memory.SetMemoryContents(data); @@ -579,32 +600,29 @@ TEST_F(CPUTest, BIT_AbsoluteIndexedX) { // BMI - Branch if Minus TEST_F(CPUTest, BMI_BranchTaken) { - cpu.PC = 0x0000; cpu.SetNegativeFlag(true); - std::vector data = {0x02}; // BMI + std::vector data = {0x30, 0x05}; // BMI + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x30); // BMI + EXPECT_EQ(cpu.PC, 0x0005); +} + +TEST_F(CPUTest, BMI_BranchNotTaken) { + cpu.SetNegativeFlag(false); + std::vector data = {0x30, 0x02}; // BMI mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x30); // BMI EXPECT_EQ(cpu.PC, 0x0002); } -TEST_F(CPUTest, BMI_BranchNotTaken) { - cpu.PC = 0x0000; - cpu.SetNegativeFlag(false); - std::vector data = {0x30, 0x02}; // BMI - mock_memory.SetMemoryContents(data); - - cpu.ExecuteInstruction(0x30); // BMI - EXPECT_EQ(cpu.PC, 0x0000); -} - // ============================================================================ // BNE - Branch if Not Equal TEST_F(CPUTest, BNE_BranchTaken) { - cpu.PC = 0x0000; cpu.SetZeroFlag(false); - std::vector data = {0x02}; // BNE + std::vector data = {0xD0, 0x02}; // BNE mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0xD0); // BNE @@ -612,44 +630,40 @@ TEST_F(CPUTest, BNE_BranchTaken) { } TEST_F(CPUTest, BNE_BranchNotTaken) { - cpu.PC = 0x0000; cpu.SetZeroFlag(true); - std::vector data = {0xD0, 0x02}; // BNE + std::vector data = {0xD0, 0x05}; // BNE mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0xD0); // BNE - EXPECT_EQ(cpu.PC, 0x0000); + EXPECT_EQ(cpu.PC, 0x0002); } // ============================================================================ // BPL - Branch if Positive TEST_F(CPUTest, BPL_BranchTaken) { - cpu.PC = 0x0000; cpu.SetNegativeFlag(false); - std::vector data = {0x02}; // BPL + std::vector data = {0x10, 0x07}; // BPL + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0x10); // BPL + EXPECT_EQ(cpu.PC, 0x0007); +} + +TEST_F(CPUTest, BPL_BranchNotTaken) { + cpu.SetNegativeFlag(true); + std::vector data = {0x10, 0x02}; // BPL mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x10); // BPL EXPECT_EQ(cpu.PC, 0x0002); } -TEST_F(CPUTest, BPL_BranchNotTaken) { - cpu.PC = 0x0000; - cpu.SetNegativeFlag(true); - std::vector data = {0x10, 0x02}; // BPL - mock_memory.SetMemoryContents(data); - - cpu.ExecuteInstruction(0x10); // BPL - EXPECT_EQ(cpu.PC, 0x0000); -} - // ============================================================================ // BRA - Branch Always TEST_F(CPUTest, BRA) { - cpu.PC = 0x0000; - std::vector data = {0x02}; // BRA + std::vector data = {0x80, 0x02}; // BRA mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x80); // BRA @@ -657,7 +671,6 @@ TEST_F(CPUTest, BRA) { } TEST_F(CPUTest, BRK) { - cpu.PC = 0x0000; std::vector data = {0x00}; // BRK mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0xFFFE, {0x10, 0x20}); // [0xFFFE] = 0x2010 @@ -673,23 +686,21 @@ TEST_F(CPUTest, BRK) { // BRL - Branch Long TEST_F(CPUTest, BRL) { - cpu.PC = 0x1000; - std::vector data(0x1001, 2); // Operand at address 0x1001 + std::vector data = {0x82, 0x10, 0x20}; // BRL mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadWord(_)).WillOnce(Return(2)); + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x2010)); cpu.ExecuteInstruction(0x82); // BRL - EXPECT_EQ(cpu.PC, 0x1004); + EXPECT_EQ(cpu.PC, 0x2010); } // ============================================================================ // BVC - Branch if Overflow Clear TEST_F(CPUTest, BVC_BranchTaken) { - cpu.PC = 0x0000; cpu.SetOverflowFlag(false); - std::vector data = {0x02}; // BVC + std::vector data = {0x50, 0x02}; // BVC mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x50); // BVC @@ -700,9 +711,8 @@ TEST_F(CPUTest, BVC_BranchTaken) { // BVS - Branch if Overflow Set TEST_F(CPUTest, BVS_BranchTaken) { - cpu.PC = 0x0000; cpu.SetOverflowFlag(true); - std::vector data = {0x02}; // BVS + std::vector data = {0x70, 0x02}; // BVS mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x70); // BVS @@ -772,7 +782,7 @@ TEST_F(CPUTest, CMP_Immediate_8Bit) { 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)); + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(::testing::Return(0x40)); // Execute the CMP Immediate instruction cpu.ExecuteInstruction(0xC9); @@ -811,7 +821,7 @@ TEST_F(CPUTest, CPX_ZeroFlagSet) { cpu.SetIndexSize(false); // Set X register to 16-bit mode cpu.SetAccumulatorSize(false); cpu.X = 0x1234; - std::vector data = {0x34, 0x12}; // CPX #0x1234 + std::vector data = {0xE0, 0x34, 0x12}; // CPX #0x1234 mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0xE0); // Immediate CPX ASSERT_TRUE(cpu.GetZeroFlag()); // Zero flag should be set @@ -838,7 +848,7 @@ TEST_F(CPUTest, CPY_ZeroFlagSet) { cpu.SetIndexSize(false); // Set Y register to 16-bit mode cpu.SetAccumulatorSize(false); cpu.Y = 0x5678; - std::vector data = {0x78, 0x56}; // CPY #0x5678 + std::vector data = {0xC0, 0x78, 0x56}; // CPY #0x5678 mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0xC0); // Immediate CPY ASSERT_TRUE(cpu.GetZeroFlag()); // Zero flag should be set @@ -894,7 +904,6 @@ TEST_F(CPUTest, DEY) { TEST_F(CPUTest, EOR_Immediate_8bit) { cpu.A = 0b10101010; // A register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x49, 0b01010101}; mock_memory.SetMemoryContents(data); @@ -906,7 +915,6 @@ TEST_F(CPUTest, EOR_DirectPageIndexedIndirectX) { cpu.A = 0b10101010; // A register cpu.X = 0x02; // X register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x41, 0x7E}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0080, {0x00, 0x10}); // [0x0080] = 0x1000 @@ -919,7 +927,6 @@ TEST_F(CPUTest, EOR_DirectPageIndexedIndirectX) { TEST_F(CPUTest, EOR_DirectPage) { cpu.A = 0b10101010; // A register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x45, 0x7F}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007F, {0b01010101}); // [0x007F] = 0b01010101 @@ -931,7 +938,6 @@ TEST_F(CPUTest, EOR_DirectPage) { TEST_F(CPUTest, EOR_DirectPageIndirectLong) { cpu.A = 0b10101010; // A register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x47, 0x7F}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007F, {0x00, 0x10, 0x00}); // [0x007F] = 0x1000 @@ -944,7 +950,6 @@ TEST_F(CPUTest, EOR_DirectPageIndirectLong) { TEST_F(CPUTest, EOR_Absolute) { cpu.A = 0b10101010; // A register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x4D, 0x00, 0x10}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x1000, {0b01010101}); // [0x1000] = 0b01010101 @@ -956,7 +961,6 @@ TEST_F(CPUTest, EOR_Absolute) { TEST_F(CPUTest, EOR_AbsoluteLong) { cpu.A = 0b10101010; // A register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x4F, 0x00, 0x10, 0x00}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x1000, {0b01010101}); // [0x1000] = 0b01010101 @@ -969,7 +973,6 @@ TEST_F(CPUTest, EOR_DirectPageIndirectLongIndexedY) { cpu.A = 0b10101010; // A register cpu.Y = 0x02; // Y register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x51, 0x7E}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007E, {0x00, 0x10, 0x00}); // [0x007E] = 0x1000 @@ -983,7 +986,6 @@ TEST_F(CPUTest, EOR_DirectPageIndirectIndexedY) { cpu.A = 0b10101010; // A register cpu.Y = 0x02; // Y register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register std::vector data = {0x51, 0x7E}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007E, {0x00, 0x10}); // [0x007E] = 0x1000 @@ -997,7 +999,7 @@ TEST_F(CPUTest, EOR_DirectPageIndirectIndexedY) { // cpu.A = 0b10101010; // A register // cpu.Y = 0x02; // Y register // cpu.status = 0xFF; // 8-bit mode -// cpu.PC = 1; // PC register +// // PC register // std::vector data = {0x57, 0x7C}; // mock_memory.SetMemoryContents(data); // mock_memory.InsertMemory(0x007E, {0x00, 0x10, 0x00}); // [0x007E] = 0x1000 @@ -1012,7 +1014,7 @@ TEST_F(CPUTest, EOR_AbsoluteIndexedX) { cpu.A = 0b10101010; // A register cpu.X = 0x02; // X register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register + // PC register std::vector data = {0x5D, 0x7C, 0x00}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007E, {0b01010101}); // [0x007E] = 0b01010101 @@ -1025,7 +1027,7 @@ TEST_F(CPUTest, EOR_AbsoluteIndexedY) { cpu.A = 0b10101010; // A register cpu.Y = 0x02; // Y register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register + // PC register std::vector data = {0x59, 0x7C, 0x00}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007E, {0b01010101}); // [0x007E] = 0b01010101 @@ -1038,7 +1040,7 @@ TEST_F(CPUTest, EOR_AbsoluteLongIndexedX) { cpu.A = 0b10101010; // A register cpu.X = 0x02; // X register cpu.status = 0xFF; // 8-bit mode - cpu.PC = 1; // PC register + // PC register std::vector data = {0x5F, 0x7C, 0x00, 0x00}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007E, {0b01010101}); // [0x007E] = 0b01010101 @@ -1050,42 +1052,39 @@ TEST_F(CPUTest, EOR_AbsoluteLongIndexedX) { // ============================================================================ // INC - Increment Memory -/** TEST_F(CPUTest, INC_DirectPage_8bit) { - cpu.PC = 0x1001; - std::vector data = {0xE6, 0x7F, 0x7F}; - mock_memory.SetMemoryContents(data); - - EXPECT_CALL(mock_memory, ReadByte(0x7F)).WillOnce(Return(0x7F)); - EXPECT_CALL(mock_memory, WriteByte(0, 0x80)).Times(1); - cpu.SetAccumulatorSize(true); + cpu.D = 0x0200; // Setting Direct Page register to 0x0200 + std::vector data = {0xE6, 0x20}; + mock_memory.SetMemoryContents(data); + mock_memory.InsertMemory(0x0220, {0x40}); // [0x0220] = 0x40 + + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(Return(0x20)); + EXPECT_CALL(mock_memory, ReadByte(0x0220)).WillOnce(Return(0x40)); + cpu.ExecuteInstruction(0xE6); // INC Direct Page - EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_EQ(mock_memory[0x0220], 0x41); + EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, INC_Absolute_16bit) { - cpu.PC = 0x1001; - std::vector data = {0xEE, 0x7F, 0xFF}; + std::vector data = {0xEE, 0x00, 0x10}; mock_memory.SetMemoryContents(data); - - EXPECT_CALL(mock_memory, ReadWord(0xFF7F)).WillOnce(Return(0x7FFF)); - EXPECT_CALL(mock_memory, WriteWord(0xFF7F, 0x8000)).Times(1); + mock_memory.InsertMemory(0x1000, {0x40}); // [0x1000] = 0x40 cpu.SetAccumulatorSize(false); cpu.ExecuteInstruction(0xEE); // INC Absolute - EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_EQ(mock_memory[0x1000], 0x41); + EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, INC_DirectPage_ZeroResult_8bit) { - cpu.PC = 0x1001; - std::vector data = {0xE6, 0xFF}; + cpu.D = 0x0200; // Setting Direct Page register to 0x0200 + std::vector data = {0xE6, 0x20}; mock_memory.SetMemoryContents(data); - - EXPECT_CALL(mock_memory, ReadByte(0xFF)).WillOnce(Return(0xFF)); - EXPECT_CALL(mock_memory, WriteByte(0xFF, 0x00)).Times(1); + mock_memory.InsertMemory(0x0220, {0xFF}); // [0x0220] = 0xFF cpu.SetAccumulatorSize(true); cpu.ExecuteInstruction(0xE6); // INC Direct Page @@ -1094,63 +1093,54 @@ TEST_F(CPUTest, INC_DirectPage_ZeroResult_8bit) { } TEST_F(CPUTest, INC_Absolute_ZeroResult_16bit) { - cpu.PC = 0x1001; - std::vector data = {0xEE, 0xFF, 0xFF}; + std::vector data = {0xEE, 0x00, 0x10}; mock_memory.SetMemoryContents(data); - - EXPECT_CALL(mock_memory, ReadWord(0xFFFF)).WillOnce(Return(0xFFFF)); - EXPECT_CALL(mock_memory, WriteWord(0xFFFF, 0x0000)).Times(1); + mock_memory.InsertMemory(0x1000, {0xFF}); // [0x1000] = 0xFF cpu.SetAccumulatorSize(false); cpu.ExecuteInstruction(0xEE); // INC Absolute EXPECT_FALSE(cpu.GetNegativeFlag()); - EXPECT_TRUE(cpu.GetZeroFlag()); + EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, INC_DirectPage_8bit_Overflow) { - cpu.PC = 0x1001; std::vector data = {0xE6, 0x80}; mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadByte(0x80)).WillOnce(Return(0xFF)); - EXPECT_CALL(mock_memory, WriteByte(0x80, 0x00)).Times(1); - cpu.SetAccumulatorSize(true); cpu.ExecuteInstruction(0xE6); // INC Direct Page EXPECT_FALSE(cpu.GetNegativeFlag()); - EXPECT_TRUE(cpu.GetZeroFlag()); + EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, INC_DirectPageIndexedX_8bit) { - cpu.PC = 0x1001; cpu.X = 0x01; - std::vector data = {0xF6, 0x7E}; + cpu.D = 0x0200; // Setting Direct Page register to 0x0200 + std::vector data = {0xF6, 0x20}; mock_memory.SetMemoryContents(data); + mock_memory.InsertMemory(0x0221, {0x40}); // [0x0221] = 0x40 - EXPECT_CALL(mock_memory, ReadByte(0x7F)).WillOnce(Return(0x7F)); - EXPECT_CALL(mock_memory, WriteByte(0x7F, 0x80)).Times(1); + EXPECT_CALL(mock_memory, ReadByte(0x0001)).WillOnce(Return(0x20)); + EXPECT_CALL(mock_memory, ReadByte(0x0221)).WillOnce(Return(0x40)); - cpu.SetAccumulatorSize(true); - cpu.ExecuteInstruction(0xF6); // INC DP Indexed, X - EXPECT_TRUE(cpu.GetNegativeFlag()); + cpu.ExecuteInstruction(0xF6); // INC Direct Page Indexed, X + EXPECT_EQ(mock_memory[0x0221], 0x41); + EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, INC_AbsoluteIndexedX_16bit) { - cpu.PC = 0x1001; cpu.X = 0x01; - std::vector data = {0xFE, 0x7F, 0xFF}; + std::vector data = {0xFE, 0x00, 0x10}; mock_memory.SetMemoryContents(data); - - EXPECT_CALL(mock_memory, ReadWord(0xFF80)).WillOnce(Return(0x7FFF)); - EXPECT_CALL(mock_memory, WriteWord(0xFF80, 0x8000)).Times(1); + mock_memory.InsertMemory(0x1001, {0x40}); // [0x1001] = 0x40 cpu.SetAccumulatorSize(false); cpu.ExecuteInstruction(0xFE); // INC Absolute Indexed, X - EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_EQ(mock_memory[0x1001], 0x41); + EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } -*/ TEST_F(CPUTest, INX) { cpu.SetIndexSize(true); // Set X register to 8-bit mode @@ -1187,22 +1177,22 @@ TEST_F(CPUTest, INY) { // ============================================================================ TEST_F(CPUTest, JMP_Absolute) { - cpu.PC = 0x1001; std::vector data = {0x4C, 0x05, 0x20}; // JMP $2005 mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadWord(0x1001)).WillOnce(Return(0x2005)); + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x2005)); cpu.ExecuteInstruction(0x4C); // JMP Absolute - EXPECT_EQ(cpu.PC, 0x2005); + cpu.ExecuteInstruction(0xEA); // NOP + + EXPECT_EQ(cpu.PC, 0x2006); } TEST_F(CPUTest, JMP_Indirect) { - cpu.PC = 0x1001; std::vector data = {0x6C, 0x03, 0x20, 0x05, 0x30}; // JMP ($2003) mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadWord(0x1001)).WillOnce(Return(0x2003)); + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x2003)); EXPECT_CALL(mock_memory, ReadWord(0x2003)).WillOnce(Return(0x3005)); cpu.ExecuteInstruction(0x6C); // JMP Indirect @@ -1215,16 +1205,12 @@ TEST_F(CPUTest, JMP_Indirect) { TEST_F(CPUTest, JML_AbsoluteLong) { cpu.E = 0; - cpu.PC = 0x1001; - cpu.PB = 0x02; // Set the program bank register to 0x02 + std::vector data = {0x5C, 0x05, 0x00, 0x03}; // JML $030005 mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x030005, {0x00, 0x20, 0x00}); - // NOP to set PB to 0x02 - cpu.ExecuteInstruction(0xEA); - - EXPECT_CALL(mock_memory, ReadWordLong(0x1001)).WillOnce(Return(0x030005)); + EXPECT_CALL(mock_memory, ReadWordLong(0x0001)).WillOnce(Return(0x030005)); cpu.ExecuteInstruction(0x5C); // JML Absolute Long EXPECT_EQ(cpu.PC, 0x0005); @@ -1236,15 +1222,18 @@ TEST_F(CPUTest, JML_AbsoluteLong) { // ============================================================================ TEST_F(CPUTest, JSR_Absolute) { - cpu.PC = 0x1001; std::vector data = {0x20, 0x05, 0x20}; // JSR $2005 mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadWord(0x1001)).WillOnce(Return(0x2005)); - EXPECT_CALL(mock_memory, PushWord(0x1002)).Times(1); + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x2005)); + EXPECT_CALL(mock_memory, PushWord(0x0000)).Times(1); cpu.ExecuteInstruction(0x20); // JSR Absolute EXPECT_EQ(cpu.PC, 0x2005); + + // Continue executing some code + cpu.ExecuteInstruction(0x60); // RTS + EXPECT_EQ(cpu.PC, 0x0003); } // ============================================================================ @@ -1252,12 +1241,11 @@ TEST_F(CPUTest, JSR_Absolute) { // ============================================================================ TEST_F(CPUTest, JSL_AbsoluteLong) { - cpu.PC = 0x1001; std::vector data = {0x22, 0x05, 0x20, 0x00}; // JSL $002005 mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadWordLong(0x1001)).WillOnce(Return(0x002005)); - EXPECT_CALL(mock_memory, PushLong(0x1003)).Times(1); + EXPECT_CALL(mock_memory, ReadWordLong(0x0001)).WillOnce(Return(0x002005)); + EXPECT_CALL(mock_memory, PushLong(0x0000)).Times(1); cpu.ExecuteInstruction(0x22); // JSL Absolute Long EXPECT_EQ(cpu.PC, 0x002005); @@ -1266,23 +1254,18 @@ TEST_F(CPUTest, JSL_AbsoluteLong) { // ============================================================================ // LDA - Load Accumulator -/** TEST_F(CPUTest, LDA_Immediate_8bit) { - cpu.PC = 0x1001; cpu.SetAccumulatorSize(true); - cpu.A = 0x00; - std::vector data = {0xA9, 0x7F, 0x7F}; + std::vector data = {0xA9, 0xFF}; mock_memory.SetMemoryContents(data); - mock_memory.InsertMemory(0x7F, {0xAA}); cpu.ExecuteInstruction(0xA9); // LDA Immediate - EXPECT_EQ(cpu.A, 0x7F); + EXPECT_EQ(cpu.A, 0xFF); EXPECT_TRUE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } TEST_F(CPUTest, LDA_Immediate_16bit) { - cpu.PC = 0x1001; std::vector data = {0xA9, 0x7F, 0xFF}; mock_memory.SetMemoryContents(data); @@ -1294,13 +1277,15 @@ TEST_F(CPUTest, LDA_Immediate_16bit) { } TEST_F(CPUTest, LDA_DirectPage) { - cpu.PC = 0x1001; - std::vector data = {0xA5, 0x7F}; - mock_memory.SetMemoryContents(data); - - EXPECT_CALL(mock_memory, ReadByte(0x7F)).WillOnce(Return(0x80)); - cpu.SetAccumulatorSize(true); + cpu.D = 0x0200; + std::vector data = {0xA5, 0x3C, 0x00}; + mock_memory.SetMemoryContents(data); + mock_memory.InsertMemory(0x00023C, {0x80}); + + EXPECT_CALL(mock_memory, ReadByte(0x000001)).WillOnce(Return(0x3C)); + EXPECT_CALL(mock_memory, ReadByte(0x00023C)).WillOnce(Return(0x80)); + cpu.ExecuteInstruction(0xA5); // LDA Direct Page EXPECT_EQ(cpu.A, 0x80); EXPECT_TRUE(cpu.GetNegativeFlag()); @@ -1308,22 +1293,20 @@ TEST_F(CPUTest, LDA_DirectPage) { } TEST_F(CPUTest, LDA_Absolute) { - cpu.PC = 0x1001; + cpu.SetAccumulatorSize(true); std::vector data = {0xAD, 0x7F, 0xFF}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x7FFF, {0x7F}); - EXPECT_CALL(mock_memory, ReadWord(0x1001)).WillOnce(Return(0x7FFF)); + EXPECT_CALL(mock_memory, ReadWord(0x0001)).WillOnce(Return(0x7FFF)); EXPECT_CALL(mock_memory, ReadByte(0x7FFF)).WillOnce(Return(0x7F)); cpu.SetAccumulatorSize(true); cpu.ExecuteInstruction(0xAD); // LDA Absolute EXPECT_EQ(cpu.A, 0x7F); - EXPECT_TRUE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } -*/ // ============================================================================ // Stack Tests @@ -1452,8 +1435,9 @@ TEST_F(CPUTest, PLD_PullDirectPageRegister) { // REP - Reset Processor Status Bits TEST_F(CPUTest, REP) { - cpu.status = 0xFF; // All flags set - std::vector data = {0x30, 0x00}; // REP #0x30 (clear N & Z flags) + cpu.status = 0xFF; // All flags set + std::vector data = {0xC2, 0x30, + 0x00}; // REP #0x30 (clear N & Z flags) mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0xC2); // REP @@ -1464,8 +1448,9 @@ TEST_F(CPUTest, REP) { // SEP - Set Processor Status Bits TEST_F(CPUTest, SEP) { - cpu.status = 0x00; // All flags cleared - std::vector data = {0x30, 0x00}; // SEP #0x30 (set N & Z flags) + cpu.status = 0x00; // All flags cleared + std::vector data = {0xE2, 0x30, + 0x00}; // SEP #0x30 (set N & Z flags) mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0xE2); // SEP diff --git a/test/emu/ppu_test.cc b/test/emu/ppu_test.cc index e8e38edf..62d1d9f6 100644 --- a/test/emu/ppu_test.cc +++ b/test/emu/ppu_test.cc @@ -1,28 +1,146 @@ -#include "app/emu/ppu.h" +#include "app/emu/video/ppu.h" #include +#include "app/emu/clock.h" +#include "app/emu/memory/memory.h" +#include "app/emu/memory/mock_memory.h" + namespace yaze { namespace app { namespace emu { -class MockPPU : public IPPU { +class MockPpu : public PpuInterface { public: - MOCK_METHOD(void, writeRegister, (uint16_t address, uint8_t data), - (override)); - MOCK_METHOD(uint8_t, readRegister, (uint16_t address), (const, override)); - MOCK_METHOD(void, setOAMData, (const std::vector& data), (override)); - MOCK_METHOD(std::vector, getOAMData, (), (const, override)); - MOCK_METHOD(void, setVRAMData, (const std::vector& data), - (override)); - MOCK_METHOD(std::vector, getVRAMData, (), (const, override)); - MOCK_METHOD(void, setCGRAMData, (const std::vector& data), - (override)); - MOCK_METHOD(std::vector, getCGRAMData, (), (const, override)); - MOCK_METHOD(void, renderFrame, (), (override)); - MOCK_METHOD(std::vector, getFrameBuffer, (), (const, override)); + MOCK_METHOD(void, Write, (uint16_t address, uint8_t data), (override)); + MOCK_METHOD(uint8_t, Read, (uint16_t address), (const, override)); + + MOCK_METHOD(void, RenderFrame, (), (override)); + MOCK_METHOD(void, RenderScanline, (), (override)); + MOCK_METHOD(void, RenderBackground, (int layer), (override)); + MOCK_METHOD(void, RenderSprites, (), (override)); + + MOCK_METHOD(void, Init, (), (override)); + MOCK_METHOD(void, Reset, (), (override)); + MOCK_METHOD(void, Update, (double deltaTime), (override)); + MOCK_METHOD(void, UpdateClock, (double deltaTime), (override)); + MOCK_METHOD(void, UpdateInternalState, (int cycles), (override)); + + MOCK_METHOD(const std::vector&, GetFrameBuffer, (), + (const, override)); + MOCK_METHOD(std::shared_ptr, GetScreen, (), (const, override)); + + MOCK_METHOD(void, UpdateModeSettings, (), (override)); + MOCK_METHOD(void, UpdateTileData, (), (override)); + MOCK_METHOD(void, UpdateTileMapData, (), (override)); + MOCK_METHOD(void, UpdatePaletteData, (), (override)); + + MOCK_METHOD(void, ApplyEffects, (), (override)); + MOCK_METHOD(void, ComposeLayers, (), (override)); + + MOCK_METHOD(void, DisplayFrameBuffer, (), (override)); + MOCK_METHOD(void, Notify, (uint32_t address, uint8_t data), (override)); + + std::vector internalFrameBuffer; + std::vector vram; + std::vector sprites; + std::vector tilemaps; + BackgroundMode bgMode; }; +class PpuTest : public ::testing::Test { + protected: + MockMemory mock_memory; + MockClock mock_clock; + MockPpu mock_ppu; + + PpuTest() {} + + void SetUp() override { + ON_CALL(mock_ppu, Init()).WillByDefault([this]() { + mock_ppu.internalFrameBuffer.resize(256 * 240); + mock_ppu.vram.resize(0x10000); + }); + + ON_CALL(mock_ppu, Write(::testing::_, ::testing::_)) + .WillByDefault([this](uint16_t address, uint8_t data) { + mock_ppu.vram[address] = data; + }); + + ON_CALL(mock_ppu, Read(::testing::_)) + .WillByDefault( + [this](uint16_t address) { return mock_ppu.vram[address]; }); + + ON_CALL(mock_ppu, RenderScanline()).WillByDefault([this]() { + // Simulate scanline rendering logic... + }); + + ON_CALL(mock_ppu, GetFrameBuffer()).WillByDefault([this]() { + return mock_ppu.internalFrameBuffer; + }); + + // Additional ON_CALL setups as needed... + } + + void TearDown() override { + // Common cleanup (if necessary) + } + + const uint8_t testVRAMValue = 0xAB; + const uint16_t testVRAMAddress = 0x2000; + const std::vector spriteData = {/* ... */}; + const std::vector bgData = {/* ... */}; + const uint8_t testPaletteIndex = 3; + const uint16_t testTileIndex = 42; +}; + +// Test Initialization +TEST_F(PpuTest, InitializationSetsCorrectFrameBufferSize) { + // EXPECT_CALL(mock_ppu, Init()).Times(1); + // mock_ppu.Init(); + // EXPECT_EQ(mock_ppu.GetFrameBuffer().size(), 256 * 240); +} + +// Test State Reset +TEST_F(PpuTest, ResetClearsFrameBuffer) { + // EXPECT_CALL(mock_ppu, Reset()).Times(1); + // mock_ppu.Reset(); + // auto frameBuffer = mock_ppu.GetFrameBuffer(); + // EXPECT_TRUE(std::all_of(frameBuffer.begin(), frameBuffer.end(), + // [](uint8_t val) { return val == 0; })); +} + +// Test Memory Interaction +TEST_F(PpuTest, ReadWriteVRAM) { + // uint16_t address = testVRAMAddress; + // uint8_t value = testVRAMValue; + // EXPECT_CALL(mock_ppu, Write(address, value)).Times(1); + // mock_ppu.Write(address, value); + // EXPECT_EQ(mock_ppu.Read(address), value); +} + +// Test Rendering Mechanics +TEST_F(PpuTest, RenderScanlineUpdatesFrameBuffer) { + // Setup PPU with necessary background and sprite data + // Call RenderScanline and check if the framebuffer is updated correctly +} + +// Test Mode and Register Handling +TEST_F(PpuTest, Mode0Rendering) { + // Set PPU to Mode0 and verify correct rendering behavior +} + +// Test Interrupts and Counters +TEST_F(PpuTest, VBlankInterruptTriggered) { + // Simulate conditions for V-Blank and test if the interrupt is triggered +} + +// Test Composite Rendering and Output +TEST_F(PpuTest, FrameComposition) { + // Setup various layers and sprites, call ComposeLayers, and verify the frame + // buffer +} + } // namespace emu } // namespace app } // namespace yaze \ No newline at end of file diff --git a/test/z3ed_test.cc b/test/z3ed_test.cc deleted file mode 100644 index 6a876238..00000000 --- a/test/z3ed_test.cc +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include - -#include "app/zelda3/overworld.h" -#include "cli/command_handler.h" - -using namespace yaze::cli; -using ::testing::_; -using ::testing::Return; -using yaze::app::zelda3::Overworld; - -// Mock class for CommandHandler -class MockCommandHandler : public CommandHandler { - public: - MOCK_METHOD(absl::Status, handle, (const std::vector& arg), - (override)); -}; - -// Test fixture class -class CommandHandlerTest : public ::testing::Test { - protected: - std::shared_ptr mockHandler = - std::make_shared(); -}; - -// TEST_F(CommandHandlerTest, TestApplyPatch) { -// Commands cmd; -// cmd.handlers["-a"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-a"]->handle("apply_patch_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestCreatePatch) { -// Commands cmd; -// cmd.handlers["-cp"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-cp"]->handle("create_patch_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestOpen) { -// Commands cmd; -// cmd.handlers["-o"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-o"]->handle("open_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestBackup) { -// Commands cmd; -// cmd.handlers["-b"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-b"]->handle("backup_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestCompress) { -// Commands cmd; -// cmd.handlers["-c"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-c"]->handle("compress_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestDecompress) { -// Commands cmd; -// cmd.handlers["-d"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-d"]->handle("decompress_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestSnesToPc) { -// Commands cmd; -// cmd.handlers["-s"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-s"]->handle("snes_to_pc_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// } - -// TEST_F(CommandHandlerTest, TestPcToSnes) { -// Commands cmd; -// cmd.handlers["-p"] = mockHandler; -// EXPECT_CALL(*mockHandler, handle(_)).WillOnce(Return(absl::OkStatus())); -// absl::Status result = cmd.handlers["-p"]->handle("pc_to_snes_args"); -// EXPECT_EQ(result, absl::OkStatus()); -// }