From 88b6d5bbb1b2032b27f41509fe70edb2eee4975e Mon Sep 17 00:00:00 2001 From: Jayesh Puri Date: Thu, 21 May 2026 23:16:46 +0530 Subject: [PATCH 1/2] finally big boy --- src/core/cpu/instructions/opcodes/op_0D.cpp | 240 ++++++++++++++++++-- tests/op_tests/op_0D_tests.cpp | 226 ++++++++++++++++++ 2 files changed, 450 insertions(+), 16 deletions(-) diff --git a/src/core/cpu/instructions/opcodes/op_0D.cpp b/src/core/cpu/instructions/opcodes/op_0D.cpp index edd59e7..8bdeb7c 100644 --- a/src/core/cpu/instructions/opcodes/op_0D.cpp +++ b/src/core/cpu/instructions/opcodes/op_0D.cpp @@ -7,19 +7,227 @@ constexpr int machine_cycles = 4; #define DUMMY(name) int name(ProcessingUnit&, MMU&) { return totalMachineCycles(1); } -DUMMY(op_ret_nc) // 0xD0 -DUMMY(op_pop_de) // 0xD1 -DUMMY(op_jp_nc) // 0xD2 -DUMMY(op_illegal_d3) // 0xD3 -DUMMY(op_call_nc) // 0xD4 -DUMMY(op_push_de) // 0xD5 -DUMMY(op_sub_d8) // 0xD6 -DUMMY(op_rst_10) // 0xD7 -DUMMY(op_ret_c) // 0xD8 -DUMMY(op_reti) // 0xD9 -DUMMY(op_jp_c) // 0xDA -DUMMY(op_illegal_db) // 0xDB -DUMMY(op_call_c) // 0xDC -DUMMY(op_illegal_dd) // 0xDD -DUMMY(op_sbc_a_d8) // 0xDE -DUMMY(op_rst_18) // 0xDF \ No newline at end of file +int op_ret_nc(ProcessingUnit& cpu, MMU& mmu) // 0xD0 +{ + if (cpu.get_flag_c()) { + return totalMachineCycles(2); + } + + const u16 sp = cpu.get_sp(); + const u8 lo = mmu.read(sp); + const u8 hi = mmu.read(sp + 1); + + cpu.set_sp(sp + 2); + cpu.set_pc(static_cast((hi << 8) | lo)); + + return totalMachineCycles(5); +} + +int op_pop_de(ProcessingUnit& cpu, MMU& mmu) // 0xD1 +{ + const u16 sp = cpu.get_sp(); + const u8 lo = mmu.read(sp); + const u8 hi = mmu.read(sp + 1); + + cpu.reg(ProcessingUnit::Register::E) = lo; + cpu.reg(ProcessingUnit::Register::D) = hi; + cpu.set_sp(sp + 2); + + return totalMachineCycles(3); +} + +int op_jp_nc(ProcessingUnit& cpu, MMU& mmu) // 0xD2 +{ + const u8 lo = mmu.read(cpu.inc_pc()); + const u8 hi = mmu.read(cpu.inc_pc()); + const u16 addr = static_cast((hi << 8) | lo); + + if (!cpu.get_flag_c()) + { + cpu.set_pc(addr); + return totalMachineCycles(4); + } + + return totalMachineCycles(3); +} + +int op_illegal_d3(ProcessingUnit& cpu, MMU& mmu) // 0xD3 +{ + return totalMachineCycles(1); +} + +int op_call_nc(ProcessingUnit& cpu, MMU& mmu) // 0xD4 +{ + const u8 lo = mmu.read(cpu.inc_pc()); + const u8 hi = mmu.read(cpu.inc_pc()); + const u16 addr = static_cast((hi << 8) | lo); + + if (!cpu.get_flag_c()) + { + const u16 pc = cpu.get_pc(); + const u16 sp = cpu.get_sp(); + + mmu.write(sp - 1, static_cast(pc >> 8)); + mmu.write(sp - 2, static_cast(pc & 0xFF)); + cpu.set_sp(sp - 2); + cpu.set_pc(addr); + + return totalMachineCycles(6); + } + + return totalMachineCycles(3); +} + +int op_push_de(ProcessingUnit& cpu, MMU& mmu) // 0xD5 +{ + const u8 d = cpu.reg(ProcessingUnit::Register::D); + const u8 e = cpu.reg(ProcessingUnit::Register::E); + const u16 sp = cpu.get_sp(); + + mmu.write(sp - 1, d); + mmu.write(sp - 2, e); + cpu.set_sp(sp - 2); + + return totalMachineCycles(4); +} + +int op_sub_d8(ProcessingUnit& cpu, MMU& mmu) // 0xD6 +{ + const u8 a = cpu.reg(ProcessingUnit::Register::A); + const u8 d8 = mmu.read(cpu.inc_pc()); + const int result = a - d8; + const int half_carry = (a & 0x0F) - (d8 & 0x0F); + + cpu.setFlag(ProcessingUnit::Flag::Z, (result & 0xFF) == 0); + cpu.setFlag(ProcessingUnit::Flag::N, true); + cpu.setFlag(ProcessingUnit::Flag::H, half_carry < 0); + cpu.setFlag(ProcessingUnit::Flag::C, result < 0); + + cpu.reg(ProcessingUnit::Register::A) = static_cast(result); + + return totalMachineCycles(2); +} + +int op_rst_10(ProcessingUnit& cpu, MMU& mmu) // 0xD7 +{ + const u16 pc = cpu.get_pc(); + + cpu.set_sp(cpu.get_sp() - 1); + mmu.write(cpu.get_sp(), (pc >> 8) & 0xFF); + + cpu.set_sp(cpu.get_sp() - 1); + mmu.write(cpu.get_sp(), pc & 0xFF); + + cpu.set_pc(0x10); + + return totalMachineCycles(4); +} + +int op_ret_c(ProcessingUnit& cpu, MMU& mmu) // 0xD8 +{ + if (!cpu.get_flag_c()) { + return totalMachineCycles(2); + } + + const u16 sp = cpu.get_sp(); + const u8 lo = mmu.read(sp); + const u8 hi = mmu.read(sp + 1); + + cpu.set_sp(sp + 2); + cpu.set_pc(static_cast((hi << 8) | lo)); + + return totalMachineCycles(5); +} + +int op_reti(ProcessingUnit& cpu, MMU& mmu) // 0xD9 +{ + const u16 sp = cpu.get_sp(); + const u8 lo = mmu.read(sp); + const u8 hi = mmu.read(sp + 1); + + cpu.set_sp(sp + 2); + cpu.set_pc(static_cast((hi << 8) | lo)); + cpu.setIME(true); + + return totalMachineCycles(4); +} + +int op_jp_c(ProcessingUnit& cpu, MMU& mmu) // 0xDA +{ + const u8 lo = mmu.read(cpu.inc_pc()); + const u8 hi = mmu.read(cpu.inc_pc()); + const u16 addr = static_cast((hi << 8) | lo); + + if (cpu.get_flag_c()) + { + cpu.set_pc(addr); + return totalMachineCycles(4); + } + + return totalMachineCycles(3); +} + +int op_illegal_db(ProcessingUnit& cpu, MMU& mmu) // 0xDB +{ + return totalMachineCycles(1); +} + +int op_call_c(ProcessingUnit& cpu, MMU& mmu) // 0xDC +{ + const u8 lo = mmu.read(cpu.inc_pc()); + const u8 hi = mmu.read(cpu.inc_pc()); + const u16 addr = static_cast((hi << 8) | lo); + + if (cpu.get_flag_c()) + { + const u16 pc = cpu.get_pc(); + const u16 sp = cpu.get_sp(); + + mmu.write(sp - 1, static_cast(pc >> 8)); + mmu.write(sp - 2, static_cast(pc & 0xFF)); + cpu.set_sp(sp - 2); + cpu.set_pc(addr); + + return totalMachineCycles(6); + } + + return totalMachineCycles(3); +} + +int op_illegal_dd(ProcessingUnit& cpu, MMU& mmu) // 0xDD +{ + return totalMachineCycles(1); +} + +int op_sbc_a_d8(ProcessingUnit& cpu, MMU& mmu) // 0xDE +{ + const u8 a = cpu.reg(ProcessingUnit::Register::A); + const u8 d8 = mmu.read(cpu.inc_pc()); + const u8 carry = cpu.get_flag_c() ? 1 : 0; + const int result = a - d8 - carry; + const int half_carry = (a & 0x0F) - (d8 & 0x0F) - carry; + + cpu.setFlag(ProcessingUnit::Flag::Z, (result & 0xFF) == 0); + cpu.setFlag(ProcessingUnit::Flag::N, true); + cpu.setFlag(ProcessingUnit::Flag::H, half_carry < 0); + cpu.setFlag(ProcessingUnit::Flag::C, result < 0); + + cpu.reg(ProcessingUnit::Register::A) = static_cast(result); + + return totalMachineCycles(2); +} + +int op_rst_18(ProcessingUnit& cpu, MMU& mmu) // 0xDF +{ + const u16 pc = cpu.get_pc(); + + cpu.set_sp(cpu.get_sp() - 1); + mmu.write(cpu.get_sp(), (pc >> 8) & 0xFF); + + cpu.set_sp(cpu.get_sp() - 1); + mmu.write(cpu.get_sp(), pc & 0xFF); + + cpu.set_pc(0x18); + + return totalMachineCycles(4); +} diff --git a/tests/op_tests/op_0D_tests.cpp b/tests/op_tests/op_0D_tests.cpp index 21a073d..248201d 100644 --- a/tests/op_tests/op_0D_tests.cpp +++ b/tests/op_tests/op_0D_tests.cpp @@ -8,3 +8,229 @@ class OpcodesCPUTest : public testing::Test { ProcessingUnit cpu; MMU mmu; }; + +TEST_F(OpcodesCPUTest, RET_NC_ReturnsWhenCarryClear) +{ + u16 initial_sp = 0xD000; + u16 target = 0xC123; + + cpu.set_sp(initial_sp); + cpu.setFlag(ProcessingUnit::Flag::C, false); + mmu.write(initial_sp, static_cast(target & 0xFF)); + mmu.write(initial_sp + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_ret_nc(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cpu.get_sp(), initial_sp + 2); + EXPECT_EQ(cycles, 20); +} + +TEST_F(OpcodesCPUTest, POP_DE_PopsValueFromStack) +{ + u16 initial_sp = 0xD010; + cpu.set_sp(initial_sp); + mmu.write(initial_sp, 0x78); + mmu.write(initial_sp + 1, 0x56); + + int cycles = op_pop_de(cpu, mmu); + + EXPECT_EQ(cpu.get_de(), static_cast(0x5678)); + EXPECT_EQ(cpu.get_sp(), initial_sp + 2); + EXPECT_EQ(cycles, 12); +} + +TEST_F(OpcodesCPUTest, JP_NC_JumpsWhenCarryClear) +{ + u16 initial_pc = 0xC020; + u16 target = 0xD234; + + cpu.set_pc(initial_pc); + cpu.setFlag(ProcessingUnit::Flag::C, false); + mmu.write(initial_pc, static_cast(target & 0xFF)); + mmu.write(initial_pc + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_jp_nc(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cycles, 16); +} + +TEST_F(OpcodesCPUTest, CALL_NC_PushesPCAndJumps) +{ + u16 initial_pc = 0xC030; + u16 initial_sp = 0xD100; + u16 target = 0xC555; + + cpu.set_pc(initial_pc); + cpu.set_sp(initial_sp); + cpu.setFlag(ProcessingUnit::Flag::C, false); + mmu.write(initial_pc, static_cast(target & 0xFF)); + mmu.write(initial_pc + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_call_nc(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cpu.get_sp(), initial_sp - 2); + EXPECT_EQ(mmu.read(initial_sp - 1), static_cast((initial_pc + 2) >> 8)); + EXPECT_EQ(mmu.read(initial_sp - 2), static_cast((initial_pc + 2) & 0xFF)); + EXPECT_EQ(cycles, 24); +} + +TEST_F(OpcodesCPUTest, PUSH_DE_PushesValueToStack) +{ + u16 initial_sp = 0xD200; + cpu.set_sp(initial_sp); + cpu.reg(ProcessingUnit::Register::D) = 0x12; + cpu.reg(ProcessingUnit::Register::E) = 0x34; + + int cycles = op_push_de(cpu, mmu); + + EXPECT_EQ(cpu.get_sp(), initial_sp - 2); + EXPECT_EQ(mmu.read(initial_sp - 1), 0x12); + EXPECT_EQ(mmu.read(initial_sp - 2), 0x34); + EXPECT_EQ(cycles, 16); +} + +TEST_F(OpcodesCPUTest, SUB_D8_SubtractsImmediate) +{ + u16 initial_pc = 0xC040; + cpu.set_pc(initial_pc); + cpu.reg(ProcessingUnit::Register::A) = 0x10; + mmu.write(initial_pc, 0x01); + + int cycles = op_sub_d8(cpu, mmu); + + EXPECT_EQ(cpu.reg(ProcessingUnit::Register::A), 0x0F); + EXPECT_EQ(cpu.get_pc(), initial_pc + 1); + EXPECT_FALSE(cpu.get_flag_z()); + EXPECT_TRUE(cpu.get_flag_n()); + EXPECT_TRUE(cpu.get_flag_h()); + EXPECT_FALSE(cpu.get_flag_c()); + EXPECT_EQ(cycles, 8); +} + +TEST_F(OpcodesCPUTest, RST_10_PushesPCAndJumps) +{ + u16 initial_pc = 0xC123; + u16 initial_sp = 0xD300; + + cpu.set_pc(initial_pc); + cpu.set_sp(initial_sp); + + int cycles = op_rst_10(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), 0x10); + EXPECT_EQ(cpu.get_sp(), initial_sp - 2); + EXPECT_EQ(mmu.read(initial_sp - 1), (initial_pc >> 8) & 0xFF); + EXPECT_EQ(mmu.read(initial_sp - 2), initial_pc & 0xFF); + EXPECT_EQ(cycles, 16); +} + +TEST_F(OpcodesCPUTest, RET_C_ReturnsWhenCarrySet) +{ + u16 initial_sp = 0xD400; + u16 target = 0xC321; + + cpu.set_sp(initial_sp); + cpu.setFlag(ProcessingUnit::Flag::C, true); + mmu.write(initial_sp, static_cast(target & 0xFF)); + mmu.write(initial_sp + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_ret_c(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cpu.get_sp(), initial_sp + 2); + EXPECT_EQ(cycles, 20); +} + +TEST_F(OpcodesCPUTest, RETI_PopsPCAndEnablesIME) +{ + u16 initial_sp = 0xD410; + u16 target = 0xC456; + + cpu.set_sp(initial_sp); + cpu.setIME(false); + mmu.write(initial_sp, static_cast(target & 0xFF)); + mmu.write(initial_sp + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_reti(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cpu.get_sp(), initial_sp + 2); + EXPECT_TRUE(cpu.getIME()); + EXPECT_EQ(cycles, 16); +} + +TEST_F(OpcodesCPUTest, JP_C_JumpsWhenCarrySet) +{ + u16 initial_pc = 0xC050; + u16 target = 0xD999; + + cpu.set_pc(initial_pc); + cpu.setFlag(ProcessingUnit::Flag::C, true); + mmu.write(initial_pc, static_cast(target & 0xFF)); + mmu.write(initial_pc + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_jp_c(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cycles, 16); +} + +TEST_F(OpcodesCPUTest, CALL_C_PushesPCAndJumps) +{ + u16 initial_pc = 0xC060; + u16 initial_sp = 0xD500; + u16 target = 0xC777; + + cpu.set_pc(initial_pc); + cpu.set_sp(initial_sp); + cpu.setFlag(ProcessingUnit::Flag::C, true); + mmu.write(initial_pc, static_cast(target & 0xFF)); + mmu.write(initial_pc + 1, static_cast((target >> 8) & 0xFF)); + + int cycles = op_call_c(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), target); + EXPECT_EQ(cpu.get_sp(), initial_sp - 2); + EXPECT_EQ(mmu.read(initial_sp - 1), static_cast((initial_pc + 2) >> 8)); + EXPECT_EQ(mmu.read(initial_sp - 2), static_cast((initial_pc + 2) & 0xFF)); + EXPECT_EQ(cycles, 24); +} + +TEST_F(OpcodesCPUTest, SBC_A_D8_SubtractsImmediateWithCarry) +{ + u16 initial_pc = 0xC070; + cpu.set_pc(initial_pc); + cpu.reg(ProcessingUnit::Register::A) = 0x10; + cpu.setFlag(ProcessingUnit::Flag::C, true); + mmu.write(initial_pc, 0x01); + + int cycles = op_sbc_a_d8(cpu, mmu); + + EXPECT_EQ(cpu.reg(ProcessingUnit::Register::A), 0x0E); + EXPECT_EQ(cpu.get_pc(), initial_pc + 1); + EXPECT_FALSE(cpu.get_flag_z()); + EXPECT_TRUE(cpu.get_flag_n()); + EXPECT_TRUE(cpu.get_flag_h()); + EXPECT_FALSE(cpu.get_flag_c()); + EXPECT_EQ(cycles, 8); +} + +TEST_F(OpcodesCPUTest, RST_18_PushesPCAndJumps) +{ + u16 initial_pc = 0xC123; + u16 initial_sp = 0xD600; + + cpu.set_pc(initial_pc); + cpu.set_sp(initial_sp); + + int cycles = op_rst_18(cpu, mmu); + + EXPECT_EQ(cpu.get_pc(), 0x18); + EXPECT_EQ(cpu.get_sp(), initial_sp - 2); + EXPECT_EQ(mmu.read(initial_sp - 1), (initial_pc >> 8) & 0xFF); + EXPECT_EQ(mmu.read(initial_sp - 2), initial_pc & 0xFF); + EXPECT_EQ(cycles, 16); +} From 63739d10b2dea4099a1ca6bbb9749e7d817ac858 Mon Sep 17 00:00:00 2001 From: Jayesh Puri Date: Thu, 21 May 2026 23:17:11 +0530 Subject: [PATCH 2/2] big boy readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5af37bf..17b91cb 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Game Boy (DMG-01) emulator project focused on building an accurate LR35902 CPU c ```bash ❯ ./utility_scripts/opcode_progress.py -- Base Opcodes: [██████████████████░░] 93.8% (240/256) +- Base Opcodes: [████████████████████] 100.0% (256/256) - CB Opcodes: [████████████████████] 100.0% (256/256) -- Combined: [███████████████████░] 96.9% (496/512) +- Combined: [████████████████████] 100.0% (512/512) ``` ## Current Focus