From 76f40531f16da5669b1ca400afdbf4f33bf37c3d Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 19 Aug 2023 15:59:17 -0400 Subject: [PATCH] Add StackPointer to memory, PHA, PLA, PHP, PLP --- src/app/emu/cpu.cc | 54 ----- src/app/emu/cpu.h | 428 +++++++++++++++++++++++++++---------- src/app/emu/mem.h | 55 ++++- src/app/gfx/scad_format.cc | 8 +- src/app/gfx/scad_format.h | 2 +- test/cpu_test.cc | 85 +++++++- 6 files changed, 447 insertions(+), 185 deletions(-) diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc index 3bf6a5fa..fb669d12 100644 --- a/src/app/emu/cpu.cc +++ b/src/app/emu/cpu.cc @@ -65,60 +65,6 @@ uint8_t CPU::FetchByteDirectPage(uint8_t operand) { return fetchedByte; } -uint16_t CPU::DirectPageIndexedIndirectX() { - uint8_t dp = FetchByte(); - return memory.ReadWord((dp + X) & 0xFF); -} - -uint16_t CPU::StackRelative() { - uint8_t sr = FetchByte(); - return SP + sr; -} - -uint16_t CPU::DirectPage() { return FetchByte(); } - -uint16_t CPU::DirectPageIndirectLong() { - uint8_t dp = FetchByte(); - return memory.ReadWordLong(dp); -} - -uint16_t CPU::Immediate() { return PC++; } - -uint16_t CPU::Absolute() { return FetchWord(); } - -uint16_t CPU::AbsoluteLong() { return FetchLong(); } - -uint16_t CPU::DirectPageIndirectIndexedY() { - uint8_t dp = FetchByte(); - return memory.ReadWord(dp) + Y; -} - -uint16_t CPU::DirectPageIndirect() { - uint8_t dp = FetchByte(); - return memory.ReadWord(dp); -} - -uint16_t CPU::StackRelativeIndirectIndexedY() { - uint8_t sr = FetchByte(); - return memory.ReadWord(SP + sr) + Y; -} - -uint16_t CPU::DirectPageIndexedX() { - uint8_t dp = FetchByte(); - return (dp + X) & 0xFF; -} - -uint16_t CPU::DirectPageIndirectLongIndexedY() { - uint8_t dp = FetchByte(); - return memory.ReadWordLong(dp) + Y; -} - -uint16_t CPU::AbsoluteIndexedY() { return FetchWord() + Y; } - -uint16_t CPU::AbsoluteIndexedX() { return FetchWord() + X; } - -uint16_t CPU::AbsoluteLongIndexedX() { return FetchLong() + X; } - void CPU::ExecuteInstruction(uint8_t opcode) { // uint8_t opcode = FetchByte(); uint8_t operand = -1; diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h index 685d6e6a..5d8263b4 100644 --- a/src/app/emu/cpu.h +++ b/src/app/emu/cpu.h @@ -11,101 +11,21 @@ namespace yaze { namespace app { namespace emu { -// ADC: Add with carry -// AND: Logical AND -// ASL: Arithmetic shift left -// BCC: Branch if carry clear -// BCS: Branch if carry set -// BEQ: Branch if equal (zero set) -// BIT: Bit test -// BMI: Branch if minus (negative set) -// BNE: Branch if not equal (zero clear) -// BPL: Branch if plus (negative clear) -// BRA: Branch always -// BRK: Break -// BRL: Branch always long -// BVC: Branch if overflow clear -// BVS: Branch if overflow set -// CLC: Clear carry -// CLD: Clear decimal -// CLI: Clear interrupt disable -// CLV: Clear overflow -// CMP: Compare -// COP: Coprocessor -// CPX: Compare X register -// CPY: Compare Y register -// DEC: Decrement -// DEX: Decrement X register -// DEY: Decrement Y register -// EOR: Exclusive OR -// INC: Increment -// INX: Increment X register -// INY: Increment Y register -// JMP: Jump -// JML: Jump long -// JSR: Jump to subroutine -// JSL: Jump to subroutine long -// LDA: Load accumulator -// LDX: Load X register -// LDY: Load Y register -// LSR: Logical shift right -// MVN: Move negative -// MVP: Move positive -// NOP: No operation -// ORA: Logical OR -// PEA: Push effective address -// PEI: Push effective indirect address -// PER: Push effective PC-relative address -// PHA: Push accumulator -// PHB: Push data bank register -// PHD: Push direct page register -// PHK: Push program bank register -// PHP: Push processor status register -// PHX: Push X register -// PHY: Push Y register -// PLA: Pull accumulator -// PLB: Pull data bank register -// PLD: Pull direct page register -// PLP: Pull processor status register -// PLX: Pull X register -// PLY: Pull Y register -// ROL: Rotate left -// ROR: Rotate right -// RTI: Return from interrupt -// RTL: Return from subroutine long -// RTS: Return from subroutine -// SBC: Subtract with carry -// STA: Store accumulator -// STP: Stop the clock -// STX: Store X register -// STY: Store Y register -// STZ: Store zero -// TDC: Transfer direct page register to accumulator -// TRB: Test and reset bits -// TSB: Test and set bits -// WAI: Wait for interrupt -// XBA: Exchange B and A accumulator -// XCE: Exchange carry and emulation - class CPU : public Memory { - private: - Memory& memory; - public: explicit CPU(Memory& mem) : memory(mem) {} - - void Init() {} + void Init() { memory.ClearMemory(); } uint8_t ReadByte(uint16_t address) const override; uint16_t ReadWord(uint16_t address) const override; uint32_t ReadWordLong(uint16_t address) const override; - void WriteByte(uint32_t address, uint8_t value) override; void WriteWord(uint32_t address, uint16_t value) override; - 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); } uint8_t FetchByte(); uint16_t FetchWord(); @@ -115,37 +35,217 @@ class CPU : public Memory { uint8_t FetchByteDirectPage(uint8_t operand); - uint16_t DirectPageIndexedIndirectX(); - uint16_t StackRelative(); - uint16_t DirectPage(); - uint16_t DirectPageIndirectLong(); - uint16_t Immediate(); - uint16_t Absolute(); - uint16_t AbsoluteLong(); - uint16_t DirectPageIndirectIndexedY(); - uint16_t DirectPageIndirect(); - uint16_t StackRelativeIndirectIndexedY(); - uint16_t DirectPageIndexedX(); - uint16_t DirectPageIndirectLongIndexedY(); - uint16_t AbsoluteIndexedY(); - uint16_t AbsoluteIndexedX(); - uint16_t AbsoluteLongIndexedX(); - void ExecuteInstruction(uint8_t opcode); - void loadROM(const std::vector& rom) { - // if (rom.size() > memory.size()) { - // std::cerr << "ROM too large" << std::endl; - // return; - // } - // std::copy(rom.begin(), rom.end(), memory.begin()); + // ========================================================================== + // Addressing Modes + + // Effective Address: + // Bank: Data Bank Register if locating data + // Program Bank Register if transferring control + // High: Second operand byte + // Low: First operand byte + // + // LDA addr + uint16_t Absolute() { return FetchWord(); } + + // Effective Address: + // The Data Bank Register is concatened with the 16-bit operand + // the 24-bit result is added to the X Index Register + // based on the emulation mode (16:X=0, 8:X=1) + // + // LDA addr, X + uint16_t AbsoluteIndexedX() { return FetchWord() + X; } + + // Effective Address: + // The Data Bank Register is concatened with the 16-bit operand + // the 24-bit result is added to the Y Index Register + // based on the emulation mode (16:Y=0, 8:Y=1) + // + // LDA addr, Y + uint16_t AbsoluteIndexedY() { return FetchWord() + Y; } + + // Test Me :) + // Effective Address: + // Bank: Program Bank Register (PBR) + // High/low: The Indirect Address + // Indirect Address: Located in the Program Bank at the sum of + // the operand double byte and X based on the + // emulation mode + // JMP (addr, X) + uint16_t AbsoluteIndexedIndirect() { + uint16_t address = FetchWord() + X; + return memory.ReadWord(address); } + // Effective Address: + // Bank: Program Bank Register (PBR) + // High/low: The Indirect Address + // Indirect Address: Located in Bank Zero, at the operand double byte + // + // JMP (addr) + uint16_t AbsoluteIndirect() { + uint16_t address = FetchWord(); + return memory.ReadWord(address); + } + + // Effective Address: + // Bank/High/Low: The 24-bit Indirect Address + // Indirect Address: Located in Bank Zero, at the operand double byte + // + // JMP [addr] + uint32_t AbsoluteIndirectLong() { + uint16_t address = FetchWord(); + return memory.ReadWordLong(address); + } + + // Effective Address: + // Bank: Third operand byte + // High: Second operand byte + // Low: First operand byte + // + // LDA long + uint16_t AbsoluteLong() { return FetchLong(); } + + // Effective Address: + // The 24-bit operand is added to X based on the emulation mode + // + // LDA long, X + uint16_t AbsoluteLongIndexedX() { return FetchLong() + X; } + + // Source Effective Address: + // Bank: Second operand byte + // High/Low: The 16-bit value in X, if X is 8-bit high byte is 0 + // + // Destination Effective Address: + // Bank: First operand byte + // High/Low: The 16-bit value in Y, if Y is 8-bit high byte is 0 + // + // Length: + // The number of bytes to be moved: 16-bit value in Acculumator C plus 1. + // + // MVN src, dst + void BlockMove(uint16_t source, uint16_t dest, uint16_t length) { + for (int i = 0; i < length; i++) { + memory.WriteByte(dest + i, memory.ReadByte(source + i)); + } + } + + // Effective Address: + // Bank: Zero + // High/low: Direct Page Register plus operand byte + // + // LDA dp + uint16_t DirectPage() { return FetchByte(); } + + // Effective Address: + // Bank: Zero + // High/low: Direct Page Register plus operand byte plus X + // based on the emulation mode + // + // LDA dp, X + uint16_t DirectPageIndexedX() { + uint8_t dp = FetchByte(); + return (dp + X) & 0xFF; + } + + // Effective Address: + // Bank: Zero + // High/low: Direct Page Register plus operand byte plus Y + // based on the emulation mode + // LDA dp, Y + uint16_t DirectPageIndexedY() { + uint8_t dp = FetchByte(); + return (dp + Y) & 0xFF; + } + + // Effective Address: + // Bank: Data bank register + // High/low: The indirect address + // Indirect Address: Located in the direct page at the sum of the direct page + // register, the operand byte, and X based on the emulation mode in bank zero. + // + // LDA (dp, X) + uint16_t DirectPageIndexedIndirectX() { + uint8_t dp = FetchByte(); + return memory.ReadWord((dp + X) & 0xFF); + } + + // Effective Address: + // Bank: Data bank register + // High/low: The 16-bit indirect address + // Indirect Address: The operand byte plus the direct page register in bank + // zero. + // + // LDA (dp) + uint16_t DirectPageIndirect() { + uint8_t dp = FetchByte(); + return memory.ReadWord(dp); + } + + // Effective Address: + // Bank/High/Low: The 24-bit indirect address + // Indirect address: The operand byte plus the direct page + // register in bank zero. + // + // LDA [dp] + uint16_t DirectPageIndirectLong() { + uint8_t dp = FetchByte(); + return memory.ReadWordLong(dp); + } + + // Effective Address: + // Found by concatenating the data bank to the double-byte + // indirect address, then adding Y based on the emulation mode. + // + // Indirect Address: Located in the Direct Page at the sum of the direct page + // register and the operand byte, in bank zero. + // + // LDA (dp), Y + uint16_t DirectPageIndirectIndexedY() { + uint8_t dp = FetchByte(); + return memory.ReadWord(dp) + Y; + } + + // Effective Address: + // Found by adding to the triple-byte indirect address Y based on the + // emulation mode. Indrect Address: Located in the Direct Page at the sum + // of the direct page register and the operand byte in bank zero. + // Indirect Address: + // Located in the Direct Page at the sum of the direct page register and + // the operand byte in bank zero. + // + // LDA (dp), Y + uint16_t DirectPageIndirectLongIndexedY() { + uint8_t dp = FetchByte(); + return memory.ReadWordLong(dp) + Y; + } + + // 8-bit data: Data Operand Byte + // 16-bit data 65816 native mode m or x = 0 + // Data High: Second Operand Byte + // Data Low: First Operand Byte + // + // LDA #const + uint16_t Immediate() { return PC++; } + + uint16_t StackRelative() { + uint8_t sr = FetchByte(); + return SP() + sr; + } + + uint16_t StackRelativeIndirectIndexedY() { + uint8_t sr = FetchByte(); + return memory.ReadWord(SP() + sr) + Y; + } + + // ========================================================================== // Registers - uint8_t A = 0; // Accumulator - uint8_t X = 0; // X index register - uint8_t Y = 0; // Y index register - uint8_t SP = 0; // Stack Pointer + + uint8_t A = 0; // Accumulator + uint8_t X = 0; // X index register + uint8_t Y = 0; // Y index register + // uint8_t SP = 0; // Stack Pointer uint16_t DB = 0; // Data Bank register uint16_t D = 0; // Direct Page register uint16_t PB = 0; // Program Bank register @@ -165,8 +265,8 @@ class CPU : public Memory { // B #$10 00010000 Break (emulation mode only) // Setting flags in the status register - int GetAccumulatorSize() { return status & 0x20; } - int GetIndexSize() { return status & 0x10; } + int GetAccumulatorSize() const { return status & 0x20; } + int GetIndexSize() const { return status & 0x10; } // Set individual flags void SetNegativeFlag(bool set) { SetFlag(0x80, set); } @@ -186,7 +286,84 @@ class CPU : public Memory { bool GetZeroFlag() const { return GetFlag(0x02); } bool GetCarryFlag() const { return GetFlag(0x01); } + // ========================================================================== // Instructions + + // Left to implement + // * = in progress + + // ADC: Add with carry * + // AND: Logical AND * + // ASL: Arithmetic shift left + // BCC: Branch if carry clear * + // BCS: Branch if carry set * + // BEQ: Branch if equal (zero set) * + // BIT: Bit test + // BMI: Branch if minus (negative set) + // BNE: Branch if not equal (zero clear) + // BPL: Branch if plus (negative clear) + // BRA: Branch always + // BRK: Break + // BRL: Branch always long + // BVC: Branch if overflow clear + // BVS: Branch if overflow set + // CMP: Compare + // COP: Coprocessor + // CPX: Compare X register + // CPY: Compare Y register + // DEC: Decrement + // DEX: Decrement X register + // DEY: Decrement Y register + // EOR: Exclusive OR + // INC: Increment + // INX: Increment X register + // INY: Increment Y register + // JMP: Jump + // JML: Jump long + // JSR: Jump to subroutine + // JSL: Jump to subroutine long + // LDA: Load accumulator + // LDX: Load X register + // LDY: Load Y register + // LSR: Logical shift right + // MVN: Move negative + // MVP: Move positive + // NOP: No operation + // ORA: Logical OR + // PEA: Push effective address + // PEI: Push effective indirect address + // PER: Push effective PC-relative address + // PHA: Push accumulator + // PHB: Push data bank register + // PHD: Push direct page register + // PHK: Push program bank register + // PHP: Push processor status register + // PHX: Push X register + // PHY: Push Y register + // PLA: Pull accumulator + // PLB: Pull data bank register + // PLD: Pull direct page register + // PLP: Pull processor status register + // PLX: Pull X register + // PLY: Pull Y register + // ROL: Rotate left + // ROR: Rotate right + // RTI: Return from interrupt + // RTL: Return from subroutine long + // RTS: Return from subroutine + // SBC: Subtract with carry + // STA: Store accumulator + // STP: Stop the clock + // STX: Store X register + // STY: Store Y register + // STZ: Store zero + // TDC: Transfer direct page register to accumulator + // TRB: Test and reset bits + // TSB: Test and set bits + // WAI: Wait for interrupt + // XBA: Exchange B and A accumulator + // XCE: Exchange carry and emulation + void ADC(uint8_t operand); void AND(uint16_t address); @@ -223,6 +400,18 @@ class CPU : public Memory { void CLV() { status &= ~0x40; } + void PHA() { memory.PushByte(A); } + + void PLA() { + A = memory.PopByte(); + SetNegativeFlag((A & 0x80) != 0); + SetZeroFlag(A == 0); + } + + void PHP() { memory.PushByte(status); } + + void PLP() { status = memory.PopByte(); } + void SEI() { status |= 0x04; } void SED() { status |= 0x08; } @@ -251,7 +440,7 @@ class CPU : public Memory { SetNegativeFlag(A & 0x80); } - void TCS() { SP = A; } + void TCS() { memory.SetSP(A); } void TAX() { X = A; @@ -290,15 +479,15 @@ class CPU : public Memory { } void TSX() { - X = SP; + X = SP(); SetZeroFlag(X == 0); SetNegativeFlag(X & 0x80); } - void TXS() { SP = X; } + void TXS() { memory.SetSP(X); } void TSC() { - A = SP; + A = SP(); SetZeroFlag(A == 0); SetNegativeFlag(A & 0x80); } @@ -328,14 +517,19 @@ class CPU : public Memory { // Helper function to get the value of a specific flag bit bool GetFlag(uint8_t mask) const { return (status & mask) != 0; } + // Appease the C++ Gods... + void PushByte(uint8_t value) override { memory.PushByte(value); } + void PushWord(uint16_t value) override { memory.PushWord(value); } + uint8_t PopByte() override { return memory.PopByte(); } + uint16_t PopWord() override { return memory.PopWord(); } void ClearMemory() override { memory.ClearMemory(); } void LoadData(const std::vector& data) override { memory.LoadData(data); } - - // Appease the C++ Gods... uint8_t operator[](int i) const override { return 0; } uint8_t at(int i) const override { return 0; } + + Memory& memory; }; } // namespace emu diff --git a/src/app/emu/mem.h b/src/app/emu/mem.h index 9e2f5f1b..828676db 100644 --- a/src/app/emu/mem.h +++ b/src/app/emu/mem.h @@ -34,6 +34,13 @@ class Memory { virtual void WriteByte(uint32_t address, uint8_t value) = 0; virtual void WriteWord(uint32_t address, uint16_t value) = 0; + virtual void PushByte(uint8_t value) = 0; + virtual uint8_t PopByte() = 0; + virtual void PushWord(uint16_t value) = 0; + virtual uint16_t PopWord() = 0; + virtual int16_t SP() const = 0; + virtual void SetSP(int16_t value) = 0; + virtual void SetMemory(const std::vector& data) = 0; virtual void ClearMemory() = 0; virtual void LoadData(const std::vector& data) = 0; @@ -65,6 +72,7 @@ class MemoryImpl : public Memory { (static_cast(memory_.at(mapped_address + 1)) << 8) | (static_cast(memory_.at(mapped_address + 2)) << 16); } + void WriteByte(uint32_t address, uint8_t value) override { uint32_t mapped_address = GetMappedAddress(address); memory_.at(mapped_address) = value; @@ -75,19 +83,47 @@ class MemoryImpl : public Memory { memory_.at(mapped_address + 1) = (value >> 8) & 0xFF; } + // Stack operations + void PushByte(uint8_t value) override { + if (SP_ > 0x0100) { + memory_.at(SP_--) = value; + } else { + // Handle stack underflow + std::cout << "Stack underflow!" << std::endl; + } + } + + uint8_t PopByte() override { + if (SP_ < 0x1FF) { + return memory_.at(++SP_); + } else { + // Handle stack overflow + std::cout << "Stack overflow!" << std::endl; + return 0; + } + } + + void PushWord(uint16_t value) override { + PushByte(value >> 8); + PushByte(value & 0xFF); + } + + uint16_t PopWord() override { + uint8_t low = PopByte(); + uint8_t high = PopByte(); + return (static_cast(high) << 8) | low; + } + + int16_t SP() const override { return SP_; } + void SetSP(int16_t value) override { SP_ = value; } + void SetMemory(const std::vector& data) override { memory_ = data; } - void ClearMemory() override { memory_.resize(64000, 0x00); } - void LoadData(const std::vector& data) override { std::copy(data.begin(), data.end(), memory_.begin()); } uint8_t at(int i) const override { return memory_[i]; } - auto size() const { return memory_.size(); } - auto begin() const { return memory_.begin(); } - auto end() const { return memory_.end(); } - uint8_t operator[](int i) const override { if (i > memory_.size()) { std::cout << i << " out of bounds \n"; @@ -96,6 +132,10 @@ class MemoryImpl : public Memory { return memory_[i]; } + auto size() const { return memory_.size(); } + auto begin() const { return memory_.begin(); } + auto end() const { return memory_.end(); } + private: uint32_t GetMappedAddress(uint32_t address) const { uint32_t bank = address >> 16; @@ -135,6 +175,9 @@ class MemoryImpl : public Memory { // Memory (64KB) std::vector memory_; + + // Stack Pointer + uint16_t SP_ = 0x01FF; }; } // namespace emu diff --git a/src/app/gfx/scad_format.cc b/src/app/gfx/scad_format.cc index 3822b893..c902e24c 100644 --- a/src/app/gfx/scad_format.cc +++ b/src/app/gfx/scad_format.cc @@ -45,7 +45,7 @@ absl::Status LoadCgx(uint8_t bpp, std::string_view filename, std::vector& cgx_data, std::vector& cgx_loaded, std::vector& cgx_header) { - std::ifstream file(filename, std::ios::binary); + std::ifstream file(filename.data(), std::ios::binary); if (!file.is_open()) { return absl::NotFoundError("CGX file not found."); } @@ -68,7 +68,7 @@ absl::Status LoadCgx(uint8_t bpp, std::string_view filename, absl::Status LoadScr(std::string_view filename, uint8_t input_value, std::vector& map_data) { - std::ifstream file(filename, std::ios::binary); + std::ifstream file(filename.data(), std::ios::binary); if (!file.is_open()) { return absl::NotFoundError("SCR/PNL/MAP file not found."); } @@ -157,9 +157,9 @@ absl::Status DrawScrWithCgx(uint8_t bpp, std::vector& map_data, return absl::OkStatus(); } -std::vector DecodeColFile(const std::string& filename) { +std::vector DecodeColFile(const std::string_view filename) { std::vector decoded_col; - std::ifstream file(filename, std::ios::binary | std::ios::ate); + std::ifstream file(filename.data(), std::ios::binary | std::ios::ate); if (!file.is_open()) { return decoded_col; // Return an empty vector if the file couldn't be diff --git a/src/app/gfx/scad_format.h b/src/app/gfx/scad_format.h index 0ab42ce7..977308df 100644 --- a/src/app/gfx/scad_format.h +++ b/src/app/gfx/scad_format.h @@ -63,7 +63,7 @@ absl::Status DrawScrWithCgx(uint8_t bpp, std::vector& map_bitmap_data, std::vector& map_data, std::vector& cgx_loaded); -std::vector DecodeColFile(const std::string& filename); +std::vector DecodeColFile(const std::string_view filename); absl::Status DecodeObjFile( std::string_view filename, std::vector& obj_data, diff --git a/test/cpu_test.cc b/test/cpu_test.cc index dd303e7a..6fda2493 100644 --- a/test/cpu_test.cc +++ b/test/cpu_test.cc @@ -18,13 +18,21 @@ class MockMemory : public Memory { MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value)); MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value)); - MOCK_CONST_METHOD1(at, uint8_t(int i)); - uint8_t operator[](int i) const override { return at(i); } + MOCK_METHOD1(PushByte, void(uint8_t value)); + MOCK_METHOD0(PopByte, uint8_t()); + MOCK_METHOD1(PushWord, void(uint16_t value)); + MOCK_METHOD0(PopWord, uint16_t()); + MOCK_CONST_METHOD0(SP, int16_t()); + MOCK_METHOD1(SetSP, void(int16_t value)); MOCK_METHOD1(SetMemory, void(const std::vector& data)); - MOCK_METHOD0(ClearMemory, void()); MOCK_METHOD1(LoadData, void(const std::vector& data)); + MOCK_METHOD0(ClearMemory, void()); + + MOCK_CONST_METHOD1(at, uint8_t(int i)); + uint8_t operator[](int i) const override { return at(i); } + void SetMemoryContents(const std::vector& data) { memory_.resize(64000); std::copy(data.begin(), data.end(), memory_.begin()); @@ -42,10 +50,30 @@ class MockMemory : public Memory { (static_cast(memory_.at(address + 1)) << 8) | (static_cast(memory_.at(address + 2)) << 16); }); + ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) { + memory_.at(SP_) = value; + }); + ON_CALL(*this, PopByte()).WillByDefault([this]() { + uint8_t value = memory_.at(SP_); + this->SetSP(SP_ + 1); + return value; + }); + ON_CALL(*this, PushWord(::testing::_)) + .WillByDefault([this](uint16_t value) { + memory_.at(SP_) = value & 0xFF; + memory_.at(SP_ + 1) = (value >> 8) & 0xFF; + }); + ON_CALL(*this, PopWord()).WillByDefault([this]() { + uint16_t value = static_cast(memory_.at(SP_)) | + (static_cast(memory_.at(SP_ + 1)) << 8); + this->SetSP(SP_ + 2); + return value; + }); } private: std::vector memory_; + uint16_t SP_ = 0x01FF; }; using ::testing::_; @@ -121,6 +149,15 @@ TEST(CPUTest, ADC_Absolute) { EXPECT_EQ(cpu.A, 0x06); } +TEST(CPUTest, ADC_AbsoluteLong) { + MockMemory mock_memory; + CPU cpu(mock_memory); + cpu.A = 0x01; + cpu.PC = 1; // PC register + cpu.status = 0x00; // 16-bit mode + std::vector data = {0x2F, 0x03, 0x00, 0x00, 0x05, 0x00}; +} + /** * Direct Page Unimplemented * @@ -289,6 +326,48 @@ TEST(CPUTest, BRL) { EXPECT_EQ(cpu.PC, 0x1004); } +// ============================================================================ +// Stack Tests + +class CPUTestKit : public ::testing::Test { + protected: + MockMemory memory_; + CPU cpu_{memory_}; +}; + +TEST_F(CPUTestKit, PHA_PLA_Test) { + cpu_.A = 0x42; + EXPECT_CALL(memory_, PushByte(0x42)).WillOnce(Return()); + cpu_.PHA(); + cpu_.A = 0x00; + EXPECT_CALL(memory_, PopByte()).WillOnce(Return(0x42)); + cpu_.PLA(); + EXPECT_EQ(cpu_.A, 0x42); +} + +TEST_F(CPUTestKit, PHP_PLP_Test) { + // Set some status flags + cpu_.SetNegativeFlag(true); + cpu_.SetZeroFlag(false); + EXPECT_TRUE(cpu_.GetNegativeFlag()); + EXPECT_FALSE(cpu_.GetZeroFlag()); + + EXPECT_CALL(memory_, PushByte(0x80)).WillOnce(Return()); + cpu_.PHP(); + + // Clear status flags + cpu_.SetNegativeFlag(false); + cpu_.SetZeroFlag(true); + EXPECT_FALSE(cpu_.GetNegativeFlag()); + EXPECT_TRUE(cpu_.GetZeroFlag()); + + EXPECT_CALL(memory_, PopByte()).WillOnce(Return(0x80)); + cpu_.PLP(); + + EXPECT_TRUE(cpu_.GetNegativeFlag()); + EXPECT_FALSE(cpu_.GetZeroFlag()); +} + // ============================================================================ // REP - Reset Processor Status Bits