diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc index b9059677..bcb9e932 100644 --- a/src/app/emu/cpu.cc +++ b/src/app/emu/cpu.cc @@ -334,23 +334,23 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xE0: // CPX Immediate - // CPX(); + CPX(Immediate()); break; case 0xE4: // CPX Direct Page // CPX(); break; case 0xEC: // CPX Absolute - // CPX(); + CPX(Absolute()); break; case 0xC0: // CPY Immediate - // CPY(); + CPY(Immediate()); break; case 0xC4: // CPY Direct Page // CPY(); break; case 0xCC: // CPY Absolute - // CPY(); + CPY(Absolute()); break; case 0x3A: // DEC Accumulator @@ -370,11 +370,11 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xCA: // DEX Decrement X register - // DEX(); + DEX(); break; case 0x88: // DEY Decrement Y register - // DEY(); + DEY(); break; case 0x41: // EOR DP Indexed Indirect, X @@ -951,7 +951,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; case 0xFB: // XCE - // XCE(); + XCE(); break; default: std::cerr << "Unknown instruction: " << std::hex @@ -962,7 +962,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) { void CPU::ADC(uint8_t operand) { auto C = GetCarryFlag(); - if (GetAccumulatorSize()) { // 8-bit mode + if (!E) { // 8-bit mode uint16_t result = (A & 0xFF) + (operand & 0xFF); // + (C ? 1 : 0); SetCarryFlag(!(result > 0xFF)); // Update the carry flag @@ -997,7 +997,7 @@ void CPU::ADC(uint8_t operand) { void CPU::AND(uint16_t address) { uint8_t operand; - if (GetAccumulatorSize() == 0) { // 16-bit mode + if (E == 0) { // 16-bit mode uint16_t operand16 = memory.ReadWord(address); A &= operand16; SetZeroFlag(A == 0); diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h index 6d047c16..61212ee3 100644 --- a/src/app/emu/cpu.h +++ b/src/app/emu/cpu.h @@ -242,14 +242,14 @@ class CPU : public Memory { // ========================================================================== // 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 - uint16_t DB = 0; // Data Bank register + uint8_t A = 0; // Accumulator + uint8_t X = 0; // X index register + uint8_t Y = 0; // Y index register uint16_t D = 0; // Direct Page register + uint16_t DB = 0; // Data Bank register uint16_t PB = 0; // Program Bank register uint16_t PC = 0; // Program Counter + uint8_t E = 1; // Emulation mode flag uint8_t status; // Processor Status (P) // Mnemonic Value Binary Description @@ -312,12 +312,7 @@ class CPU : public Memory { // 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 @@ -333,19 +328,6 @@ class CPU : public Memory { // 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 @@ -390,16 +372,86 @@ class CPU : public Memory { PC++; } + // SEC: Set carry flag void SEC() { status |= 0x01; } + // CLC: Clear carry flag void CLC() { status &= ~0x01; } + // CLD: Clear decimal mode void CLD() { status &= ~0x08; } + // CLI: Clear interrupt disable flag void CLI() { status &= ~0x04; } + // CLV: Clear overflow flag void CLV() { status &= ~0x40; } + bool emulation_mode = false; + + void CPX(uint16_t address) { + uint16_t memory_value = + E ? memory.ReadByte(address) : memory.ReadWord(address); + compare(X, memory_value); + } + + void CPY(uint16_t address) { + uint16_t memory_value = + E ? memory.ReadByte(address) : memory.ReadWord(address); + compare(Y, memory_value); + } + + // DEX: Decrement X register + void DEX() { + X--; + SetZeroFlag(X == 0); + SetNegativeFlag(X & 0x80); + } + + // DEY: Decrement Y register + void DEY() { + Y--; + SetZeroFlag(Y == 0); + SetNegativeFlag(Y & 0x80); + } + + // INX: Increment X register + void INX() { + X++; + SetNegativeFlag(X & 0x80); + SetZeroFlag(X == 0); + } + + // INY: Increment Y register + void INY() { + Y++; + SetNegativeFlag(Y & 0x80); + SetZeroFlag(Y == 0); + } + + // INC: Increment memory + void INC(uint16_t address) { + if (GetAccumulatorSize()) { + uint8_t value = ReadByte(address); + value++; + if (value == static_cast(0x100)) { + value = 0x00; // Wrap around in 8-bit mode + } + WriteByte(address, value); + SetNegativeFlag(value & 0x80); + SetZeroFlag(value == 0); + } else { + uint16_t value = ReadWord(address); + value++; + if (value == static_cast(0x10000)) { + value = 0x0000; // Wrap around in 16-bit mode + } + WriteByte(address, value); + SetNegativeFlag(value & 0x80); + SetZeroFlag(value == 0); + } + } + // Push Accumulator on Stack void PHA() { memory.PushByte(A); } @@ -535,19 +587,22 @@ class CPU : public Memory { SetNegativeFlag(A & 0x80); } - void INX() { - X++; - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); - } - - void INY() { - Y++; - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x80); + // XCE: Exchange Carry and Emulation Flags + void XCE() { + uint8_t carry = status & 0x01; + status &= ~0x01; + status |= E; + E = carry; } private: + void compare(uint16_t register_value, uint16_t memory_value) { + uint16_t result = register_value - memory_value; + SetNegativeFlag(result & (E ? 0x8000 : 0x80)); // Negative flag + SetZeroFlag(result == 0); // Zero flag + SetCarryFlag(register_value >= 0); // Carry flag + } + // Helper function to set or clear a specific flag bit void SetFlag(uint8_t mask, bool set) { if (set) { diff --git a/src/app/emu/mem.h b/src/app/emu/mem.h index 828676db..d4cb5ee6 100644 --- a/src/app/emu/mem.h +++ b/src/app/emu/mem.h @@ -74,8 +74,8 @@ class MemoryImpl : public Memory { } void WriteByte(uint32_t address, uint8_t value) override { - uint32_t mapped_address = GetMappedAddress(address); - memory_.at(mapped_address) = value; + // uint32_t mapped_address = GetMappedAddress(address); + memory_[address] = value; } void WriteWord(uint32_t address, uint16_t value) override { uint32_t mapped_address = GetMappedAddress(address); diff --git a/test/cpu_test.cc b/test/cpu_test.cc index 0800fcc0..beca912d 100644 --- a/test/cpu_test.cc +++ b/test/cpu_test.cc @@ -36,6 +36,9 @@ class MockMemory : public Memory { void SetMemoryContents(const std::vector& data) { memory_.resize(64000); std::copy(data.begin(), data.end(), memory_.begin()); + } + + void Init() { ON_CALL(*this, ReadByte(::testing::_)) .WillByDefault( [this](uint16_t address) { return memory_.at(address); }); @@ -50,6 +53,15 @@ class MockMemory : public Memory { (static_cast(memory_.at(address + 1)) << 8) | (static_cast(memory_.at(address + 2)) << 16); }); + ON_CALL(*this, WriteByte(::testing::_, ::testing::_)) + .WillByDefault([this](uint32_t address, uint8_t value) { + memory_[address] = value; + }); + ON_CALL(*this, WriteWord(::testing::_, ::testing::_)) + .WillByDefault([this](uint32_t address, uint16_t value) { + memory_[address] = value & 0xFF; + memory_[address + 1] = (value >> 8) & 0xFF; + }); ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) { memory_.at(SP_) = value; }); @@ -69,6 +81,9 @@ class MockMemory : public Memory { this->SetSP(SP_ + 2); return value; }); + ON_CALL(*this, ClearMemory()).WillByDefault([this]() { + memory_.resize(64000, 0x00); + }); } private: @@ -78,6 +93,11 @@ class MockMemory : public Memory { class CPUTest : public ::testing::Test { public: + void SetUp() override { + mock_memory.Init(); + mock_memory.ClearMemory(); + } + MockMemory mock_memory; CPU cpu{mock_memory}; }; @@ -239,9 +259,10 @@ TEST_F(CPUTest, AND_Immediate) { EXPECT_EQ(cpu.A, 0b10100000); // A register should now be 0b10100000 } -TEST_F(CPUTest, AND_Absolute) { +TEST_F(CPUTest, AND_Absolute_16BitMode) { cpu.A = 0b11111111; // A register - cpu.status = 0x00; // 16-bit mode + 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); @@ -310,6 +331,142 @@ TEST_F(CPUTest, BRL) { EXPECT_EQ(cpu.PC, 0x1004); } +// ============================================================================ + +// Test for CPX instruction +TEST_F(CPUTest, CPX_CarryFlagSet) { + cpu.X = 0x1000; + cpu.CPX(0x0F00); + ASSERT_TRUE(cpu.GetCarryFlag()); // Carry flag should be set +} + +TEST_F(CPUTest, CPX_ZeroFlagSet) { + cpu.X = 0x0F00; + cpu.ExecuteInstruction(0xE0); // Immediate CPX + ASSERT_TRUE(cpu.GetZeroFlag()); // Zero flag should be set +} + +TEST_F(CPUTest, CPX_NegativeFlagSet) { + cpu.PC = 1; + cpu.X = 0x8000; + std::vector data = {0xE0, 0xFF, 0xFF}; + mock_memory.SetMemoryContents(data); + + cpu.ExecuteInstruction(0xE0); // Immediate CPX (0xFFFF) + + ASSERT_TRUE(cpu.GetNegativeFlag()); // Negative flag should be set +} + +// Test for CPY instruction +TEST_F(CPUTest, CPY_CarryFlagSet) { + cpu.Y = 0x1000; + cpu.CPY(0x0F00); + ASSERT_TRUE(cpu.GetCarryFlag()); // Carry flag should be set +} + +TEST_F(CPUTest, CPY_ZeroFlagSet) { + cpu.Y = 0x0F00; + cpu.CPY(0x0F00); + ASSERT_TRUE(cpu.GetZeroFlag()); // Zero flag should be set +} + +TEST_F(CPUTest, CPY_NegativeFlagSet) { + cpu.PC = 1; + cpu.Y = 0x8000; + std::vector data = {0xC0, 0xFF, 0xFF}; + mock_memory.SetMemoryContents(data); + cpu.ExecuteInstruction(0xC0); // Immediate CPY (0xFFFF) + ASSERT_TRUE(cpu.GetNegativeFlag()); // Negative flag should be set +} + +// ============================================================================ +// DEC - Decrement Memory + +// Test for DEX instruction +TEST_F(CPUTest, DEX) { + cpu.X = 0x02; // Set X register to 2 + cpu.ExecuteInstruction(0xCA); // Execute DEX instruction + EXPECT_EQ(0x01, cpu.X); // Expected value of X register after decrementing + + cpu.X = 0x00; // Set X register to 0 + cpu.ExecuteInstruction(0xCA); // Execute DEX instruction + EXPECT_EQ(0xFF, cpu.X); // Expected value of X register after decrementing + + cpu.X = 0x80; // Set X register to 128 + cpu.ExecuteInstruction(0xCA); // Execute DEX instruction + EXPECT_EQ(0x7F, cpu.X); // Expected value of X register after decrementing +} + +// Test for DEY instruction +TEST_F(CPUTest, DEY) { + cpu.Y = 0x02; // Set Y register to 2 + cpu.ExecuteInstruction(0x88); // Execute DEY instruction + EXPECT_EQ(0x01, cpu.Y); // Expected value of Y register after decrementing + + cpu.Y = 0x00; // Set Y register to 0 + cpu.ExecuteInstruction(0x88); // Execute DEY instruction + EXPECT_EQ(0xFF, cpu.Y); // Expected value of Y register after decrementing + + cpu.Y = 0x80; // Set Y register to 128 + cpu.ExecuteInstruction(0x88); // Execute DEY instruction + EXPECT_EQ(0x7F, cpu.Y); // Expected value of Y register after decrementing +} + +// ============================================================================ +// INC - Increment Memory + +/** +TEST_F(CPUTest, INC) { + cpu.status &= 0x20; + + EXPECT_CALL(mock_memory, WriteByte(0x1000, 0x7F)).WillOnce(Return()); + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x7F)); + EXPECT_CALL(mock_memory, WriteByte(0x1000, 0x80)).WillOnce(Return()); + + cpu.WriteByte(0x1000, 0x7F); + cpu.INC(0x1000); + EXPECT_EQ(cpu.ReadByte(0x1000), 0x80); + EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_FALSE(cpu.GetZeroFlag()); + + EXPECT_CALL(mock_memory, WriteByte(0x1000, 0xFF)).WillOnce(Return()); + cpu.WriteByte(0x1000, 0xFF); + cpu.INC(0x1000); + EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x00)); + EXPECT_EQ(cpu.ReadByte(0x1000), 0x00); + EXPECT_FALSE(cpu.GetNegativeFlag()); + EXPECT_TRUE(cpu.GetZeroFlag()); +} +*/ + +TEST_F(CPUTest, INX) { + cpu.X = 0x7F; + cpu.INX(); + EXPECT_EQ(cpu.X, 0x80); + EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_FALSE(cpu.GetZeroFlag()); + + cpu.X = 0xFF; + cpu.INX(); + EXPECT_EQ(cpu.X, 0x00); + EXPECT_FALSE(cpu.GetNegativeFlag()); + EXPECT_TRUE(cpu.GetZeroFlag()); +} + +TEST_F(CPUTest, INY) { + cpu.Y = 0x7F; + cpu.INY(); + EXPECT_EQ(cpu.Y, 0x80); + EXPECT_TRUE(cpu.GetNegativeFlag()); + EXPECT_FALSE(cpu.GetZeroFlag()); + + cpu.Y = 0xFF; + cpu.INY(); + EXPECT_EQ(cpu.Y, 0x00); + EXPECT_FALSE(cpu.GetNegativeFlag()); + EXPECT_TRUE(cpu.GetZeroFlag()); +} + // ============================================================================ // Stack Tests @@ -507,6 +664,35 @@ TEST_F(CPUTest, TAY) { EXPECT_EQ(cpu.Y, 0xDE); // Y register should now be equal to A } +// ============================================================================ +// XCE - Exchange Carry and Emulation Flags + +TEST_F(CPUTest, XCESwitchToNativeMode) { + cpu.ExecuteInstruction(0x18); // Clear carry flag + cpu.ExecuteInstruction(0xFB); // Switch to native mode + EXPECT_FALSE(cpu.E); // Emulation mode flag should be cleared +} + +TEST_F(CPUTest, XCESwitchToEmulationMode) { + cpu.ExecuteInstruction(0x38); // Set carry flag + cpu.ExecuteInstruction(0xFB); // Switch to emulation mode + EXPECT_TRUE(cpu.E); // Emulation mode flag should be set +} + +TEST_F(CPUTest, XCESwitchBackAndForth) { + cpu.ExecuteInstruction(0x18); // Clear carry flag + cpu.ExecuteInstruction(0xFB); // Switch to native mode + EXPECT_FALSE(cpu.E); // Emulation mode flag should be cleared + + cpu.ExecuteInstruction(0x38); // Set carry flag + cpu.ExecuteInstruction(0xFB); // Switch to emulation mode + EXPECT_TRUE(cpu.E); // Emulation mode flag should be set + + cpu.ExecuteInstruction(0x18); // Clear carry flag + cpu.ExecuteInstruction(0xFB); // Switch to native mode + EXPECT_FALSE(cpu.E); // Emulation mode flag should be cleared +} + } // namespace emu } // namespace app } // namespace yaze