SNES, CPU, Emulator + tests updated

This commit is contained in:
scawful
2023-11-30 02:12:11 -05:00
parent 0bf45c86a9
commit 446734321c
15 changed files with 1144 additions and 682 deletions

View File

@@ -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<uint16_t>(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<int>(DB) << ":" << std::hex << PC << ": 0x" << std::hex
<< static_cast<int>(PB) << ":" << std::hex << PC << ": 0x" << std::hex
<< static_cast<int>(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<int>(DB) << ":" << std::hex << PC << ": 0x"
<< static_cast<int>(PB) << ":" << std::hex << PC << ": 0x"
<< std::hex << static_cast<int>(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<uint8_t>(address & 0xFFFF);
next_pc_ = static_cast<uint16_t>(address & 0xFFFF);
// Set the PBR to the upper 8 bits of the address
PB = static_cast<uint8_t>((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<int>(opcode) << ", " << mnemonic << std::endl;
return 1; // Default to 1 as a safe fallback
}
}
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -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<uint8_t> 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<int8_t>(FetchByte()); }
int16_t FetchSignedWord() {
auto offset = static_cast<int16_t>(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<uint8_t>& 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<uint8_t> 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<int8_t>(FetchByte()); }
int16_t FetchSignedWord() {
auto offset = static_cast<int16_t>(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<uint8_t>& 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;
};

View File

@@ -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<MemoryImpl*>(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<ImU64>(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<InstructionEntry>& 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)
}
}

View File

@@ -1,6 +1,8 @@
#ifndef YAZE_APP_CORE_EMULATOR_H
#define YAZE_APP_CORE_EMULATOR_H
#include <imgui/imgui.h>
#include <cstdint>
#include <vector>
@@ -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<Bookmark> bookmarks;
void RenderCpuInstructionLog(
const std::vector<InstructionEntry>& 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

View File

@@ -0,0 +1,118 @@
#pragma once
#include <cstdint>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include "app/emu/internal/opcodes.h"
namespace yaze {
namespace app {
namespace emu {
class AsmParser {
public:
std::vector<uint8_t> 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<uint8_t> 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<uint8_t>& bytes,
const std::string& operand,
const std::string& addressing_mode) {
if (addressing_mode == ".b") {
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
} else if (addressing_mode == ".w") {
uint16_t word_operand =
static_cast<uint16_t>(std::stoi(operand, nullptr, 16));
bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF));
bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF));
} else if (addressing_mode == ".l") {
uint32_t long_operand =
static_cast<uint32_t>(std::stoul(operand, nullptr, 16));
bytes.push_back(static_cast<uint8_t>(long_operand & 0xFF));
bytes.push_back(static_cast<uint8_t>((long_operand >> 8) & 0xFF));
bytes.push_back(static_cast<uint8_t>((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<std::string, uint8_t> mnemonic_to_opcode_;
};
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,82 @@
#include "app/emu/memory/memory.h"
#include <imgui/imgui.h>
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#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

View File

@@ -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

View File

@@ -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<uint8_t>& data) {
memory_.resize(64000);

View File

@@ -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;

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -4,6 +4,8 @@
#include <gtest/gtest.h>
#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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0b10101010}; // AND #0b10101010
cpu.A = 0b11110000; // A register
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data(0x1001, 2); // Operand at address 0x1001
std::vector<uint8_t> 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<uint8_t> data = {0x10, 0x02, 0x01}; // Operand at address 0x1001
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0x00, 0x10}; // BIT
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0x02}; // BMI
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0x02}; // BNE
std::vector<uint8_t> 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<uint8_t> data = {0xD0, 0x02}; // BNE
std::vector<uint8_t> 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<uint8_t> data = {0x02}; // BPL
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0x02}; // BRA
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> data(0x1001, 2); // Operand at address 0x1001
std::vector<uint8_t> 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<uint8_t> data = {0x02}; // BVC
std::vector<uint8_t> 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<uint8_t> data = {0x02}; // BVS
std::vector<uint8_t> 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<uint8_t> data = {0x34, 0x12}; // CPX #0x1234
std::vector<uint8_t> 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<uint8_t> data = {0x78, 0x56}; // CPY #0x5678
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0xEE, 0x7F, 0xFF};
std::vector<uint8_t> 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<uint8_t> data = {0xE6, 0xFF};
cpu.D = 0x0200; // Setting Direct Page register to 0x0200
std::vector<uint8_t> 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<uint8_t> data = {0xEE, 0xFF, 0xFF};
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> data = {0xF6, 0x7E};
cpu.D = 0x0200; // Setting Direct Page register to 0x0200
std::vector<uint8_t> 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<uint8_t> data = {0xFE, 0x7F, 0xFF};
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> data = {0xA9, 0x7F, 0x7F};
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> data = {0xA5, 0x7F};
mock_memory.SetMemoryContents(data);
EXPECT_CALL(mock_memory, ReadByte(0x7F)).WillOnce(Return(0x80));
cpu.SetAccumulatorSize(true);
cpu.D = 0x0200;
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> data = {0x30, 0x00}; // REP #0x30 (clear N & Z flags)
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
@@ -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<uint8_t> data = {0x30, 0x00}; // SEP #0x30 (set N & Z flags)
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

View File

@@ -1,28 +1,146 @@
#include "app/emu/ppu.h"
#include "app/emu/video/ppu.h"
#include <gmock/gmock.h>
#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<uint8_t>& data), (override));
MOCK_METHOD(std::vector<uint8_t>, getOAMData, (), (const, override));
MOCK_METHOD(void, setVRAMData, (const std::vector<uint8_t>& data),
(override));
MOCK_METHOD(std::vector<uint8_t>, getVRAMData, (), (const, override));
MOCK_METHOD(void, setCGRAMData, (const std::vector<uint8_t>& data),
(override));
MOCK_METHOD(std::vector<uint8_t>, getCGRAMData, (), (const, override));
MOCK_METHOD(void, renderFrame, (), (override));
MOCK_METHOD(std::vector<uint32_t>, 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<uint8_t>&, GetFrameBuffer, (),
(const, override));
MOCK_METHOD(std::shared_ptr<gfx::Bitmap>, 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<uint8_t> internalFrameBuffer;
std::vector<uint8_t> vram;
std::vector<SpriteAttributes> sprites;
std::vector<Tilemap> 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<uint8_t> spriteData = {/* ... */};
const std::vector<uint8_t> 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

View File

@@ -1,88 +0,0 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#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<std::string>& arg),
(override));
};
// Test fixture class
class CommandHandlerTest : public ::testing::Test {
protected:
std::shared_ptr<MockCommandHandler> mockHandler =
std::make_shared<MockCommandHandler>();
};
// 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());
// }