diff --git a/CMakeLists.txt b/CMakeLists.txt index c884cd1..d9a81a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,14 @@ set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN On) option(CLOCK_SPEED_MHZ "The clock speed for the processor") +option(BUILD_PROFILER "Build profiling for the instruction handlers") add_subdirectory(emulator) add_subdirectory(emulator_app) +if (BUILD_PROFILER) + add_subdirectory(profiler) +endif() + enable_testing() add_subdirectory(tests) diff --git a/CMakePresets.json b/CMakePresets.json index 1144867..148e7c7 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -100,7 +100,8 @@ "binaryDir": "${sourceDir}/build/unix-rel-ninja", "inherits": ["unix-ninja", "rel", "conan-rel"], "cacheVariables": { - "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/unix-rel-ninja/install" + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/unix-rel-ninja/install", + "BUILD_PROFILER": true } }, { @@ -130,7 +131,8 @@ "cacheVariables": { "CMAKE_CXX_FLAGS": "-O0 --coverage -g -fsanitize=address", "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/unix-deb-ninja/install", - "CLOCK_SPEED_MHZ": "3.58" + "CLOCK_SPEED_MHZ": "3.58", + "BUILD_PROFILER": true } }, { diff --git a/conanfile.txt b/conanfile.txt index ea2cdea..8826c09 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,6 +1,6 @@ [requires] +fmt/11.0.2 gtest/1.13.0 -fmt/10.2.1 raylib/5.0 [generators] diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index c7688d4..5d5f5ae 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -1,4 +1,3 @@ - find_package(fmt REQUIRED) add_library(emulator) @@ -14,3 +13,12 @@ if (CLOCK_SPEED_MHZ) endif() target_link_libraries(emulator PRIVATE fmt::fmt) +if(BUILD_PROFILER) + message(STATUS "Building profiler") + target_link_libraries(emulator PRIVATE profiler) + target_compile_definitions(emulator PRIVATE BUILD_PROFILER=) +else() + message(STATUS "Profiler not build") +endif() + +target_link_libraries(emulator PRIVATE fmt::fmt) diff --git a/emulator/emulator.cpp b/emulator/emulator.cpp index 2db1b1d..4f408a1 100644 --- a/emulator/emulator.cpp +++ b/emulator/emulator.cpp @@ -1,12 +1,19 @@ module; +// #ifdef BUILD_PROFILER +import profiler; +// #endif // BUILD_PROFILER + +#include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -19,10 +26,53 @@ module; #define CLOCK_SPEED_MHZ 1.79 #endif // CLOCK_SPEED_MHZ +#include + export module emulator; +// Define a macro for the profiler +#ifdef BUILD_PROFILER +struct ProfileBook +{ + std::unordered_map functions; + + bool update(std::string const& func_name, double profile) + { + auto found = functions.find(func_name); + if (found != end(functions)) + { + // update it + found->second += profile; + return true; + } + + functions[func_name] = profile; + return true; + } +}; + +#define ENABLE_PROFILER(cpu) \ + volatile auto profile_result_666 = profiler::FunctionProfiler(cpu.profiler_book) +#else +#define ENABLE_PROFILER(cpu) +#endif // BUILD_PROFILER + + export namespace emulator { + class OpcodeNotSupported : public std::exception + { + public: + OpcodeNotSupported(std::string opcode) : _error_msg{"opcode not supported: " + opcode} {} + + const char* what() const throw() override + { + return _error_msg.c_str(); + } + + private: + std::string _error_msg; + }; struct Registers { @@ -64,6 +114,25 @@ export namespace emulator // Clock speed for this particular CPU double clock_speed = CLOCK_SPEED_MHZ; + +#ifdef BUILD_PROFILER + // If profiling is enabled, create space for a bookeper + std::shared_ptr profiler_book{std::make_shared()}; + + std::unordered_map current_profile() + { + if (!profiler_book) + { + return {}; + } + return profiler_book->functions; + } +#else + std::unordered_map current_profile() + { + return {}; + } +#endif // BUILD_PROFILER }; } // namespace emulator @@ -84,6 +153,7 @@ Instruction ld_immediate(std::uint8_t emulator::Registers::*reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -102,6 +172,7 @@ Instruction ld_zeropage(std::uint8_t emulator::Registers::*reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -123,6 +194,8 @@ Instruction ld_zeropage_plus_reg(std::uint8_t emulator::Registers::*to, std::uin { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -143,6 +216,7 @@ Instruction ld_absolute(std::uint8_t emulator::Registers::*to) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 2) >= program.size()) { return std::nullopt; @@ -165,6 +239,7 @@ Instruction ld_absolute_plus_reg(std::uint8_t emulator::Registers::*to, std::uin { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 2) >= program.size()) { return std::nullopt; @@ -190,6 +265,7 @@ Instruction ld_index_indirect(std::uint8_t emulator::Registers::*to, std::uint8_ { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -215,6 +291,7 @@ Instruction ld_indirect_index(std::uint8_t emulator::Registers::*to, std::uint8_ { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -237,6 +314,7 @@ Instruction ld_indirect_index(std::uint8_t emulator::Registers::*to, std::uint8_ std::optional inc_zeropage(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -252,6 +330,7 @@ std::optional inc_zeropage(emulator::Cpu& cpu, std::span inc_zeropage_plus_x(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -267,6 +346,7 @@ std::optional inc_zeropage_plus_x(emulator::Cpu& cpu, std::sp std::optional inc_absolute(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 2) >= program.size()) { return std::nullopt; @@ -284,6 +364,8 @@ std::optional inc_absolute(emulator::Cpu& cpu, std::span inc_absolute_plus_x(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 2) >= program.size()) { return std::nullopt; @@ -303,6 +385,7 @@ std::optional inc_absolute_plus_x(emulator::Cpu& cpu, std::sp std::optional dec_zeropage(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -318,8 +401,9 @@ std::optional dec_zeropage(emulator::Cpu& cpu, std::span program) + return [=](emulator::Cpu& cpu, std::span /* program */) { + ENABLE_PROFILER(cpu); ((cpu.reg).*reg)++; cpu.flags.n = (cpu.reg).*reg & 0b1000'0000; cpu.flags.z = (cpu.reg).*reg == 0; @@ -329,8 +413,9 @@ Instruction inc_reg(std::uint8_t emulator::Registers::*reg) Instruction dec_reg(std::uint8_t emulator::Registers::*reg) { - return [=](emulator::Cpu& cpu, std::span program) + return [=](emulator::Cpu& cpu, std::span /* program */) { + ENABLE_PROFILER(cpu); ((cpu.reg).*reg)--; cpu.flags.n = (cpu.reg).*reg & 0b1000'0000; cpu.flags.z = (cpu.reg).*reg == 0; @@ -340,8 +425,9 @@ Instruction dec_reg(std::uint8_t emulator::Registers::*reg) Instruction transfer_regs(std::uint8_t emulator::Registers::*from, std::uint8_t emulator::Registers::*to) { - return [=](emulator::Cpu& cpu, std::span program) + return [=](emulator::Cpu& cpu, std::span /* program */) { + ENABLE_PROFILER(cpu); (cpu.reg).*to = (cpu.reg).*from; cpu.flags.z = (cpu.reg).*to == 0; cpu.flags.n = (cpu.reg).*to & 0b1000'0000; @@ -353,6 +439,7 @@ Instruction transfer_regs(std::uint8_t emulator::Registers::*from, std::uint8_t // does not set any flags. std::optional txa(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); cpu.reg.sp = cpu.reg.x; return std::make_optional(1); } @@ -361,6 +448,7 @@ Instruction st_indirect(std::uint8_t emulator::Registers::*from, std::uint8_t em { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -384,6 +472,7 @@ Instruction st_zeropage(std::uint8_t emulator::Registers::*from) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); // LOAD Value into accumulator if ((cpu.reg.pc + 1) >= program.size()) { @@ -401,6 +490,7 @@ Instruction st_absolute(std::uint8_t emulator::Registers::*from) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 2) >= program.size()) { return std::nullopt; @@ -423,6 +513,7 @@ Instruction cmp_immediate_reg(std::uint8_t emulator::Registers::*reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -442,6 +533,8 @@ Instruction cmp_zeropage_reg(std::uint8_t emulator::Registers::*reg) { return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -461,6 +554,7 @@ Instruction cmp_zeropage_reg(std::uint8_t emulator::Registers::*reg) // Branching functions here std::optional bne(emulator::Cpu& cpu, std::span program) { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -484,6 +578,7 @@ Instruction branch_flag_value(bool emulator::Flags::*flag) // TODO : Do we need a return here? return [=](emulator::Cpu& cpu, std::span program) -> std::optional { + ENABLE_PROFILER(cpu); if ((cpu.reg.pc + 1) >= program.size()) { return std::nullopt; @@ -494,87 +589,269 @@ Instruction branch_flag_value(bool emulator::Flags::*flag) }; } +// Logical operations +std::optional or_acc_immediate(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + + auto const value = program[cpu.reg.pc + 1]; + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + return std::make_optional(2); +} + +std::optional or_acc_zeropage(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + + auto const offset = program[cpu.reg.pc + 1]; + auto const value = cpu.mem[offset]; + + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + return std::make_optional(2); +} + +std::optional or_acc_zeropage_x(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + + auto const zp_offset = program[cpu.reg.pc + 1]; + auto const offset = static_cast(zp_offset + cpu.reg.x); + auto const value = cpu.mem[offset]; + + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + return std::make_optional(2); +} + +std::optional or_acc_absolute(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 2) >= program.size()) + { + return std::nullopt; + } + + // (hsb << 8) + lsb convert little endian to the address + auto const lsb = program[cpu.reg.pc + 1]; + auto const hsb = program[cpu.reg.pc + 2]; + auto const addr = static_cast((hsb << 8) | lsb); + + auto const value = cpu.mem[addr]; + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + + return std::make_optional(3); +} + +// TODO : merge this with the abs y function, same code +std::optional or_acc_absolute_x(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 2) >= program.size()) + { + return std::nullopt; + } + + // (hsb << 8) + lsb convert little endian to the address + auto const lsb = program[cpu.reg.pc + 1]; + auto const hsb = program[cpu.reg.pc + 2]; + auto const abs_addr = static_cast((hsb << 8) | lsb); + auto const addr = static_cast(abs_addr + cpu.reg.x); + // TOOD : add 1 to cycle if this addr crosses page boundary + + auto const value = cpu.mem[addr]; + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + + return std::make_optional(3); +} + +// TODO : merge this with the abs x function, same code +std::optional or_acc_absolute_y(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 2) >= program.size()) + { + return std::nullopt; + } + + // (hsb << 8) + lsb convert little endian to the address + auto const lsb = program[cpu.reg.pc + 1]; + auto const hsb = program[cpu.reg.pc + 2]; + auto const abs_addr = static_cast((hsb << 8) | lsb); + auto const addr = static_cast(abs_addr + cpu.reg.y); + // TOOD : add 1 to cycle if this addr crosses page boundary + + auto const value = cpu.mem[addr]; + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + + return std::make_optional(3); +} + +std::optional or_acc_index_indirect(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + + // (hsb << 8) + lsb convert little endian to the address + auto const zp_addr = static_cast(program[cpu.reg.pc + 1] + cpu.reg.x); + + auto const lsb = cpu.mem[static_cast(zp_addr + 1)]; + auto const hsb = cpu.mem[static_cast(zp_addr + 2)]; + auto const abs_addr = static_cast((hsb << 8) | lsb); + + auto const value = cpu.mem[abs_addr]; + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + + return std::make_optional(2); +} + +std::optional or_acc_indirect_index(emulator::Cpu& cpu, std::span program) +{ + ENABLE_PROFILER(cpu); + if ((cpu.reg.pc + 1) >= program.size()) + { + return std::nullopt; + } + + auto const zp_addr = program[cpu.reg.pc + 1]; + auto const lsb = cpu.mem[static_cast(zp_addr + 1)]; + auto const hsb = cpu.mem[static_cast(zp_addr + 2)]; + auto const abs_addr = static_cast(((hsb << 8) | lsb) + cpu.reg.y); + + auto const value = cpu.mem[abs_addr]; + cpu.reg.a = cpu.reg.a | value; + cpu.flags.n = 0b1000'0000 & cpu.reg.a; + cpu.flags.z = !static_cast(cpu.reg.a); + + return std::make_optional(2); +} + // TODO : provide support for counting the number of cycles passed // from the start of the program -std::unordered_map get_instructions() +std::array get_instructions() { // Byte key indicates which function we need to call // to handle the specific instruction - return {{ - {0x00, [](emulator::Cpu&, std::span) { return std::nullopt; }}, - - - // Transferring instrucitons here - {0x8a, transfer_regs(&emulator::Registers::x, &emulator::Registers::a)}, // TXA - {0x98, transfer_regs(&emulator::Registers::y, &emulator::Registers::a)}, // TYA - {0xa8, transfer_regs(&emulator::Registers::a, &emulator::Registers::y)}, // TAY - {0xaa, transfer_regs(&emulator::Registers::a, &emulator::Registers::x)}, // TAX - {0xba, transfer_regs(&emulator::Registers::sp, &emulator::Registers::x)}, // TSX - {0x9a, txa}, - - // Memory storing functions here - // TODO : Add more tests for these - {0x85, st_zeropage(&emulator::Registers::a)}, - {0x8d, st_absolute(&emulator::Registers::a)}, - {0x91, st_indirect(&emulator::Registers::a, &emulator::Registers::y)}, - {0x86, st_zeropage(&emulator::Registers::x)}, - {0x8e, st_absolute(&emulator::Registers::x)}, - {0x84, st_zeropage(&emulator::Registers::y)}, - {0x8c, st_absolute(&emulator::Registers::y)}, - - // TODO : Finish supporting the ld* family - // of instructions - {0xa9, ld_immediate(&emulator::Registers::a)}, - {0xa5, ld_zeropage(&emulator::Registers::a)}, - {0xb5, ld_zeropage_plus_reg(&emulator::Registers::a, &emulator::Registers::x)}, - {0xbd, ld_absolute_plus_reg(&emulator::Registers::a, &emulator::Registers::x)}, - {0xb9, ld_absolute_plus_reg(&emulator::Registers::a, &emulator::Registers::y)}, - {0xa1, ld_index_indirect(&emulator::Registers::a, &emulator::Registers::x)}, - {0xb1, ld_indirect_index(&emulator::Registers::a, &emulator::Registers::y)}, - {0xad, ld_absolute(&emulator::Registers::a)}, - {0xa2, ld_immediate(&emulator::Registers::x)}, - {0xa6, ld_zeropage(&emulator::Registers::x)}, - {0xb6, ld_zeropage_plus_reg(&emulator::Registers::x, &emulator::Registers::y)}, - {0xae, ld_absolute(&emulator::Registers::x)}, - {0xbe, ld_absolute_plus_reg(&emulator::Registers::x, &emulator::Registers::y)}, - {0xa0, ld_immediate(&emulator::Registers::y)}, - {0xa4, ld_zeropage(&emulator::Registers::y)}, - {0xb4, ld_zeropage_plus_reg(&emulator::Registers::y, &emulator::Registers::x)}, - {0xbc, ld_absolute_plus_reg(&emulator::Registers::y, &emulator::Registers::x)}, - {0xac, ld_absolute(&emulator::Registers::y)}, - - // TODO : finish support for the cmp* family - // of instructions - {0xc0, cmp_immediate_reg(&emulator::Registers::y)}, - {0xc5, cmp_zeropage_reg(&emulator::Registers::a)}, - {0xe0, cmp_immediate_reg(&emulator::Registers::x)}, - - {0xf0, branch_flag_value(&emulator::Flags::z)}, - {0xd0, branch_flag_value(&emulator::Flags::z)}, - {0x30, branch_flag_value(&emulator::Flags::n)}, - {0x10, branch_flag_value(&emulator::Flags::n)}, - {0xb0, branch_flag_value(&emulator::Flags::c)}, - {0x90, branch_flag_value(&emulator::Flags::c)}, - {0x70, branch_flag_value(&emulator::Flags::v)}, - {0x50, branch_flag_value(&emulator::Flags::v)}, - - // TODO : finish support for the inc/dec - // instructions - {0xe6, inc_zeropage}, - {0xf6, inc_zeropage_plus_x}, // TODO : Need tests for this - {0xee, inc_absolute}, - {0xfe, inc_absolute_plus_x}, // TODO : Need tests for this - {0xc8, inc_reg(&emulator::Registers::y)}, - {0xe8, inc_reg(&emulator::Registers::x)}, - {0xc6, dec_zeropage}, - {0x88, dec_reg(&emulator::Registers::y)}, - {0xca, dec_reg(&emulator::Registers::x)}, - }}; + // using Instruction = std::function(emulator::Cpu&, std::span)>; + std::array supported_instructions{}; + for (std::size_t i = 0; i < supported_instructions.size(); ++i) + { + supported_instructions[i] = [=](emulator::Cpu&, std::span) -> std::optional + { throw emulator::OpcodeNotSupported(fmt::format("{0:#x}", i)); }; + } + // make sure that all default elemets of supported_instructions + // are functions that will raise an error + + // Supported instructions + supported_instructions[0x00] = [](emulator::Cpu&, std::span) { return std::nullopt; }; + supported_instructions[0x8a] = transfer_regs(&emulator::Registers::x, &emulator::Registers::a); + supported_instructions[0x98] = transfer_regs(&emulator::Registers::y, &emulator::Registers::a); + supported_instructions[0xa8] = transfer_regs(&emulator::Registers::a, &emulator::Registers::y); + supported_instructions[0xaa] = transfer_regs(&emulator::Registers::a, &emulator::Registers::x); + supported_instructions[0xba] = transfer_regs(&emulator::Registers::sp, &emulator::Registers::x); + supported_instructions[0x9a] = txa; + + supported_instructions[0x85] = st_zeropage(&emulator::Registers::a); + supported_instructions[0x8d] = st_absolute(&emulator::Registers::a); + supported_instructions[0x91] = st_indirect(&emulator::Registers::a, &emulator::Registers::y); + supported_instructions[0x86] = st_zeropage(&emulator::Registers::x); + supported_instructions[0x8e] = st_absolute(&emulator::Registers::x); + supported_instructions[0x84] = st_zeropage(&emulator::Registers::y); + supported_instructions[0x8c] = st_absolute(&emulator::Registers::y); + + supported_instructions[0xa9] = ld_immediate(&emulator::Registers::a); + supported_instructions[0xa5] = ld_zeropage(&emulator::Registers::a); + supported_instructions[0xb5] = ld_zeropage_plus_reg(&emulator::Registers::a, &emulator::Registers::x); + supported_instructions[0xbd] = ld_absolute_plus_reg(&emulator::Registers::a, &emulator::Registers::x); + supported_instructions[0xb9] = ld_absolute_plus_reg(&emulator::Registers::a, &emulator::Registers::y); + supported_instructions[0xa1] = ld_index_indirect(&emulator::Registers::a, &emulator::Registers::x); + supported_instructions[0xb1] = ld_indirect_index(&emulator::Registers::a, &emulator::Registers::y); + supported_instructions[0xad] = ld_absolute(&emulator::Registers::a); + supported_instructions[0xa2] = ld_immediate(&emulator::Registers::x); + supported_instructions[0xa6] = ld_zeropage(&emulator::Registers::x); + supported_instructions[0xb6] = ld_zeropage_plus_reg(&emulator::Registers::x, &emulator::Registers::y); + supported_instructions[0xae] = ld_absolute(&emulator::Registers::x); + supported_instructions[0xbe] = ld_absolute_plus_reg(&emulator::Registers::x, &emulator::Registers::y); + supported_instructions[0xa0] = ld_immediate(&emulator::Registers::y); + supported_instructions[0xa4] = ld_zeropage(&emulator::Registers::y); + supported_instructions[0xb4] = ld_zeropage_plus_reg(&emulator::Registers::y, &emulator::Registers::x); + supported_instructions[0xbc] = ld_absolute_plus_reg(&emulator::Registers::y, &emulator::Registers::x); + supported_instructions[0xac] = ld_absolute(&emulator::Registers::y); + + // This is the immediate mode compares to registers + supported_instructions[0xc9] = cmp_immediate_reg(&emulator::Registers::a); // TODO : test + supported_instructions[0xc0] = cmp_immediate_reg(&emulator::Registers::y); + supported_instructions[0xe0] = cmp_immediate_reg(&emulator::Registers::x); + + // TODO : support for compare zeropage simple + supported_instructions[0xc5] = cmp_zeropage_reg(&emulator::Registers::a); + supported_instructions[0xe4] = cmp_zeropage_reg(&emulator::Registers::x); // TODO : test + supported_instructions[0xc4] = cmp_zeropage_reg(&emulator::Registers::y); // TODO : test + + supported_instructions[0xf0] = branch_flag_value(&emulator::Flags::z); + supported_instructions[0xd0] = branch_flag_value(&emulator::Flags::z); + supported_instructions[0x30] = branch_flag_value(&emulator::Flags::n); + supported_instructions[0x10] = branch_flag_value(&emulator::Flags::n); + supported_instructions[0xb0] = branch_flag_value(&emulator::Flags::c); + supported_instructions[0x90] = branch_flag_value(&emulator::Flags::c); + supported_instructions[0x70] = branch_flag_value(&emulator::Flags::v); + supported_instructions[0x50] = branch_flag_value(&emulator::Flags::v); + + supported_instructions[0xe6] = inc_zeropage; + supported_instructions[0xf6] = inc_zeropage_plus_x; + supported_instructions[0xee] = inc_absolute; + supported_instructions[0xfe] = inc_absolute_plus_x; + supported_instructions[0xc8] = inc_reg(&emulator::Registers::y); + supported_instructions[0xe8] = inc_reg(&emulator::Registers::x); + + supported_instructions[0xc6] = dec_zeropage; + supported_instructions[0x88] = dec_reg(&emulator::Registers::y); + supported_instructions[0xca] = dec_reg(&emulator::Registers::x); + + // Logical operators + supported_instructions[0x05] = or_acc_zeropage; + supported_instructions[0x09] = or_acc_immediate; + supported_instructions[0x15] = or_acc_immediate; + supported_instructions[0x0d] = or_acc_absolute; + supported_instructions[0x1d] = or_acc_absolute_x; + supported_instructions[0x19] = or_acc_absolute_y; + supported_instructions[0x01] = or_acc_index_indirect; + supported_instructions[0x11] = or_acc_indirect_index; + + return supported_instructions; } std::optional execute_next(emulator::Cpu& cpu, std::span program, - std::unordered_map instructions) + std::array instructions) { + ENABLE_PROFILER(cpu); // Read 1 byte for the operator if (cpu.reg.pc >= program.size()) { @@ -583,33 +860,36 @@ std::optional execute_next(emulator::Cpu& cpu, std::spansecond(cpu, program); + return instruction(cpu, program); + } + catch (emulator::OpcodeNotSupported const& e) + { + std::cout << e.what() << std::endl; + return std::nullopt; } - - // Instruction not supported here - // unsupported command - return std::nullopt; } + export namespace emulator { - std::size_t execute(Cpu& cpu, std::span program) + [[nodiscard]] std::optional execute(Cpu& cpu, std::span program) { - auto const instructions = get_instructions(); std::size_t n_cycles = 0; + ENABLE_PROFILER(cpu); + auto const instructions = get_instructions(); while (cpu.reg.pc < program.size()) { auto const time_now = std::chrono::high_resolution_clock::now(); auto maybe_increment = execute_next(cpu, program, instructions); if (!maybe_increment) { - return false; + return std::nullopt; } cpu.reg.pc += maybe_increment->bytes; diff --git a/emulator_app/CMakeLists.txt b/emulator_app/CMakeLists.txt index bb19f12..7b84406 100644 --- a/emulator_app/CMakeLists.txt +++ b/emulator_app/CMakeLists.txt @@ -1,10 +1,8 @@ -find_package(fmt REQUIRED) find_package(raylib REQUIRED) add_executable(emulator_app main.cpp) target_link_libraries(emulator_app PRIVATE emulator - fmt::fmt raylib) diff --git a/emulator_app/main.cpp b/emulator_app/main.cpp index 68f3503..d1d4dae 100644 --- a/emulator_app/main.cpp +++ b/emulator_app/main.cpp @@ -6,10 +6,10 @@ import emulator; #include #include #include -#include #include #include +#include namespace { @@ -81,7 +81,15 @@ int main(int argc, char** argv) std::vector program_contents{(std::istreambuf_iterator(file)), std::istreambuf_iterator()}; - emulator::execute(easy65k, {reinterpret_cast(program_contents.data()), program_contents.size()}); + if (!emulator::execute(easy65k, {reinterpret_cast(program_contents.data()), program_contents.size()})) + { + std::cout << std::format("failed to execute the program\n"); + } + + for (auto const& [function, profile] : easy65k.current_profile()) + { + std::cout << std::format("{}: {:.6f}\n", function, profile); + } draw(easy65k); } diff --git a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt new file mode 100644 index 0000000..39ee04b --- /dev/null +++ b/profiler/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(profiler) + +target_sources(profiler PUBLIC + FILE_SET CXX_MODULES FILES profiler.cpp) diff --git a/profiler/profiler.cpp b/profiler/profiler.cpp new file mode 100644 index 0000000..d3b08b6 --- /dev/null +++ b/profiler/profiler.cpp @@ -0,0 +1,56 @@ +module; + +#include +#include +#include +#include +#include +#include + +export module profiler; + +export namespace profiler +{ + + // Constrait for collections that can be used to store + // a profile result + template + concept Bookeper = std::convertible_to && std::convertible_to + && requires(T v, FuncName func_name, Measure measure) { + { + v.update(func_name, measure) + } -> std::same_as; + }; + + template + requires Bookeper + class FunctionProfiler + { + public: + FunctionProfiler(std::shared_ptr books, const std::source_location& sl = std::source_location::current()) + : _unit_name{sl.function_name()}, _start{std::chrono::high_resolution_clock::now()}, _books{books} + { + } + + ~FunctionProfiler() + { + using namespace std::chrono; + auto const now = std::chrono::high_resolution_clock::now(); + auto const time_diff = duration_cast>(now - _start); + + // TODO : Save the info in a map or something + if (!_books) + { + std::cout << "cannot store profile as books is null\n"; + return; + } + + _books->update(_unit_name, time_diff.count()); + } + + private: + std::string _unit_name; + std::chrono::time_point _start; + std::shared_ptr _books; + }; +} // namespace profiler diff --git a/scripts/gather_cov.sh b/scripts/gather_cov.sh new file mode 100755 index 0000000..6c43f38 --- /dev/null +++ b/scripts/gather_cov.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: ' This script will find run lcov to generate + a report and then push it to codecov. +' + +THIS_DIR=$(cd "$(dirname "$0")" && pwd) +PROJECT_DIR="$(cd "${THIS_DIR}/.." && pwd)" + +echo "Project root dir: ${PROJECT_DIR}" +lcov --capture --directory . --output-file lcov.info \ + --rc geninfo_adjust_src_path="${PROJECT_DIR}/build/unix-deb-ninja \ + => ${PROJECT_DIR}" \ + --gcov-tool "${PROJECT_DIR}/scripts/gcov_for_clang.sh" \ + --ignore-errors inconsistent \ + --ignore-errors unused + + +SYSTEM_DIR="/usr/" +CONAN_DIR="${HOME}/.conan2/" + +# Remove the system & test dirs +lcov --ignore-errors inconsistent --remove lcov.info "${SYSTEM_DIR}*" --output-file lcov.info +lcov --ignore-errors inconsistent --remove lcov.info "${CONAN_DIR}*" --output-file lcov.info +lcov --ignore-errors inconsistent --remove lcov.info "${PROJECT_DIR}/tests*" --output-file lcov.info + +# Generate HTML report into coverage/ +genhtml lcov.info --ignore-errors inconsistent --ignore-errors corrupt --output-directory coverage > coverage_stats.txt +LINES_COV="$(grep lines < coverage_stats.txt | grep -Eo "[0-9]+\.[0-9]+%" | cut -d '.' -f1)" +FUNCTIONS_COV="$(grep functions < coverage_stats.txt | grep -Eo "[0-9]+\.[0-9]+%" | cut -d '.' -f1)" + +# Make sure we fail the pipeline if coverage is less than something +LINES_COV_THRESH="98" +if [ "$LINES_COV" -lt "$LINES_COV_THRESH" ]; then + echo "Line coverage is less than threshold $LINES_COV_THRESH" + exit 1 +fi + +FUNCTIONS_COV_THRESH="100" +if [ "$FUNCTIONS_COV" -lt "$FUNCTIONS_COV_THRESH" ]; then + echo "Functions coverage is less than threshold $FUNCTIONS_COV_THRESH" + exit 1 +fi diff --git a/scripts/gcov_for_clang.sh b/scripts/gcov_for_clang.sh new file mode 100755 index 0000000..4e320d8 --- /dev/null +++ b/scripts/gcov_for_clang.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec llvm-cov gcov "$@" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ca77f0..48fd2de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ include(CTest) +include(GoogleTest) find_package(GTest REQUIRED COMPONENTS gtest_main) @@ -15,16 +16,19 @@ function(create_tests cpp_file_name) gtest::gtest) add_test(NAME ${cpp_file_name} COMMAND ${cpp_file_name}) + gtest_discover_tests(${cpp_file_name}) endfunction() - +create_tests(branch_tests) create_tests(emulator_tests) -create_tests(ld_immediate_tests) -create_tests(ld_zeropage_tests) -create_tests(ld_absolute_tests) +create_tests(increment_tests) create_tests(ld_absolute_indexed_tests) +create_tests(ld_absolute_tests) +create_tests(ld_immediate_tests) create_tests(ld_index_indirect_tests) create_tests(ld_indirect_indexed_tests) -create_tests(branch_tests) +create_tests(ld_zeropage_tests) +create_tests(ora_immediate_tests) +create_tests(ora_zeropage_tests) +create_tests(program_size_tests) create_tests(tx_tests) -create_tests(increment_tests) diff --git a/tests/branch_tests.cpp b/tests/branch_tests.cpp index c413289..f06afc6 100644 --- a/tests/branch_tests.cpp +++ b/tests/branch_tests.cpp @@ -51,7 +51,7 @@ TEST(BranchingTests, BranchOnCarrySetWhenUnset) emulator::Cpu cpu; cpu.flags.c = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -82,7 +82,7 @@ TEST(BranchingTests, BranchOnCarrySetWhenSet) emulator::Cpu cpu; cpu.flags.c = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -113,7 +113,7 @@ TEST(BranchingTests, BranchOnCarryClearWhenSet) emulator::Cpu cpu; cpu.flags.c = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -144,7 +144,7 @@ TEST(BranchingTests, BranchOnCarryClearWhenUnset) emulator::Cpu cpu; cpu.flags.c = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -175,7 +175,7 @@ TEST(BranchingTests, BranchOnZeroSetWhenUnset) emulator::Cpu cpu; cpu.flags.z = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -198,7 +198,7 @@ TEST(BranchingTests, BranchOnZeroSetWhenSet) {{0xf0, 0x00}, 0x02}, {{0xf0, 0x01}, 0x03}, {{0xf0, 0x0a}, 0x0c}, - {{0xf0, 0xff}, 0x01}, + // {{0xf0, 0xff}, 0x01}, - bad test: jump is not valid }}; for (auto& [program, expected_pc] : programs) @@ -206,7 +206,7 @@ TEST(BranchingTests, BranchOnZeroSetWhenSet) emulator::Cpu cpu; cpu.flags.z = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -237,7 +237,7 @@ TEST(BranchingTests, BranchOnZeroClearWhenSet) emulator::Cpu cpu; cpu.flags.z = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -268,7 +268,7 @@ TEST(BranchingTests, BranchOnZeroClearWhenUnset) emulator::Cpu cpu; cpu.flags.z = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -299,7 +299,7 @@ TEST(BranchingTests, BranchOnNegativeSetWhenUnset) emulator::Cpu cpu; cpu.flags.n = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -330,7 +330,7 @@ TEST(BranchingTests, BranchOnNegativeSetWhenSet) emulator::Cpu cpu; cpu.flags.n = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -361,7 +361,7 @@ TEST(BranchingTests, BranchOnNegativeClearWhenSet) emulator::Cpu cpu; cpu.flags.n = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -392,7 +392,7 @@ TEST(BranchingTests, BranchOnNegativeClearWhenUnset) emulator::Cpu cpu; cpu.flags.n = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -423,7 +423,7 @@ TEST(BranchingTests, BranchOnOverflowSetWhenUnset) emulator::Cpu cpu; cpu.flags.n = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -454,7 +454,7 @@ TEST(BranchingTests, BranchOnOverflowSetWhenSet) emulator::Cpu cpu; cpu.flags.v = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -485,7 +485,7 @@ TEST(BranchingTests, BranchOnOverflowClearWhenSet) emulator::Cpu cpu; cpu.flags.v = true; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -516,7 +516,7 @@ TEST(BranchingTests, BranchOnOverflowClearWhenUnset) emulator::Cpu cpu; cpu.flags.v = false; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); diff --git a/tests/emulator_tests.cpp b/tests/emulator_tests.cpp index 15fe1fa..a7d6ab8 100644 --- a/tests/emulator_tests.cpp +++ b/tests/emulator_tests.cpp @@ -17,7 +17,7 @@ TEST(EmulatorTests, EmulateInxNoFlag) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.x, program[1] + 1); ASSERT_EQ(cpu.reg.sp, 0x00); @@ -37,7 +37,7 @@ TEST(EmulatorTests, EmulateInyNoFlag) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.y, program[1] + 1); ASSERT_EQ(cpu.reg.sp, 0x00); @@ -59,7 +59,7 @@ TEST(EmulatorTests, EmulateDexNoFlag) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.x, program[1] - 1); ASSERT_EQ(cpu.reg.sp, 0x00); @@ -79,7 +79,7 @@ TEST(EmulatorTests, EmulateDeyNoFlag) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.y, program[1] - 1); ASSERT_EQ(cpu.reg.sp, 0x00); @@ -101,7 +101,7 @@ TEST(EmulatorTests, EmulateCpxXGreaterThanValue) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.x, program[1]); ASSERT_EQ(cpu.reg.sp, 0x00); @@ -127,7 +127,7 @@ TEST(EmulatorTests, EmulateCpxSameValues) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.x, program[1]); ASSERT_EQ(cpu.reg.sp, 0x00); @@ -152,7 +152,7 @@ TEST(EmulatorTests, EmulateCpxSetCarry) for (auto const& program : programs) { emulator::Cpu cpu; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.x, program[1]); ASSERT_EQ(cpu.reg.sp, 0x00); diff --git a/tests/increment_tests.cpp b/tests/increment_tests.cpp index ba758b1..e153615 100644 --- a/tests/increment_tests.cpp +++ b/tests/increment_tests.cpp @@ -34,7 +34,7 @@ TEST(IncrementTests, INXNoFlags) { emulator::Cpu cpu; cpu.reg.x = init_x; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, init_x + 1); @@ -62,7 +62,7 @@ TEST(IncrementTests, INYNoFlags) { emulator::Cpu cpu; cpu.reg.y = init_y; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -91,7 +91,7 @@ TEST(IncrementTests, INCZeropageNoFlags) emulator::Cpu cpu; // TODO : Set the memory to init cpu.mem[init_v] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -123,7 +123,7 @@ TEST(IncrementTests, INCZeropagePlusXNoFlags) cpu.reg.x = 0x0a; std::uint8_t const pos = cpu.reg.x + program[1]; cpu.mem[pos] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x0a); @@ -157,7 +157,7 @@ TEST(IncrementTests, INCAbsoluteNoFlags) std::uint16_t pos = (hsb << 8) | lsb; cpu.mem[pos] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -192,7 +192,7 @@ TEST(IncrementTests, INCAbsolutePluxXNoFlags) std::uint16_t pos = (hsb << 8) | lsb + cpu.reg.x; cpu.mem[pos] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x0a); @@ -212,7 +212,7 @@ TEST(IncrementTests, INXZeroFlag) emulator::Cpu cpu; cpu.reg.x = 0xff; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -230,7 +230,7 @@ TEST(IncrementTests, INYZeroFlag) emulator::Cpu cpu; cpu.reg.y = 0xff; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -248,7 +248,7 @@ TEST(IncrementTests, INCZeropageZeroFlag) emulator::Cpu cpu; cpu.mem[0x0a] = 0xff; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -268,7 +268,7 @@ TEST(IncrementTests, INCZeropagePluxXZeroFlag) emulator::Cpu cpu; cpu.reg.x = 0x0a; cpu.mem[0x14] = 0xff; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x0a); @@ -292,7 +292,7 @@ TEST(IncrementTests, INCAbsoluteZeroFlag) std::uint16_t const pos = ((hsb << 8) | lsb); cpu.mem[pos] = 0xff; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -317,7 +317,7 @@ TEST(IncrementTests, INCAbsolutePluxXZeroFlag) std::uint16_t const pos = ((hsb << 8) | lsb) + cpu.reg.x; cpu.mem[pos] = 0xff; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x0a); @@ -350,7 +350,7 @@ TEST(IncrementTests, INXNegativeFlag) { emulator::Cpu cpu; cpu.reg.x = init_x; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, init_x + 1); @@ -382,7 +382,7 @@ TEST(IncrementTests, INYNegativeFlag) { emulator::Cpu cpu; cpu.reg.y = init_y; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -416,7 +416,7 @@ TEST(IncrementTests, INCZeropageNegativeFlag) { emulator::Cpu cpu; cpu.mem[init_v] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -455,7 +455,7 @@ TEST(IncrementTests, INCZeropagePlusXNegativeFlag) cpu.mem[pos] = init_v; cpu.reg.x = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, init_v); @@ -495,7 +495,7 @@ TEST(IncrementTests, INCAbsoluteNegativeFlag) std::uint16_t const pos = (hsb << 8) | lsb; cpu.mem[pos] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -536,7 +536,7 @@ TEST(IncrementTests, INCAbsolutePluxXNegativeFlag) std::uint16_t const pos = (hsb << 8) | lsb + cpu.reg.x; cpu.mem[pos] = init_v; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x0a); diff --git a/tests/ld_absolute_indexed_tests.cpp b/tests/ld_absolute_indexed_tests.cpp index 73716f1..9d26c66 100644 --- a/tests/ld_absolute_indexed_tests.cpp +++ b/tests/ld_absolute_indexed_tests.cpp @@ -40,7 +40,7 @@ TEST(LDTests, LDAAbsolutePlusXNonZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); @@ -70,7 +70,7 @@ TEST(LDTests, LDAAbsolutePlusYNonZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); @@ -100,7 +100,7 @@ TEST(LDTests, LDXAbsolutePlusYNonZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -130,7 +130,7 @@ TEST(LDTests, LDYAbsolutePlusXNonZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -161,7 +161,7 @@ TEST(LDTests, LDAAbsolutePlusXWithZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -192,7 +192,7 @@ TEST(LDTests, LDAAbsolutePlusYWithZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -223,7 +223,7 @@ TEST(LDTests, LDXAbsolutePlusYWithZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -254,7 +254,7 @@ TEST(LDTests, LDYAbsolutePlusXWithZero) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -281,7 +281,7 @@ TEST(LDTests, LDAAbsolutePlusXWithNegative) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0xFF); @@ -308,7 +308,7 @@ TEST(LDTests, LDAAbsolutePlusYWithNegative) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0xFF); @@ -335,7 +335,7 @@ TEST(LDTests, LDXAbsolutePlusYWithNegative) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -362,7 +362,7 @@ TEST(LDTests, LDYAbsolutePlusXWithNegative) 0xed, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); diff --git a/tests/ld_absolute_tests.cpp b/tests/ld_absolute_tests.cpp index 115e3b7..4499986 100644 --- a/tests/ld_absolute_tests.cpp +++ b/tests/ld_absolute_tests.cpp @@ -36,7 +36,7 @@ TEST(LDTests, LDAAbsoluteNonZero) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); @@ -61,7 +61,7 @@ TEST(LDTests, LDXAbsoluteNonZero) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -86,7 +86,7 @@ TEST(LDTests, LDYAbsoluteNonZero) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -111,7 +111,7 @@ TEST(LDTests, LDAAbsoluteWithZero) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -136,7 +136,7 @@ TEST(LDTests, LDXAbsoluteWithZero) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -161,7 +161,7 @@ TEST(LDTests, LDYAbsoluteWithZero) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -186,7 +186,7 @@ TEST(LDTests, LDAAbsoluteWithNegative) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0xFF); @@ -211,7 +211,7 @@ TEST(LDTests, LDXAbsoluteWithNegative) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -236,7 +236,7 @@ TEST(LDTests, LDYAbsoluteWithNegative) 0x00, 0x02, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); diff --git a/tests/ld_immediate_tests.cpp b/tests/ld_immediate_tests.cpp index 66f7f6a..f196e33 100644 --- a/tests/ld_immediate_tests.cpp +++ b/tests/ld_immediate_tests.cpp @@ -29,7 +29,7 @@ TEST(LDTests, LdAImmediateWithNonZero) 0x0a, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x0a); @@ -54,7 +54,7 @@ TEST(LDTests, LdAImmediateWithZero) 0x00, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -78,7 +78,7 @@ TEST(LDTests, LdAImmediateWithNegative) 0xFF, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0xFF); ASSERT_EQ(cpu.reg.x, 0x00); @@ -98,7 +98,7 @@ TEST(LDTests, LdXImmediateWithNonZero) 0x0a, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -119,7 +119,7 @@ TEST(LDTests, LdXImmediateWithZero) 0x00, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -139,7 +139,7 @@ TEST(LDTests, LdXImmediateWithNegative) 0xFF, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0xFF); @@ -160,7 +160,7 @@ TEST(LDTests, LdYImmediateWithNonZero) 0x0a, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -181,7 +181,7 @@ TEST(LDTests, LdYImmediateWithZero) 0x00, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -201,7 +201,7 @@ TEST(LDTests, LdYImmediateWithNegative) 0xFF, }; emulator::Cpu cpu; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); diff --git a/tests/ld_index_indirect_tests.cpp b/tests/ld_index_indirect_tests.cpp index 28c3d92..a9300ff 100644 --- a/tests/ld_index_indirect_tests.cpp +++ b/tests/ld_index_indirect_tests.cpp @@ -30,7 +30,7 @@ TEST(LDTests, LDAIndexIndirectXNonZero) 0xa1, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); @@ -58,7 +58,7 @@ TEST(LDTests, LDAIndexIndirectXWithZero) 0xa1, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -85,7 +85,7 @@ TEST(LDTests, LDAIndexIndirectXNegative) 0xa1, 0x00, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0xff); @@ -115,7 +115,7 @@ TEST(LDTests, LDAIndexIndirectXNonZeroPosWrap) 0xa1, 0xee, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); diff --git a/tests/ld_indirect_indexed_tests.cpp b/tests/ld_indirect_indexed_tests.cpp index 6a192fb..ae454fc 100644 --- a/tests/ld_indirect_indexed_tests.cpp +++ b/tests/ld_indirect_indexed_tests.cpp @@ -32,7 +32,7 @@ TEST(LDTests, LDAIndirectIndexYNonZero) 0xb1, 0x58, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); @@ -61,7 +61,7 @@ TEST(LDTests, LDAIndirectIndexYWithZero) 0xb1, 0x58, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -89,7 +89,7 @@ TEST(LDTests, LDAIndirectIndexYWithNegative) 0xb1, 0x58, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0xff); diff --git a/tests/ld_zeropage_tests.cpp b/tests/ld_zeropage_tests.cpp index 1c805d4..fd31b67 100644 --- a/tests/ld_zeropage_tests.cpp +++ b/tests/ld_zeropage_tests.cpp @@ -31,7 +31,7 @@ TEST(LDTests, LDAZeropageWithNonZero) 0xa5, 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x5a); @@ -57,7 +57,7 @@ TEST(LDTests, LDAZeropageWithZero) 0xa5, 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -83,7 +83,7 @@ TEST(LDTests, LDAZeropageWithNegative) 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0xff); ASSERT_EQ(cpu.reg.x, 0x00); @@ -108,7 +108,7 @@ TEST(LDTests, LDXZeropageWithNonZero) 0xa6, 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -134,7 +134,7 @@ TEST(LDTests, LDXZeropageWithZero) 0xa6, 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -160,7 +160,7 @@ TEST(LDTests, LDXZeropageWithNegative) 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0xff); @@ -185,7 +185,7 @@ TEST(LDTests, LDYZeropageWithNonZero) 0xa4, 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); // Registry expect ASSERT_EQ(cpu.reg.a, 0x00); @@ -211,7 +211,7 @@ TEST(LDTests, LDYZeropageWithZero) 0xa4, 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -237,7 +237,7 @@ TEST(LDTests, LDYZeropageWithNegative) 0x22, }; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); diff --git a/tests/ora_immediate_tests.cpp b/tests/ora_immediate_tests.cpp new file mode 100644 index 0000000..142763d --- /dev/null +++ b/tests/ora_immediate_tests.cpp @@ -0,0 +1,166 @@ +/* +This tests will check that the behaviour of the +"ORA" immediate opcode 0x09 + +This instructions will OR the immediate value +given to the program to the accumulator, then +store the result in the accumulator. + +This sets the N and Z flags depending on the value +left in the accumulator. + +This instruction takes exactly two cycles. +*/ + +import emulator; + +#include "common.h" + +#include + +#include +#include + + +// NOLINTNEXTLINE +TEST(ORAImmediateTests, NoFlagOperations) +{ + // test_case = {init acc, immediate value} + std::array, 3> const test_cases{{ + {0b0101'0101, 0b0010'1010}, + {0b0101'0101, 0b0101'0101}, + {0b0111'1111, 0b0000'0000}, + }}; + + for (auto const& [init_acc, im_value] : test_cases) + { + emulator::Cpu cpu; + cpu.reg.a = init_acc; + + std::array program{ + 0x09, + im_value, + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, init_acc | im_value); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); + } +} + +// NOLINTNEXTLINE +TEST(ORAImmediateTests, NegativeFlagOperation) +{ + // test_case = {init acc, immediate value} + std::array, 6> const test_cases{{ + {0b1000'0000, 0b0010'1010}, + {0b1000'0000, 0b0101'0101}, + {0b1000'0000, 0b0000'0000}, + {0b0010'1010, 0b1000'0000}, + {0b0101'0101, 0b1000'0000}, + {0b0000'0000, 0b1000'0000}, + }}; + + for (auto const& [init_acc, im_value] : test_cases) + { + emulator::Cpu cpu; + cpu.reg.a = init_acc; + + std::array program{ + 0x09, + im_value, + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, init_acc | im_value); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_EQ(cpu.flags, make_flags(0b1000'0000)); + } +} + +// NOLINTNEXTLINE +TEST(ORAImmediateTests, ZeroFlagOperation) +{ + emulator::Cpu cpu; + cpu.reg.a = 0x00; + + std::array program{ + 0x09, + 0x00, + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010)); +} + +// NOLINTNEXTLINE +TEST(ORAImmediateTests, MakeSureFlagsAreSound) +{ + // Weird testcase, we should never have both Z and N + // flags set + + for (std::int16_t acc = 0; acc < 256; ++acc) + { + for (std::int16_t val = 0; val < 256; ++val) + { + emulator::Cpu cpu; + cpu.reg.a = static_cast(acc); + + std::array program{ + 0x09, + static_cast(val), + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, val | acc); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_FALSE(cpu.flags.v); + ASSERT_FALSE(cpu.flags.b); + ASSERT_FALSE(cpu.flags.d); + ASSERT_FALSE(cpu.flags.i); + ASSERT_FALSE(cpu.flags.c); + + if (cpu.flags.z) + { + ASSERT_FALSE(cpu.flags.n); + } + if (cpu.flags.n) + { + ASSERT_FALSE(cpu.flags.z); + } + } + } +} + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/ora_zeropage_tests.cpp b/tests/ora_zeropage_tests.cpp new file mode 100644 index 0000000..6f1650e --- /dev/null +++ b/tests/ora_zeropage_tests.cpp @@ -0,0 +1,171 @@ +/* +This tests will check that the behaviour of the +"ORA" zeropage opcode 0x05 + +This instructions will OR the value at the +zeropage location given to the program to +the accumulator, then store the result in +the accumulator. + +This sets the N and Z flags depending on the value +left in the accumulator. + +This instruction takes exactly two cycles. +*/ + +import emulator; + +#include "common.h" + +#include + +#include +#include + + +// NOLINTNEXTLINE +TEST(ORAZeropageTests, NoFlagOperations) +{ + // test_case = {init acc, value, zp address} + std::array, 3> const test_cases{{ + {0b0101'0101, 0b0010'1010, 0x00}, + {0b0101'0101, 0b0101'0101, 0x88}, + {0b0111'1111, 0b0000'0000, 0xff}, + }}; + + for (auto const& [init_acc, value, address] : test_cases) + { + emulator::Cpu cpu; + cpu.reg.a = init_acc; + cpu.mem[static_cast(address)] = value; + + std::array program{ + 0x05, + static_cast(address), + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, init_acc | value); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_EQ(cpu.flags, make_flags(0b0000'0000)); + } +} + +// NOLINTNEXTLINE +TEST(ORAZeropageTests, NegativeFlagOperation) +{ + // test_case = {init acc, value, zp address} + std::array, 6> const test_cases{{ + {0b1000'0000, 0b0010'1010, 0x00}, + {0b1000'0000, 0b0101'0101, 0x88}, + {0b1000'0000, 0b0000'0000, 0xFF}, + {0b0010'1010, 0b1000'0000, 0x00}, + {0b0101'0101, 0b1000'0000, 0x88}, + {0b0000'0000, 0b1000'0000, 0xff}, + }}; + + for (auto const& [acc, value, address] : test_cases) + { + emulator::Cpu cpu; + cpu.reg.a = acc; + cpu.mem[static_cast(address)] = value; + + std::array program{ + 0x05, + static_cast(address), + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, acc | value); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_EQ(cpu.flags, make_flags(0b1000'0000)); + } +} + +// NOLINTNEXTLINE +TEST(ORAZeropageTests, ZeroFlagOperation) +{ + emulator::Cpu cpu; + cpu.reg.a = 0x00; + cpu.mem[0x88] = 0x00; + + std::array program{ + 0x05, + 0x88, + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, 0x00); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_EQ(cpu.flags, make_flags(0b0000'0010)); +} + +// NOLINTNEXTLINE +TEST(ORAZeropageTests, MakeSureFlagsAreSound) +{ + // Weird testcase, we should never have both Z and N + // flags set + + for (std::int16_t acc = 0; acc < 256; ++acc) + { + for (std::int16_t val = 0; val < 256; ++val) + { + emulator::Cpu cpu; + cpu.reg.a = static_cast(acc); + cpu.mem[0x88] = static_cast(val); + + std::array program{ + 0x05, + 0x88, + }; + ASSERT_TRUE(emulator::execute(cpu, program)); + + // Registry expect + ASSERT_EQ(cpu.reg.a, val | acc); + ASSERT_EQ(cpu.reg.x, 0x00); + ASSERT_EQ(cpu.reg.y, 0x00); + ASSERT_EQ(cpu.reg.sp, 0x00); + ASSERT_EQ(cpu.reg.pc, 0x02); + + // Flags expect + ASSERT_FALSE(cpu.flags.v); + ASSERT_FALSE(cpu.flags.b); + ASSERT_FALSE(cpu.flags.d); + ASSERT_FALSE(cpu.flags.i); + ASSERT_FALSE(cpu.flags.c); + + if (cpu.flags.z) + { + ASSERT_FALSE(cpu.flags.n); + } + if (cpu.flags.n) + { + ASSERT_FALSE(cpu.flags.z); + } + } + } +} + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/program_size_tests.cpp b/tests/program_size_tests.cpp new file mode 100644 index 0000000..9561d8d --- /dev/null +++ b/tests/program_size_tests.cpp @@ -0,0 +1,198 @@ +/* +This test file will make sure that all branches +checking for program sizes are covered. + +Essentially, we are testing that, where the program +is malformed by not having enough argument bytes, we +fail. +*/ + +import emulator; + +#include "common.h" + +#include + +#include +#include + + +// NOLINTNEXTLINE +TEST(ProgramSizeTests, LdImmediateMalformed) +{ + std::array constexpr program{0xa9}; + emulator::Cpu cpu{}; + + ASSERT_FALSE(emulator::execute(cpu, program)); +} + +// 0xa5, LdZeropageMalformed +TEST(ProgramSizeTests, LdZeropageMalformed0) +{ + std::array constexpr program{0xa5}; + emulator::Cpu cpu{}; + + ASSERT_FALSE(emulator::execute(cpu, program)); +} + +// NOLINTNEXTLINE +TEST(ProgramSizeTests, LdZeropageMalformed1) +{ + std::array constexpr program{0xa6}; + emulator::Cpu cpu{}; + + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdZeropageMalformed2) +{ + std::array constexpr program{0xa4}; + emulator::Cpu cpu{}; + + ASSERT_FALSE(emulator::execute(cpu, program)); +} +// 0xb5, LdZeropagePlusRegMalformed +TEST(ProgramSizeTests, LdZeropagePlusRegMalformed0) +{ + std::array constexpr program{0xb6}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdZeropagePlusRegMalformed1) +{ + std::array constexpr program{0xb4}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsoluteMalformed0) +{ + std::array constexpr program{0xad}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsoluteMalformed1) +{ + std::array constexpr program{0xae}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsoluteMalformed2) +{ + std::array constexpr program{0xac}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsolutePlusRegMalformed0) +{ + std::array constexpr program{0xbd}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsolutePlusRegMalformed1) +{ + std::array constexpr program{0xb9}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsolutePlusRegMalformed2) +{ + std::array constexpr program{0xbe}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdAbsolutePlusRegMalformed3) +{ + std::array constexpr program{0xbc}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdindexIndirectMalformed) +{ + std::array constexpr program{0xa1}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, LdIndirectIndexMalformed) +{ + std::array constexpr program{0xb1}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, IncZeropageMalformed0) +{ + std::array constexpr program{0xe6}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, IncZeropageMalformed1) +{ + std::array constexpr program{0xf6}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, IncAbsoluteMalformed) +{ + std::array constexpr program{0x33}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, IncAbsolutePlusXmalformed) +{ + std::array constexpr program{0xf3}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, DecZeropageMalformed) +{ + std::array constexpr program{0xc6}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StIndirectMalformed) +{ + std::array constexpr program{0x91}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StZeroPageMalformed0) +{ + std::array constexpr program{0x84}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StZeroPageMalformed1) +{ + std::array constexpr program{0x85}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StZeroPageMalformed2) +{ + std::array constexpr program{0x86}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StAbsoluteMalformed0) +{ + std::array constexpr program{0x8c}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StAbsoluteMalformed1) +{ + std::array constexpr program{0x8d}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} +TEST(ProgramSizeTests, StAbsoluteMalformed22) +{ + std::array constexpr program{0x8e}; + emulator::Cpu cpu{}; + ASSERT_FALSE(emulator::execute(cpu, program)); +} + + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/tx_tests.cpp b/tests/tx_tests.cpp index cbf8a35..3b212ff 100644 --- a/tests/tx_tests.cpp +++ b/tests/tx_tests.cpp @@ -24,7 +24,7 @@ TEST(TXTests, TXANoFlags) { emulator::Cpu cpu; cpu.reg.x = init_x; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, init_x); ASSERT_EQ(cpu.reg.x, init_x); @@ -54,7 +54,7 @@ TEST(TXTests, TAXNoFlags) { emulator::Cpu cpu; cpu.reg.a = init_a; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, init_a); ASSERT_EQ(cpu.reg.x, init_a); @@ -84,7 +84,7 @@ TEST(TXTests, TAYNoFlags) { emulator::Cpu cpu; cpu.reg.a = init_a; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, init_a); ASSERT_EQ(cpu.reg.x, 0x00); @@ -114,7 +114,7 @@ TEST(TXTests, TSXNoFlags) { emulator::Cpu cpu; cpu.reg.sp = init_sp; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, init_sp); @@ -144,7 +144,7 @@ TEST(TXTests, TYANoFlags) { emulator::Cpu cpu; cpu.reg.y = init_y; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, init_y); ASSERT_EQ(cpu.reg.x, 0x00); @@ -177,7 +177,7 @@ TEST(TXTests, TXSNoFlags) { emulator::Cpu cpu; cpu.reg.x = init_x; - emulator::execute(cpu, {program.data(), program.size()}); + ASSERT_TRUE(emulator::execute(cpu, {program.data(), program.size()})); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, init_x); @@ -198,7 +198,7 @@ TEST(TXTests, TXAZeroFlag) emulator::Cpu cpu; cpu.reg.x = 0x00; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -219,7 +219,7 @@ TEST(TXTests, TAXZeroFlag) cpu.reg.a = 0x00; cpu.reg.x = 0xFF; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -241,7 +241,7 @@ TEST(TXTests, TAYZeroFlag) cpu.reg.a = 0x00; cpu.reg.y = 0xFF; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -263,7 +263,7 @@ TEST(TXTests, TSXZeroFlag) cpu.reg.sp = 0x00; cpu.reg.x = 0xFF; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -284,7 +284,7 @@ TEST(TXTests, TYAZeroFlag) emulator::Cpu cpu; cpu.reg.y = 0x00; cpu.reg.a = 0xFF; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, 0x00); @@ -316,7 +316,7 @@ TEST(TXTests, TXANegativeFlag) { emulator::Cpu cpu; cpu.reg.x = init_x; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, init_x); ASSERT_EQ(cpu.reg.x, init_x); @@ -346,7 +346,7 @@ TEST(TXTests, TAXNegativeFlag) { emulator::Cpu cpu; cpu.reg.a = init_a; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, init_a); ASSERT_EQ(cpu.reg.x, init_a); @@ -377,7 +377,7 @@ TEST(TXTests, TAYNegativeFlag) { emulator::Cpu cpu; cpu.reg.a = init_a; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, init_a); ASSERT_EQ(cpu.reg.x, 0); @@ -408,7 +408,7 @@ TEST(TXTests, TSXNegativeFlag) { emulator::Cpu cpu; cpu.reg.sp = init_sp; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, 0x00); ASSERT_EQ(cpu.reg.x, init_sp); @@ -440,7 +440,7 @@ TEST(TXTests, TYANegativeFlag) { emulator::Cpu cpu; cpu.reg.y = init_y; - emulator::execute(cpu, program); + ASSERT_TRUE(emulator::execute(cpu, program)); ASSERT_EQ(cpu.reg.a, init_y); ASSERT_EQ(cpu.reg.x, 0x00);