diff --git a/emulator/emulator.cpp b/emulator/emulator.cpp index 1a3805f..53a7cf9 100644 --- a/emulator/emulator.cpp +++ b/emulator/emulator.cpp @@ -303,6 +303,15 @@ std::optional pull_stack_to_status_reg(emulator::Cpu& cpu, st return new_value; } +[[nodiscard]] std::uint8_t rol_operation(emulator::Cpu& cpu, std::uint8_t value) +{ + std::uint8_t const new_value = (value << 1) | (static_cast(cpu.flags.c)); + cpu.flags.n = new_value & 0b1000'0000; + cpu.flags.z = new_value == 0; + cpu.flags.c = value & (0b1000'0000); + return new_value; +} + std::optional ror_accumulator(emulator::Cpu& cpu, std::span program) { ENABLE_PROFILER(cpu); @@ -319,7 +328,7 @@ std::optional ror_zeropage(emulator::Cpu& cpu, std::span(2); } @@ -349,9 +358,9 @@ std::optional ror_absolute(emulator::Cpu& cpu, std::span(3); } @@ -371,6 +380,75 @@ std::optional ror_absolute_indexed(emulator::Cpu& cpu, std::s cpu.mem[pos] = ror_operation(cpu, value); return std::make_optional(3); } + +std::optional rol_accumulator(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + cpu.reg.a = rol_operation(cpu, cpu.reg.a); + return std::make_optional(1); +} + +std::optional rol_zeropage(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + auto const zp = program[cpu.reg.pc + 1]; + auto const value = cpu.mem[zp]; + cpu.mem[zp] = rol_operation(cpu, value); + return std::make_optional(2); +} + +std::optional rol_zeropage_indexed(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + + auto const zp = program[cpu.reg.pc + 1]; + auto const pos = zeropage_indexed(cpu, zp, &emulator::Registers::x); + auto const value = cpu.mem[pos]; + + cpu.mem[pos] = rol_operation(cpu, value); + return std::make_optional(2); +} + +std::optional rol_absolute(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 2) >= program.size()) + { + return std::nullopt; + } + + auto const lsb = program[cpu.reg.pc + 1]; + auto const hsb = program[cpu.reg.pc + 2]; + auto const pos = (hsb << 8) | lsb; + auto const value = cpu.mem[pos]; + cpu.mem[pos] = rol_operation(cpu, value); + return std::make_optional(3); +} + +std::optional rol_absolute_indexed(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 2) >= program.size()) + { + return std::nullopt; + } + + auto const lsb = program[cpu.reg.pc + 1]; + auto const hsb = program[cpu.reg.pc + 2]; + auto const pos = absolute_indexed(cpu, lsb, hsb, &emulator::Registers::x); + auto const value = cpu.mem[pos]; + + cpu.mem[pos] = rol_operation(cpu, value); + return std::make_optional(3); +} /* End bit rotation functions */ /* Flag setting opcodes */ @@ -1298,6 +1376,13 @@ std::array get_instructions() supported_instructions[0x6e] = ror_absolute; supported_instructions[0x7e] = ror_absolute_indexed; + // ROL opcodes + supported_instructions[0x2a] = rol_accumulator; + supported_instructions[0x26] = rol_zeropage; + supported_instructions[0x36] = rol_zeropage_indexed; + supported_instructions[0x2e] = rol_absolute; + supported_instructions[0x3e] = rol_absolute_indexed; + // Stack-related opcodes supported_instructions[0x48] = push_accumulator_to_stack; supported_instructions[0x08] = push_status_reg_to_stack; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0c928ad..85f7ebe 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -52,5 +52,6 @@ create_tests(pha_tests) create_tests(php_tests) create_tests(pla_tests) create_tests(plp_tests) +create_tests(rol_tests) create_tests(ror_tests) create_tests(tx_tests) diff --git a/tests/rol_tests.cpp b/tests/rol_tests.cpp new file mode 100644 index 0000000..08b98ac --- /dev/null +++ b/tests/rol_tests.cpp @@ -0,0 +1,438 @@ +import emulator; + +#include "common.h" + +#include + +#include +#include +#include + +// NOLINTNEXTLINE +TEST(ROLTests, EightBitShift) +{ + // Complex test to make sure we can do + // a complete 8-bit shift with ROL: + + // TAX ; save byte + // ROL A ; b7 into Cb + // TXA ; restore byte + // ROL A ; eight bit rotate + + constexpr std::array program{0xaa, 0x2a, 0x8a, 0x2a}; + + emulator::Cpu cpu; + cpu.reg.a = 0b0101'0101; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.reg.a, 0b1010'1010); + ASSERT_EQ(cpu.reg.x, 0b0101'0101); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x04); + + ASSERT_EQ(cpu.flags, make_flags(0b1000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AccCarrySet) +{ + constexpr std::array program{ + {0x2a}, + }; + + emulator::Cpu cpu; + cpu.flags.c = true; + cpu.reg.a = 0b0000'0001; + + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.reg.a, 0b0000'0011); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x01); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AccCarryUnset) +{ + constexpr std::array program{ + {0x2a}, + }; + + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.reg.a = 0b0000'0001; + + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.reg.a, 0b0000'0010); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x01); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AccCarryZero) +{ + constexpr std::array program{ + {0x2a}, + }; + + // inputs that will rotate to the right to set + // the zero flag. + // test_data = {init_acc, expected_carry} + constexpr std::array, 2> test_data{{ + {0b1000'0000, true}, + {0b0000'0000, false}, + }}; + + for (auto const& [init_acc, expected_carry] : test_data) + { + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.reg.a = init_acc; + + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.reg.a, 0b0000'0000); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x01); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010 | static_cast(expected_carry))); + } +} + +// NOLINTNEXTLINE +TEST(ROLTests, ZpCarrySet) +{ + constexpr std::array program{ + {0x26, 0xfe}, + }; + + emulator::Cpu cpu; + cpu.flags.c = true; + cpu.mem[0xfe] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0xfe], 0b0000'0011); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x02); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, ZpCarryUnset) +{ + constexpr std::array program{ + {0x26, 0xfe}, + }; + + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.mem[0xfe] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0xfe], 0b0000'0010); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x02); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, ZpCarryZero) +{ + constexpr std::array program{ + {0x26, 0xfe}, + }; + + // inputs that will rotate to the right to set + // the zero flag. + // test_data = {init_mem, expected_carry} + constexpr std::array, 2> test_data{{ + {0b1000'0000, true}, + {0b0000'0000, false}, + }}; + + for (auto const& [init_mem, expected_carry] : test_data) + { + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.mem[0xfe] = init_mem; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0xfe], 0b0000'0000); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x02); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010 | static_cast(expected_carry))); + } +} + +// NOLINTNEXTLINE +TEST(ROLTests, ZpIndexedCarrySet) +{ + constexpr std::array program{ + {0x36, 0xfe}, + }; + + emulator::Cpu cpu; + cpu.flags.c = true; + cpu.reg.x = 0x02; + cpu.mem[0x00] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0x00], 0b0000'0011); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x02); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x02); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, ZpIndexedCarryUnset) +{ + constexpr std::array program{ + {0x36, 0xfe}, + }; + + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.reg.x = 0x02; + cpu.mem[0x00] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0x00], 0b0000'0010); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x02); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x02); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, ZpIndexedCarryZero) +{ + constexpr std::array program{ + {0x36, 0xfe}, + }; + + // inputs that will rotate to the right to set + // the zero flag. + // test_data = {init_mem, expected_carry} + constexpr std::array, 2> test_data{{ + {0b1000'0000, true}, + {0b0000'0000, false}, + }}; + + for (auto const& [init_mem, expected_carry] : test_data) + { + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.reg.x = 0x02; + cpu.mem[0x00] = init_mem; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0x00], 0b0000'0000); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x02); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x02); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010 | static_cast(expected_carry))); + } +} + +// NOLINTNEXTLINE +TEST(ROLTests, AbsCarrySet) +{ + constexpr std::array program{ + {0x2e, 0xfe, 0xff}, + }; + + emulator::Cpu cpu; + cpu.flags.c = true; + cpu.mem[0xfffe] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0xfffe], 0b0000'0011); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x03); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AbsCarryUnset) +{ + constexpr std::array program{ + {0x2e, 0xfe, 0xff}, + }; + + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.mem[0xfffe] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0xfffe], 0b0000'0010); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x03); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AbsCarryZero) +{ + constexpr std::array program{ + {0x2e, 0xfe, 0xff}, + }; + + // inputs that will rotate to the right to set + // the zero flag. + // test_data = {init_mem, expected_carry} + constexpr std::array, 2> test_data{{ + {0b1000'0000, true}, + {0b0000'0000, false}, + }}; + + for (auto const& [init_mem, expected_carry] : test_data) + { + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.mem[0xfffe] = init_mem; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0xfffe], 0b0000'0000); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x03); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010 | static_cast(expected_carry))); + } +} + +// NOLINTNEXTLINE +TEST(ROLTests, AbsIndexedCarrySet) +{ + constexpr std::array program{ + {0x3e, 0xfe, 0xff}, + }; + + emulator::Cpu cpu; + cpu.flags.c = true; + cpu.reg.x = 0x02; + cpu.mem[0x0000] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0x0000], 0b0000'0011); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x02); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x03); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AbsIndexedCarryUnset) +{ + constexpr std::array program{ + {0x3e, 0xfe, 0xff}, + }; + + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.reg.x = 0x02; + cpu.mem[0x0000] = 0b0000'0001; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0x00], 0b0000'0010); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x02); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x03); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); +} + +// NOLINTNEXTLINE +TEST(ROLTests, AbsIndexedCarryZero) +{ + constexpr std::array program{ + {0x3e, 0xfe, 0xff}, + }; + + // inputs that will rotate to the right to set + // the zero flag. + // test_data = {init_mem, expected_carry} + constexpr std::array, 2> test_data{{ + {0b1000'0000, true}, + {0b0000'0000, false}, + }}; + + for (auto const& [init_mem, expected_carry] : test_data) + { + emulator::Cpu cpu; + cpu.flags.c = false; + cpu.reg.x = 0x02; + cpu.mem[0x00] = init_mem; + emulator::execute(cpu, program); + + ASSERT_EQ(cpu.mem[0x00], 0b0000'0000); + + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x02); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0xff); + ASSERT_EQ(cpu.reg.pc, 0x03); + + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010 | static_cast(expected_carry))); + } +}