Add CPU and Memory class for SNES emulator
This commit is contained in:
@@ -2,6 +2,7 @@ add_executable(
|
|||||||
yaze
|
yaze
|
||||||
app/yaze.cc
|
app/yaze.cc
|
||||||
app/rom.cc
|
app/rom.cc
|
||||||
|
app/emu/cpu.cc
|
||||||
${YAZE_APP_CORE_SRC}
|
${YAZE_APP_CORE_SRC}
|
||||||
${YAZE_APP_EDITOR_SRC}
|
${YAZE_APP_EDITOR_SRC}
|
||||||
${YAZE_APP_GFX_SRC}
|
${YAZE_APP_GFX_SRC}
|
||||||
|
|||||||
1038
src/app/emu/cpu.cc
Normal file
1038
src/app/emu/cpu.cc
Normal file
File diff suppressed because it is too large
Load Diff
334
src/app/emu/cpu.h
Normal file
334
src/app/emu/cpu.h
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "mem.h"
|
||||||
|
|
||||||
|
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) {}
|
||||||
|
|
||||||
|
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 SetMemory(const std::vector<uint8_t>& data) override {
|
||||||
|
memory.SetMemory(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t FetchByte();
|
||||||
|
uint16_t FetchWord();
|
||||||
|
uint32_t FetchLong();
|
||||||
|
int8_t FetchSignedByte();
|
||||||
|
int16_t FetchSignedWord();
|
||||||
|
|
||||||
|
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<uint8_t>& rom) {
|
||||||
|
// if (rom.size() > memory.size()) {
|
||||||
|
// std::cerr << "ROM too large" << std::endl;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// std::copy(rom.begin(), rom.end(), memory.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
uint16_t D = 0; // Direct Page register
|
||||||
|
uint16_t PB = 0; // Program Bank register
|
||||||
|
uint16_t PC = 0; // Program Counter
|
||||||
|
uint8_t status; // Processor Status (P)
|
||||||
|
|
||||||
|
// Mnemonic Value Binary Description
|
||||||
|
// N #$80 10000000 Negative
|
||||||
|
// V #$40 01000000 Overflow
|
||||||
|
// M #$20 00100000 Accumulator size (0 = 16-bit, 1 = 8-bit)
|
||||||
|
// X #$10 00010000 Index size (0 = 16-bit, 1 = 8-bit)
|
||||||
|
// D #$08 00001000 Decimal
|
||||||
|
// I #$04 00000100 IRQ disable
|
||||||
|
// Z #$02 00000010 Zero
|
||||||
|
// C #$01 00000001 Carry
|
||||||
|
// E 6502 emulation mode
|
||||||
|
// B #$10 00010000 Break (emulation mode only)
|
||||||
|
|
||||||
|
// Setting flags in the status register
|
||||||
|
void SetZeroFlag(bool condition) {
|
||||||
|
if (condition) {
|
||||||
|
status |= 0x02;
|
||||||
|
} else {
|
||||||
|
status &= ~0x02;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNegativeFlag(bool condition) {
|
||||||
|
if (condition) {
|
||||||
|
status |= 0x80;
|
||||||
|
} else {
|
||||||
|
status &= ~0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetOverflowFlag(bool condition) {
|
||||||
|
if (condition) {
|
||||||
|
status |= 0x40;
|
||||||
|
} else {
|
||||||
|
status &= ~0x40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCarryFlag(bool condition) {
|
||||||
|
if (condition) {
|
||||||
|
status |= 0x01;
|
||||||
|
} else {
|
||||||
|
status &= ~0x01;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetCarryFlag() { return status & 0x01; }
|
||||||
|
int GetZeroFlag() { return status & 0x02; }
|
||||||
|
|
||||||
|
int GetAccumulatorSize() { return status & 0x20; }
|
||||||
|
int GetIndexSize() { return status & 0x10; }
|
||||||
|
|
||||||
|
int GetEmulationMode() { return status & 0x04; }
|
||||||
|
|
||||||
|
// Instructions
|
||||||
|
void ADC(uint8_t operand);
|
||||||
|
|
||||||
|
void BEQ(int8_t offset) {
|
||||||
|
if (GetZeroFlag()) { // If the zero flag is set
|
||||||
|
PC += offset; // Add the offset to the program counter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BCC(int8_t offset) {
|
||||||
|
if (!GetCarryFlag()) { // If the carry flag is clear
|
||||||
|
PC += offset; // Add the offset to the program counter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BRL(int16_t offset) {
|
||||||
|
PC += offset; // Add the offset to the program counter
|
||||||
|
}
|
||||||
|
|
||||||
|
void LDA() {
|
||||||
|
A = memory[PC];
|
||||||
|
SetZeroFlag(A == 0);
|
||||||
|
SetNegativeFlag(A & 0x80);
|
||||||
|
PC++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SEC() { status |= 0x01; }
|
||||||
|
|
||||||
|
void CLC() { status &= ~0x01; }
|
||||||
|
|
||||||
|
void CLD() { status &= ~0x08; }
|
||||||
|
|
||||||
|
void CLI() { status &= ~0x04; }
|
||||||
|
|
||||||
|
void CLV() { status &= ~0x40; }
|
||||||
|
|
||||||
|
void SEI() { status |= 0x04; }
|
||||||
|
|
||||||
|
void SED() { status |= 0x08; }
|
||||||
|
|
||||||
|
void SEP() {
|
||||||
|
PC++;
|
||||||
|
auto byte = FetchByte();
|
||||||
|
status |= byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void REP() {
|
||||||
|
PC++;
|
||||||
|
auto byte = FetchByte();
|
||||||
|
status &= ~byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCD() {
|
||||||
|
D = A;
|
||||||
|
SetZeroFlag(D == 0);
|
||||||
|
SetNegativeFlag(D & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDC() {
|
||||||
|
A = D;
|
||||||
|
SetZeroFlag(A == 0);
|
||||||
|
SetNegativeFlag(A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCS() { SP = A; }
|
||||||
|
|
||||||
|
void TAX() {
|
||||||
|
X = A;
|
||||||
|
SetZeroFlag(X == 0);
|
||||||
|
SetNegativeFlag(X & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAY() {
|
||||||
|
Y = A;
|
||||||
|
SetZeroFlag(Y == 0);
|
||||||
|
SetNegativeFlag(Y & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TYA() {
|
||||||
|
A = Y;
|
||||||
|
SetZeroFlag(A == 0);
|
||||||
|
SetNegativeFlag(A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TXA() {
|
||||||
|
A = X;
|
||||||
|
SetZeroFlag(A == 0);
|
||||||
|
SetNegativeFlag(A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TXY() {
|
||||||
|
X = Y;
|
||||||
|
SetZeroFlag(X == 0);
|
||||||
|
SetNegativeFlag(X & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TYX() {
|
||||||
|
Y = X;
|
||||||
|
SetZeroFlag(Y == 0);
|
||||||
|
SetNegativeFlag(Y & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TSX() {
|
||||||
|
X = SP;
|
||||||
|
SetZeroFlag(X == 0);
|
||||||
|
SetNegativeFlag(X & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TXS() { SP = X; }
|
||||||
|
|
||||||
|
void TSC() {
|
||||||
|
A = SP;
|
||||||
|
SetZeroFlag(A == 0);
|
||||||
|
SetNegativeFlag(A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void INX() {
|
||||||
|
X++;
|
||||||
|
SetZeroFlag(X == 0);
|
||||||
|
SetNegativeFlag(X & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void INY() {
|
||||||
|
Y++;
|
||||||
|
SetZeroFlag(Y == 0);
|
||||||
|
SetNegativeFlag(Y & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appease the C++ Gods...
|
||||||
|
uint8_t operator[](int i) const override { return 0; }
|
||||||
|
uint8_t at(int i) const override { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
45
src/app/emu/mem.h
Normal file
45
src/app/emu/mem.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef MEM_H
|
||||||
|
#define MEM_H
|
||||||
|
|
||||||
|
// memory.h
|
||||||
|
class Memory {
|
||||||
|
public:
|
||||||
|
virtual ~Memory() = default;
|
||||||
|
virtual uint8_t ReadByte(uint16_t address) const = 0;
|
||||||
|
virtual uint16_t ReadWord(uint16_t address) const = 0;
|
||||||
|
virtual uint32_t ReadWordLong(uint16_t address) const = 0;
|
||||||
|
virtual void SetMemory(const std::vector<uint8_t>& data) = 0;
|
||||||
|
|
||||||
|
virtual uint8_t operator[](int i) const = 0;
|
||||||
|
virtual uint8_t at(int i) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MemoryImpl : public Memory {
|
||||||
|
public:
|
||||||
|
uint8_t ReadByte(uint16_t address) const override {
|
||||||
|
return memory_.at(address);
|
||||||
|
}
|
||||||
|
uint16_t ReadWord(uint16_t address) const override {
|
||||||
|
return static_cast<uint16_t>(memory_.at(address)) |
|
||||||
|
(static_cast<uint16_t>(memory_.at(address + 1)) << 8);
|
||||||
|
}
|
||||||
|
void SetMemory(const std::vector<uint8_t>& data) override { memory_ = data; }
|
||||||
|
|
||||||
|
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";
|
||||||
|
return memory_[0];
|
||||||
|
}
|
||||||
|
return memory_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory (64KB)
|
||||||
|
std::vector<uint8_t> memory_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEM_H
|
||||||
@@ -14,11 +14,13 @@ add_executable(
|
|||||||
yaze_test
|
yaze_test
|
||||||
yaze_test.cc
|
yaze_test.cc
|
||||||
z3ed_test.cc
|
z3ed_test.cc
|
||||||
|
cpu_test.cc
|
||||||
../src/cli/patch.cc
|
../src/cli/patch.cc
|
||||||
../src/cli/command_handler.cc
|
../src/cli/command_handler.cc
|
||||||
compression_test.cc
|
compression_test.cc
|
||||||
snes_palette_test.cc
|
snes_palette_test.cc
|
||||||
../src/app/rom.cc
|
../src/app/rom.cc
|
||||||
|
../src/app/emu/cpu.cc
|
||||||
../src/app/gfx/bitmap.cc
|
../src/app/gfx/bitmap.cc
|
||||||
../src/app/gfx/snes_tile.cc
|
../src/app/gfx/snes_tile.cc
|
||||||
../src/app/gfx/snes_palette.cc
|
../src/app/gfx/snes_palette.cc
|
||||||
|
|||||||
253
test/cpu_test.cc
Normal file
253
test/cpu_test.cc
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
#include "app/emu/cpu.h"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "app/emu/mem.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
|
||||||
|
class MockMemory : public Memory {
|
||||||
|
public:
|
||||||
|
MOCK_CONST_METHOD1(ReadByte, uint8_t(uint16_t address));
|
||||||
|
MOCK_CONST_METHOD1(ReadWord, uint16_t(uint16_t address));
|
||||||
|
MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint16_t address));
|
||||||
|
MOCK_CONST_METHOD1(at, uint8_t(int i));
|
||||||
|
uint8_t operator[](int i) const override { return at(i); }
|
||||||
|
|
||||||
|
MOCK_METHOD1(SetMemory, void(const std::vector<uint8_t>& data));
|
||||||
|
|
||||||
|
void SetMemoryContents(const std::vector<uint8_t>& data) {
|
||||||
|
memory_ = data;
|
||||||
|
ON_CALL(*this, ReadByte(::testing::_))
|
||||||
|
.WillByDefault(
|
||||||
|
[this](uint16_t address) { return memory_.at(address); });
|
||||||
|
ON_CALL(*this, ReadWord(::testing::_))
|
||||||
|
.WillByDefault([this](uint16_t address) {
|
||||||
|
return static_cast<uint16_t>(memory_.at(address)) |
|
||||||
|
(static_cast<uint16_t>(memory_.at(address + 1)) << 8);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> memory_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
TEST(CPUTest, CheckMemoryContents) {
|
||||||
|
MockMemory memory;
|
||||||
|
std::vector<uint8_t> data = {0x00, 0x01, 0x02, 0x03, 0x04};
|
||||||
|
memory.SetMemoryContents(data);
|
||||||
|
EXPECT_EQ(memory.ReadByte(0), 0x00);
|
||||||
|
EXPECT_EQ(memory.ReadByte(1), 0x01);
|
||||||
|
EXPECT_EQ(memory.ReadByte(2), 0x02);
|
||||||
|
EXPECT_EQ(memory.ReadByte(3), 0x03);
|
||||||
|
EXPECT_EQ(memory.ReadByte(4), 0x04);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, AddTwoPositiveNumbers) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0x01;
|
||||||
|
std::vector<uint8_t> data = {0x69, 0x01};
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x01));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x69); // ADC Immediate
|
||||||
|
EXPECT_EQ(cpu.A, 0x02);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, AddPositiveAndNegativeNumbers) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 10;
|
||||||
|
std::vector<uint8_t> data = {0x69, static_cast<uint8_t>(-20)};
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(-20));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x69); // ADC Immediate
|
||||||
|
EXPECT_EQ(cpu.A, static_cast<uint8_t>(-10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, CheckCarryFlag) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0xFF;
|
||||||
|
cpu.status = 0;
|
||||||
|
std::vector<uint8_t> data = {0x15, 0x01}; // Operand at address 0x15
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(1));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x69); // ADC Immediate
|
||||||
|
|
||||||
|
EXPECT_EQ(cpu.A, 0x00);
|
||||||
|
EXPECT_TRUE(cpu.GetCarryFlag());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, BCCWhenCarryFlagClear) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.SetCarryFlag(false);
|
||||||
|
cpu.PC = 0x1000;
|
||||||
|
std::vector<uint8_t> data(0x1001, 2); // Operand at address 0x1001
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(2));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x90); // BCC
|
||||||
|
EXPECT_EQ(cpu.PC, 0x1002);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, BCCWhenCarryFlagSet) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.SetCarryFlag(true);
|
||||||
|
cpu.PC = 0x1000;
|
||||||
|
std::vector<uint8_t> data(0x1001, 2); // Operand at address 0x1001
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(2));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x90); // BCC
|
||||||
|
cpu.BCC(2);
|
||||||
|
EXPECT_EQ(cpu.PC, 0x1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, BranchLongAlways) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.PC = 0x1000;
|
||||||
|
std::vector<uint8_t> data(0x1001, 2); // Operand at address 0x1001
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadWord(_)).WillOnce(Return(2));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x82); // BRL
|
||||||
|
EXPECT_EQ(cpu.PC, 0x1004);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, REP) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.status = 0xFF; // All flags set
|
||||||
|
std::vector<uint8_t> data = {0xC2, 0x30, 0x00}; // REP #0x30 (clear N & Z flags)
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0xC2); // REP
|
||||||
|
EXPECT_EQ(cpu.status, 0xCF); // 11001111
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, SEP) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.status = 0x00; // All flags cleared
|
||||||
|
std::vector<uint8_t> data = {0xE2, 0x30, 0x00}; // SEP #0x30 (set N & Z flags)
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0xE2); // SEP
|
||||||
|
EXPECT_EQ(cpu.status, 0x30); // 00110000
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, TXA) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.X = 0xAB; // X register
|
||||||
|
std::vector<uint8_t> data = {0x8A}; // TXA
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x8A); // TXA
|
||||||
|
EXPECT_EQ(cpu.A, 0xAB); // A register should now be equal to X
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, TAX) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0xBC; // A register
|
||||||
|
std::vector<uint8_t> data = {0xAA}; // TAX
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0xAA); // TAX
|
||||||
|
EXPECT_EQ(cpu.X, 0xBC); // X register should now be equal to A
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, TYA) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.Y = 0xCD; // Y register
|
||||||
|
std::vector<uint8_t> data = {0x98}; // TYA
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x98); // TYA
|
||||||
|
EXPECT_EQ(cpu.A, 0xCD); // A register should now be equal to Y
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, TAY) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0xDE; // A register
|
||||||
|
std::vector<uint8_t> data = {0xA8}; // TAY
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0xA8); // TAY
|
||||||
|
EXPECT_EQ(cpu.Y, 0xDE); // Y register should now be equal to A
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, ADCDirectPage) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0x01;
|
||||||
|
cpu.D = 0x0000;
|
||||||
|
std::vector<uint8_t> data = {0x65, 0x01, 0x05};
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(0x01));
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x65); // ADC Direct Page
|
||||||
|
EXPECT_EQ(cpu.A, 0x06);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, ADCAbsolute) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0x01;
|
||||||
|
std::vector<uint8_t> data = {0x6D, 0x10, 0x00, 0x05};
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x6D); // ADC Absolute
|
||||||
|
EXPECT_EQ(cpu.A, 0x06);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, ADCIndirectX) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0x01;
|
||||||
|
cpu.X = 0x02;
|
||||||
|
std::vector<uint8_t> data = {0x72, 0x10, 0x00, 0x00, 0x20, 0x05};
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x72); // ADC Indirect Indexed with X
|
||||||
|
EXPECT_EQ(cpu.A, 0x06);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CPUTest, ADCIndexedIndirect) {
|
||||||
|
MockMemory mock_memory;
|
||||||
|
CPU cpu(mock_memory);
|
||||||
|
cpu.A = 0x01;
|
||||||
|
cpu.X = 0x02;
|
||||||
|
std::vector<uint8_t> data = {0x61, 0x10, 0x18, 0x20, 0x05};
|
||||||
|
mock_memory.SetMemoryContents(data);
|
||||||
|
|
||||||
|
cpu.ExecuteInstruction(0x61); // ADC Indexed Indirect
|
||||||
|
EXPECT_EQ(cpu.A, 0x06);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
Reference in New Issue
Block a user