diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..947d1d9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.13) +project(6502_emulator) + +set(CMAKE_CXX_STANDARD 14) + +configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() +execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src + ${CMAKE_CURRENT_BINARY_DIR}/googletest-build + EXCLUDE_FROM_ALL) + +file(GLOB_RECURSE test_src "test/**.h" "test/**.cpp" "src/**.h" "src/**.cpp") +list(FILTER test_src EXCLUDE REGEX ".*/6502-emulator/src/main.cpp$") +add_executable(6502_emulator_test ${test_src} test/test-utils.h) +target_link_libraries(6502_emulator_test gtest_main) +add_test(NAME run_tests COMMAND 6502_emulator_test) + +file(GLOB_RECURSE emulator_src "src/**.h" "src/**.cpp") +add_executable(6502_emulator ${emulator_src} src/utils.cpp) \ No newline at end of file diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in new file mode 100644 index 0000000..98ce4c3 --- /dev/null +++ b/CMakeLists.txt.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.13) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) \ No newline at end of file diff --git a/src/machine/cpu.cpp b/src/machine/cpu.cpp new file mode 100644 index 0000000..80916bc --- /dev/null +++ b/src/machine/cpu.cpp @@ -0,0 +1,49 @@ +#include "cpu.h" +#include + +namespace emu_6502 { + Cpu::Cpu() { + a = make_unique>("A"); + x = make_unique>("X"); + y = make_unique>("Y"); + sp = make_unique>("SP"); + pc = make_unique(); + ps = make_unique(); + } + + Register& Cpu::get_a() { + return *a; + } + + Register& Cpu::get_x() { + return *x; + } + + Register& Cpu::get_y() { + return *y; + } + + Register& Cpu::get_sp() { + return *sp; + } + + ProgramCounter& Cpu::get_pc() { + return *pc; + } + + StatusRegister& Cpu::get_ps() { + return *ps; + } + + void Cpu::dump() { + cout << "--------------" << endl; + cout << "A=0x" << hex << (int) a->get_value() << + " X=0x" << hex << (int) x->get_value() << + " Y=0x" << hex << (int) y->get_value() << endl; + + cout << "SP=0x" << hex << (int) sp->get_value() << + " PC=0x" << hex << (int) pc->get_value() << endl; + + cout << "NV-BDIZC" << endl << ps->get_value() << endl; + } +} \ No newline at end of file diff --git a/src/machine/cpu.h b/src/machine/cpu.h new file mode 100644 index 0000000..9e4c6e2 --- /dev/null +++ b/src/machine/cpu.h @@ -0,0 +1,38 @@ +#ifndef INC_6502_EMULATOR_CPU_H +#define INC_6502_EMULATOR_CPU_H + +#include "register.h" +#include "status-register.h" +#include "program-counter.h" + +#include + +using namespace std; + +namespace emu_6502 { + class Cpu { + private: + unique_ptr> a; + unique_ptr> x; + unique_ptr> y; + unique_ptr> sp; + unique_ptr pc; + unique_ptr ps; + + public: + Cpu(); + Cpu(const Cpu&) = delete; + Cpu& operator=(const Cpu&) = delete; + + Register& get_a(); + Register& get_x(); + Register& get_y(); + Register& get_sp(); + ProgramCounter& get_pc(); + StatusRegister& get_ps(); + + void dump(); + }; +} + +#endif //INC_6502_EMULATOR_CPU_H diff --git a/src/machine/machine.cpp b/src/machine/machine.cpp new file mode 100644 index 0000000..f6d3bd7 --- /dev/null +++ b/src/machine/machine.cpp @@ -0,0 +1,106 @@ +#include "machine.h" +#include "../opcode/opcode-handler-directory.h" + +#include + +namespace emu_6502 { + class MachineImpl { + private: + uint16_t code_loaded_at; + uint16_t code_size; + + unique_ptr cpu; + unique_ptr memory; + unique_ptr stack; + unique_ptr opcode_handler_dir; + public: + MachineImpl() { + cpu = make_unique(); + memory = make_unique(); + stack = make_unique(*memory, cpu->get_sp()); + opcode_handler_dir = make_unique(); + } + + MachineImpl(const MachineImpl&) = delete; + MachineImpl& operator=(const MachineImpl&) = delete; + + Cpu& get_cpu() { + return *cpu; + } + + Memory& get_memory() { + return *memory; + } + + Stack& get_stack() { + return *stack; + } + + void load(const vector& program, uint16_t load_at) { + code_loaded_at = load_at; + code_size = program.size(); + uint32_t code_end = load_at + code_size; + if (code_end > 0xFE00) // need space for graphics, interupts, etc. + throw runtime_error("Program does not fit into memory"); + + for (auto i = 0; i < program.size(); i++) + memory->set_at(load_at + i, program.at(i)); + + cpu->get_pc().set_value(load_at); + } + + bool is_eop() { + return cpu->get_pc().get_value() >= code_loaded_at + code_size; + } + + uint8_t read_program_byte() { + if (is_eop()) + throw runtime_error("Passed end of program"); + + auto byte = memory->get_at(cpu->get_pc().get_value()); + cpu->get_pc().inc(); + + return byte; + } + + void execute(Machine& machine) { + while (!is_eop()) { + auto byte = read_program_byte(); + opcode_handler_dir->execute(byte, machine); + } + } + }; + + Machine::Machine() : pimpl(make_unique()) { + } + + Machine::~Machine() = default; + + Cpu& Machine::get_cpu() { + return pimpl->get_cpu(); + } + + Memory& Machine::get_memory() { + return pimpl->get_memory(); + } + + Stack& Machine::get_stack() { + return pimpl->get_stack(); + } + + void Machine::load(const vector& program, uint16_t load_at) { + pimpl->load(program, load_at); + } + + bool Machine::is_eop() { + return pimpl->is_eop(); + } + + uint8_t Machine::read_program_byte() { + return pimpl->read_program_byte(); + } + + void Machine::execute() { + pimpl->execute(*this); + } +} \ No newline at end of file diff --git a/src/machine/machine.h b/src/machine/machine.h new file mode 100644 index 0000000..5fa2a9a --- /dev/null +++ b/src/machine/machine.h @@ -0,0 +1,36 @@ +#ifndef INC_6502_EMULATOR_MACHINE_H +#define INC_6502_EMULATOR_MACHINE_H + +#include "cpu.h" +#include "memory.h" +#include "stack.h" + +#include +#include + +using namespace std; + +namespace emu_6502 { + class MachineImpl; + + class Machine { + private: + unique_ptr pimpl; + public: + Machine(); + ~Machine(); + Machine(const Machine&) = delete; + Machine& operator=(const Machine&) = delete; + + Cpu& get_cpu(); + Memory& get_memory(); + Stack& get_stack(); + + bool is_eop(); + uint8_t read_program_byte(); + void load(const vector& program, uint16_t load_at); + void execute(); + }; +} + +#endif //INC_6502_EMULATOR_MACHINE_H diff --git a/src/machine/memory.cpp b/src/machine/memory.cpp new file mode 100644 index 0000000..1bd1776 --- /dev/null +++ b/src/machine/memory.cpp @@ -0,0 +1,27 @@ +#include "memory.h" + +namespace emu_6502 { + inline uint16_t Memory::get_page_offset(uint8_t page) { + return page * 0xFF; + } + + Memory::Memory() { + memory = vector(0xFFFF); + } + + uint8_t Memory::get_at(uint16_t address) { + return memory.at(address); + } + + void Memory::set_at(uint16_t address, uint8_t value) { + memory.at(address) = value; + } + + uint8_t Memory::get_from_page(uint8_t page, uint8_t offset) { + return get_at(get_page_offset(page) + offset); + } + + void Memory::set_on_page(uint8_t page, uint8_t offset, uint8_t value) { + set_at(get_page_offset(page) + offset, value); + } +} \ No newline at end of file diff --git a/src/machine/memory.h b/src/machine/memory.h new file mode 100644 index 0000000..81bd9e7 --- /dev/null +++ b/src/machine/memory.h @@ -0,0 +1,29 @@ +#ifndef INC_6502_EMULATOR_MEMORY_H +#define INC_6502_EMULATOR_MEMORY_H + +#include +#include + +using namespace std; + +namespace emu_6502 { + class Memory { + private: + vector memory; + + uint16_t get_page_offset(uint8_t page); + + public: + Memory(); + Memory(const Memory&) = delete; + Memory& operator=(const Memory&) = delete; + + uint8_t get_at(uint16_t address); + void set_at(uint16_t address, uint8_t value); + + uint8_t get_from_page(uint8_t page, uint8_t offset); + void set_on_page(uint8_t page, uint8_t offset, uint8_t value); + }; +} + +#endif //INC_6502_EMULATOR_MEMORY_H diff --git a/src/machine/program-counter.cpp b/src/machine/program-counter.cpp new file mode 100644 index 0000000..8082d95 --- /dev/null +++ b/src/machine/program-counter.cpp @@ -0,0 +1,7 @@ +#include "program-counter.h" + +namespace emu_6502 { + void ProgramCounter::inc() { + value += 1; + } +} \ No newline at end of file diff --git a/src/machine/program-counter.h b/src/machine/program-counter.h new file mode 100644 index 0000000..595dcb5 --- /dev/null +++ b/src/machine/program-counter.h @@ -0,0 +1,18 @@ +#ifndef INC_6502_EMULATOR_PROGRAM_COUNTER_H +#define INC_6502_EMULATOR_PROGRAM_COUNTER_H + +#include "register.h" + +namespace emu_6502 { + class ProgramCounter : public Register { + public: + ProgramCounter() : Register("PC") {} + + ProgramCounter(const ProgramCounter&) = delete; + ProgramCounter& operator=(const ProgramCounter&) = delete; + + void inc(); + }; +} + +#endif //INC_6502_EMULATOR_PROGRAM_COUNTER_H diff --git a/src/machine/register.cpp b/src/machine/register.cpp new file mode 100644 index 0000000..5d3ed9c --- /dev/null +++ b/src/machine/register.cpp @@ -0,0 +1,33 @@ +#include "register.h" + +namespace emu_6502 { + template + Register::Register(string name) { + this->name = name; + this->value = 0; + } + + template + void Register::set_value(T value) { + this->value = value; + } + + template + const T Register::get_value() { + return value; + } + + template + const string& Register::get_name() { + return name; + } +} + +template +class emu_6502::Register; + +template +class emu_6502::Register; + +template +class emu_6502::Register>; \ No newline at end of file diff --git a/src/machine/register.h b/src/machine/register.h new file mode 100644 index 0000000..4861b5c --- /dev/null +++ b/src/machine/register.h @@ -0,0 +1,30 @@ +#ifndef INC_6502_EMULATOR_REGISTER_H +#define INC_6502_EMULATOR_REGISTER_H + +#include +#include + +using namespace std; + +namespace emu_6502 { + template + class Register { + private: + string name; + + protected: + T value; + + public: + Register(string name); + Register(const Register&) = delete; + Register& operator=(const Register&) = delete; + + const string& get_name(); + + const T get_value(); + void set_value(T value); + }; +} + +#endif //INC_6502_EMULATOR_REGISTER_H diff --git a/src/machine/stack.cpp b/src/machine/stack.cpp new file mode 100644 index 0000000..aae9138 --- /dev/null +++ b/src/machine/stack.cpp @@ -0,0 +1,22 @@ +#include "stack.h" + +namespace emu_6502 { + Stack::Stack(Memory& memory, Register& sp) : memory(memory), sp(sp) { + sp.set_value(0xFF); + } + + void Stack::push(uint8_t value) { + auto sp_value = sp.get_value(); + memory.set_on_page(Stack::PAGE, sp_value, value); + + sp.set_value(sp_value + 1); + } + + uint8_t Stack::pop() { + // a "feature" of the 6502 is that there is no check when popping and stack will wrap around + auto sp_value = sp.get_value() - 1; + sp.set_value(sp_value); + + return memory.get_from_page(Stack::PAGE, sp_value); + } +} \ No newline at end of file diff --git a/src/machine/stack.h b/src/machine/stack.h new file mode 100644 index 0000000..299dc48 --- /dev/null +++ b/src/machine/stack.h @@ -0,0 +1,30 @@ +#ifndef INC_6502_EMULATOR_STACK_H +#define INC_6502_EMULATOR_STACK_H + +#include +#include "memory.h" +#include "register.h" + +using namespace std; + +namespace emu_6502 { + class Stack { + private: + Memory& memory; + Register& sp; + + public: + const uint8_t PAGE = 1; + + Stack(Memory& memory, Register& sp); + + Stack(const Stack&) = delete; + Stack& operator=(const Stack&) = delete; + + void push(uint8_t value); + uint8_t pop(); + }; +} + + +#endif //INC_6502_EMULATOR_STACK_H diff --git a/src/machine/status-register.cpp b/src/machine/status-register.cpp new file mode 100644 index 0000000..fbd7fd8 --- /dev/null +++ b/src/machine/status-register.cpp @@ -0,0 +1,59 @@ +#include "status-register.h" + +namespace emu_6502 { + bool StatusRegister::is_carry_set() const { + return this->value[Flag::CARRY]; + } + + bool StatusRegister::is_zero_set() const { + return this->value[Flag::ZERO]; + } + + bool StatusRegister::is_interupt_disable_set() const { + return this->value[Flag::INTERUPT_DISABLE]; + } + + bool StatusRegister::is_decimal_set() const { + return this->value[Flag::DECIMAL]; + } + + bool StatusRegister::is_break_set() const { + return this->value[Flag::BREAK]; + } + + bool StatusRegister::is_overflow_set() const { + return this->value[Flag::OVERFLOW]; + } + + bool StatusRegister::is_negative_set() const { + return this->value[Flag::NEGATIVE]; + } + + void StatusRegister::set_carry(bool state) { + this->value[Flag::CARRY] = state; + } + + void StatusRegister::set_zero(bool state) { + this->value[Flag::ZERO] = state; + } + + void StatusRegister::set_interupt_disable(bool state) { + this->value[Flag::INTERUPT_DISABLE] = state; + } + + void StatusRegister::set_decimal(bool state) { + this->value[Flag::DECIMAL] = state; + } + + void StatusRegister::set_break(bool state) { + this->value[Flag::BREAK] = state; + } + + void StatusRegister::set_overflow(bool state) { + this->value[Flag::OVERFLOW] = state; + } + + void StatusRegister::set_negative(bool state) { + this->value[Flag::NEGATIVE] = state; + } +} \ No newline at end of file diff --git a/src/machine/status-register.h b/src/machine/status-register.h new file mode 100644 index 0000000..9514a67 --- /dev/null +++ b/src/machine/status-register.h @@ -0,0 +1,47 @@ +#ifndef INC_6502_EMULATOR_STATUS_REGISTER_H +#define INC_6502_EMULATOR_STATUS_REGISTER_H + +#include +#include +#include "register.h" + +using namespace std; + +namespace emu_6502 { + class StatusRegister : public Register> { + private: + enum Flag { + CARRY = 0, + ZERO = 1, + INTERUPT_DISABLE = 2, + DECIMAL = 3, + BREAK = 4, + OVERFLOW = 6, + NEGATIVE = 7 + }; + + public: + StatusRegister() : Register>("PS") {} + + StatusRegister(const StatusRegister&) = delete; + StatusRegister& operator=(const StatusRegister&) = delete; + + bool is_carry_set() const; + bool is_zero_set() const; + bool is_interupt_disable_set() const; + bool is_decimal_set() const; + bool is_break_set() const; + bool is_overflow_set() const; + bool is_negative_set() const; + + void set_carry(bool state); + void set_zero(bool state); + void set_interupt_disable(bool state); + void set_decimal(bool state); + void set_break(bool state); + void set_overflow(bool state); + void set_negative(bool state); + }; +} + +#endif //INC_6502_EMULATOR_STATUS_REGISTER_H diff --git a/src/machine/terminal.cpp b/src/machine/terminal.cpp new file mode 100644 index 0000000..dcf6392 --- /dev/null +++ b/src/machine/terminal.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "terminal.h" + +namespace emu_6502 { + Terminal::Terminal(Memory& memory) : memory(memory) { +// for (auto i = 0; i < 100; i++) +// memory->set_at(Terminal::LOW_ADDR + i, 0x20 + i); +// +// if ((mainwin = initscr()) == NULL) { +// fprintf(stderr, "Error initialising ncurses.\n"); +// exit(EXIT_FAILURE); +// } +// +// start_color(); + } + + Terminal::~Terminal() { +// delwin(mainwin); +// endwin(); + + cout << "shutting down" << endl; + } + + void Terminal::refresh() { +// for (auto n = 0; n <= 100; n++) { +// auto line = (n / TERM_COLS); +// auto col = n % TERM_COLS; +// +// mvaddch(line, col, memory->get_at(Terminal::LOW_ADDR + n)); +// n++; +// } +// +// wrefresh(mainwin); + } +} \ No newline at end of file diff --git a/src/machine/terminal.h b/src/machine/terminal.h new file mode 100644 index 0000000..a44a3de --- /dev/null +++ b/src/machine/terminal.h @@ -0,0 +1,33 @@ +#ifndef INC_6502_EMULATOR_TERMINAL_H +#define INC_6502_EMULATOR_TERMINAL_H + +#include +#include +#include "memory.h" + +using namespace std; + +namespace emu_6502 { + class Terminal { + private: + const uint16_t LOW_ADDR = 0xFB00; + const uint16_t HIGH_ADDR = 0xFF00; + const uint8_t TERM_LINES = 25; + const uint8_t TERM_COLS = 40; + + //WINDOW * mainwin; + + Memory& memory; + + public: + Terminal(Memory& memory); + Terminal(const Terminal&) = delete; + Terminal& operator=(const Terminal&) = delete; + ~Terminal(); + + void refresh(); + }; +} + + +#endif //INC_6502_EMULATOR_TERMINAL_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a84138e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include "machine/machine.h" +#include "machine/terminal.h" +#include "machine/memory.h" +#include "opcode/opcode-handler-directory.h" +#include "opcode/handler/load-opcode-handler-container.h" + +using namespace std; +using namespace emu_6502; + +int main() { + + vector code{0xa9, 53, 0xA5, 16}; + + auto machine = make_unique(); + machine->load(code, 0x600); + + machine->execute(); + +// auto memory = make_shared(); +// auto term = make_unique(memory); +// +// term->refresh(); + + + + return 0; +} \ No newline at end of file diff --git a/src/opcode/handler/load-opcode-handler-container.cpp b/src/opcode/handler/load-opcode-handler-container.cpp new file mode 100644 index 0000000..641adc1 --- /dev/null +++ b/src/opcode/handler/load-opcode-handler-container.cpp @@ -0,0 +1,92 @@ +#include "load-opcode-handler-container.h" +#include "../../utils.h" + +#include + +namespace emu_6502 { + LoadOpcodeHandlerContainer::LoadOpcodeHandlerContainer() : OpcodeHandlerContainer() { + handlers.insert({Op::LDA_IMM, [this](Machine& machine) { ld_imm(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_ZPG, [this](Machine& machine) { ld_zpg(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_ZPG_X, [this](Machine& machine) { ld_zpg_x(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_ABS, [this](Machine& machine) { ld_abs(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_ABS_X, [this](Machine& machine) { ld_abs_x(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_ABS_Y, [this](Machine& machine) { ld_abs_y(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_IND_X, [this](Machine& machine) { ld_ind_x(machine, machine.get_cpu().get_a()); }}); + handlers.insert({Op::LDA_IND_Y, [this](Machine& machine) { ld_ind_y(machine, machine.get_cpu().get_a()); }}); + + handlers.insert({Op::LDX_IMM, [this](Machine& machine) { ld_imm(machine, machine.get_cpu().get_x()); }}); + handlers.insert({Op::LDX_ZPG, [this](Machine& machine) { ld_zpg(machine, machine.get_cpu().get_x()); }}); + handlers.insert({Op::LDX_ZPG_Y, [this](Machine& machine) { ld_zpg_y(machine, machine.get_cpu().get_x()); }}); + handlers.insert({Op::LDX_ABS, [this](Machine& machine) { ld_abs(machine, machine.get_cpu().get_x()); }}); + handlers.insert({Op::LDX_ABS_Y, [this](Machine& machine) { ld_abs_y(machine, machine.get_cpu().get_x()); }}); + + handlers.insert({Op::LDY_IMM, [this](Machine& machine) { ld_imm(machine, machine.get_cpu().get_y()); }}); + handlers.insert({Op::LDY_ZPG, [this](Machine& machine) { ld_zpg(machine, machine.get_cpu().get_y()); }}); + handlers.insert({Op::LDY_ZPG_X, [this](Machine& machine) { ld_zpg_x(machine, machine.get_cpu().get_y()); }}); + handlers.insert({Op::LDY_ABS, [this](Machine& machine) { ld_abs(machine, machine.get_cpu().get_y()); }}); + handlers.insert({Op::LDY_ABS_X, [this](Machine& machine) { ld_abs_x(machine, machine.get_cpu().get_y()); }}); + } + + void LoadOpcodeHandlerContainer::set_flags(Cpu& cpu, Register& reg) { + auto value = reg.get_value(); + cpu.get_ps().set_negative((value & 0x80) == 0x80); + cpu.get_ps().set_zero(value == 0); + } + + void LoadOpcodeHandlerContainer::set_from(Machine& machine, Register& reg, uint16_t address) { + auto value = machine.get_memory().get_at(address); + reg.set_value(value); + set_flags(machine.get_cpu(), reg); + } + + void LoadOpcodeHandlerContainer::ld_imm(Machine& machine, Register& reg) { + reg.set_value(machine.read_program_byte()); + set_flags(machine.get_cpu(), reg); + } + + void LoadOpcodeHandlerContainer::ld_zpg(Machine& machine, Register& reg) { + auto addr = get_zpg_address(machine.read_program_byte()); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_zpg_x(Machine& machine, Register& reg) { + auto addr = get_zpg_x_address(machine.read_program_byte(), machine.get_cpu()); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_zpg_y(Machine& machine, Register& reg) { + auto addr = get_zpg_y_address(machine.read_program_byte(), machine.get_cpu()); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_abs(Machine& machine, Register& reg) { + auto low = machine.read_program_byte(); + auto high = machine.read_program_byte(); + auto addr = get_abs_address(low, high); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_abs_x(Machine& machine, Register& reg) { + auto low = machine.read_program_byte(); + auto high = machine.read_program_byte(); + auto addr = get_abs_x_address(low, high, machine.get_cpu()); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_abs_y(Machine& machine, Register& reg) { + auto low = machine.read_program_byte(); + auto high = machine.read_program_byte(); + auto addr = get_abs_y_address(low, high, machine.get_cpu()); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_ind_x(Machine& machine, Register& reg) { + auto addr = get_ind_x_address(machine.read_program_byte(), machine); + set_from(machine, reg, addr); + } + + void LoadOpcodeHandlerContainer::ld_ind_y(Machine& machine, Register& reg) { + auto addr = get_ind_y_address(machine.read_program_byte(), machine); + set_from(machine, reg, addr); + } +} \ No newline at end of file diff --git a/src/opcode/handler/load-opcode-handler-container.h b/src/opcode/handler/load-opcode-handler-container.h new file mode 100644 index 0000000..f78f92f --- /dev/null +++ b/src/opcode/handler/load-opcode-handler-container.h @@ -0,0 +1,58 @@ +#ifndef INC_6502_EMULATOR_LOAD_OPCODE_HANDLER_CONTAINER_H +#define INC_6502_EMULATOR_LOAD_OPCODE_HANDLER_CONTAINER_H + +#include "opcode-handler-container.h" +#include "../../machine/machine.h" + +#include +#include +#include + +using namespace std; + +namespace emu_6502 { + class LoadOpcodeHandlerContainer : public OpcodeHandlerContainer { + private: + enum Op { + LDA_IMM = 0xA9, + LDA_ZPG = 0xA5, + LDA_ZPG_X = 0xB5, + LDA_ABS = 0xAD, + LDA_ABS_X = 0xBD, + LDA_ABS_Y = 0xB9, + LDA_IND_X = 0xA1, + LDA_IND_Y = 0xB1, + + LDX_IMM = 0xA2, + LDX_ZPG = 0xA6, + LDX_ZPG_Y = 0xB6, + LDX_ABS = 0xAE, + LDX_ABS_Y = 0xBE, + + LDY_IMM = 0xA0, + LDY_ZPG = 0xA4, + LDY_ZPG_X = 0xB4, + LDY_ABS = 0xAC, + LDY_ABS_X = 0xBC + }; + + void set_flags(Cpu& machine, Register& reg); + void set_from(Machine& machine, Register& reg, uint16_t address); + + void ld_imm(Machine& machine, Register& reg); + void ld_zpg(Machine& machine, Register& reg); + void ld_zpg_x(Machine& machine, Register& reg); + void ld_zpg_y(Machine& machine, Register& reg); + void ld_abs(Machine& machine, Register& reg); + void ld_abs_x(Machine& machine, Register& reg); + void ld_abs_y(Machine& machine, Register& reg); + void ld_ind_x(Machine& machine, Register& reg); + void ld_ind_y(Machine& machine, Register& reg); + + public: + LoadOpcodeHandlerContainer(); + LoadOpcodeHandlerContainer(const LoadOpcodeHandlerContainer& other) = delete; + }; +} + +#endif //INC_6502_EMULATOR_LOAD_OPCODE_HANDLER_CONTAINER_H diff --git a/src/opcode/handler/opcode-handler-container.cpp b/src/opcode/handler/opcode-handler-container.cpp new file mode 100644 index 0000000..31a056c --- /dev/null +++ b/src/opcode/handler/opcode-handler-container.cpp @@ -0,0 +1,9 @@ +#include "opcode-handler-container.h" + +namespace emu_6502 { + OpcodeHandlerContainer::OpcodeHandlerContainer() : handlers{} {} + + const unordered_map>& OpcodeHandlerContainer::get_handlers() { + return handlers; + } +} diff --git a/src/opcode/handler/opcode-handler-container.h b/src/opcode/handler/opcode-handler-container.h new file mode 100644 index 0000000..3830cad --- /dev/null +++ b/src/opcode/handler/opcode-handler-container.h @@ -0,0 +1,24 @@ +#ifndef INC_6502_EMULATOR_OPCODE_HANDLER_CONTAINER_H +#define INC_6502_EMULATOR_OPCODE_HANDLER_CONTAINER_H + +#include "../../machine/machine.h" + +#include +#include +#include + +namespace emu_6502 { + class OpcodeHandlerContainer { + protected: + unordered_map> handlers; + + public: + OpcodeHandlerContainer(); + OpcodeHandlerContainer(const OpcodeHandlerContainer& other) = delete; + + virtual ~OpcodeHandlerContainer() {}; + + const unordered_map>& get_handlers(); + }; +} +#endif //INC_6502_EMULATOR_OPCODE_HANDLER_CONTAINER_H diff --git a/src/opcode/opcode-handler-directory.cpp b/src/opcode/opcode-handler-directory.cpp new file mode 100644 index 0000000..8e7dd5b --- /dev/null +++ b/src/opcode/opcode-handler-directory.cpp @@ -0,0 +1,28 @@ +#include "opcode-handler-directory.h" +#include "handler/load-opcode-handler-container.h" +#include "../utils.h" + +namespace emu_6502 { + + OpcodeHandlerDirectory::OpcodeHandlerDirectory() : handler_containers{}, handlers{} { + handler_containers.push_back(make_unique()); + + init_handlers(); + } + + void OpcodeHandlerDirectory::init_handlers() { + for (auto& hc : handler_containers) { + for (auto& h : hc->get_handlers()) { + handlers.insert({h.first, h.second}); + } + } + } + + void OpcodeHandlerDirectory::execute(uint8_t opcode, Machine& machine) { + auto func = handlers.find(opcode); + if (func == handlers.end()) + throw runtime_error("No appropriate handler for opcode 0x" + uint_to_hex(opcode)); + + func->second(machine); + } +} \ No newline at end of file diff --git a/src/opcode/opcode-handler-directory.h b/src/opcode/opcode-handler-directory.h new file mode 100644 index 0000000..fc46259 --- /dev/null +++ b/src/opcode/opcode-handler-directory.h @@ -0,0 +1,31 @@ +#ifndef INC_6502_EMULATOR_OPCODE_HANDLER_DIRECTORY_H +#define INC_6502_EMULATOR_OPCODE_HANDLER_DIRECTORY_H + +#include "handler/opcode-handler-container.h" +#include "../machine/machine.h" + +#include +#include +#include +#include + +using namespace std; + +namespace emu_6502 { + class OpcodeHandlerDirectory { + private: + vector> handler_containers; + unordered_map> handlers; + + void init_handlers(); + + public: + OpcodeHandlerDirectory(); + OpcodeHandlerDirectory(const OpcodeHandlerDirectory&) = delete; + OpcodeHandlerDirectory& operator=(const OpcodeHandlerDirectory&) = delete; + + void execute(uint8_t opcode, Machine& machine); + }; +} + +#endif //INC_6502_EMULATOR_OPCODE_HANDLER_DIRECTORY_H diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..405e8ec --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,57 @@ +#include "utils.h" + +namespace emu_6502 { + string uint_to_hex(uint64_t value) { + stringstream ss; + ss << hex << value; + + return ss.str(); + } + + uint8_t get_zpg_address(uint8_t offset) { + return offset; + } + + uint8_t get_zpg_x_address(uint8_t offset, Cpu& cpu) { + // expect wrap around + return offset + cpu.get_x().get_value(); + } + + uint8_t get_zpg_y_address(uint8_t offset, Cpu& cpu) { + // expect wrap around + return offset + cpu.get_y().get_value(); + } + + uint16_t get_abs_address(uint8_t low_byte, uint8_t high_byte) { + uint16_t address = (high_byte << 8) + low_byte; + return address; + } + + uint16_t get_abs_x_address(uint8_t low_byte, uint8_t high_byte, Cpu& cpu) { + uint16_t address = (high_byte << 8) + low_byte + cpu.get_x().get_value(); + return address; + } + + uint16_t get_abs_y_address(uint8_t low_byte, uint8_t high_byte, Cpu& cpu) { + uint16_t address = (high_byte << 8) + low_byte + cpu.get_y().get_value(); + return address; + } + + uint16_t get_ind_x_address(uint8_t offset, Machine& machine) { + uint8_t paddress = machine.get_cpu().get_x().get_value() + offset; + auto low = machine.get_memory().get_at(paddress); + auto high = machine.get_memory().get_at(paddress + 1); + uint16_t address = (high << 8) + low; + + return address; + } + + uint16_t get_ind_y_address(uint8_t offset, Machine& machine) { + auto low = machine.get_memory().get_at(offset); + auto high = machine.get_memory().get_at(offset + 1); + auto y = machine.get_cpu().get_y().get_value(); + uint16_t address = (high << 8) + low + y; + + return address; + } +} \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..44ee4a0 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,22 @@ +#ifndef INC_6502_EMULATOR_UTILS_H +#define INC_6502_EMULATOR_UTILS_H + +#include "machine/machine.h" +#include +#include + +using namespace std; + +namespace emu_6502 { + string uint_to_hex(uint64_t value); + uint8_t get_zpg_address(uint8_t offset); + uint8_t get_zpg_x_address(uint8_t offset, Cpu& cpu); + uint8_t get_zpg_y_address(uint8_t offset, Cpu& cpu); + uint16_t get_abs_address(uint8_t low_byte, uint8_t high_byte); + uint16_t get_abs_x_address(uint8_t low_byte, uint8_t high_byte, Cpu& cpu); + uint16_t get_abs_y_address(uint8_t low_byte, uint8_t high_byte, Cpu& cpu); + uint16_t get_ind_x_address(uint8_t offset, Machine& machine); + uint16_t get_ind_y_address(uint8_t offset, Machine& machine); +} + +#endif // INC_6502_EMULATOR_UTILS_H \ No newline at end of file diff --git a/test/load-opcode-handler-test.cpp b/test/load-opcode-handler-test.cpp new file mode 100644 index 0000000..7a3452d --- /dev/null +++ b/test/load-opcode-handler-test.cpp @@ -0,0 +1,760 @@ +#include "gtest/gtest.h" +#include "test-utils.h" + +#include +#include + +using namespace std; +using namespace emu_6502; + +const uint8_t LDA_IMM = 0xA9; +const uint8_t LDA_ZPG = 0xA5; +const uint8_t LDA_ZPG_X = 0xB5; +const uint8_t LDA_ABS = 0xAD; +const uint8_t LDA_ABS_X = 0xBD; +const uint8_t LDA_ABS_Y = 0xB9; +const uint8_t LDA_IND_X = 0xA1; +const uint8_t LDA_IND_Y = 0xB1; + +const uint8_t LDX_IMM = 0xA2; +const uint8_t LDX_ZPG = 0xA6; +const uint8_t LDX_ZPG_Y = 0xB6; +const uint8_t LDX_ABS = 0xAE; +const uint8_t LDX_ABS_Y = 0xBE; + +const uint8_t LDY_IMM = 0xA0; +const uint8_t LDY_ZPG = 0xA4; +const uint8_t LDY_ZPG_X = 0xB4; +const uint8_t LDY_ABS = 0xAC; +const uint8_t LDY_ABS_X = 0xBC; + +TEST(LoadOpcodeHandlerContainer, LDA_IMM) { + auto machine = create_machine({LDA_IMM, 36}); + machine->execute(); + + ASSERT_EQ(36, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IMM_ZeroFlag) { + auto machine = create_machine({LDA_IMM, 0}); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IMM_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDA_IMM, 128}); + machine->execute(); + + ASSERT_EQ(128, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG) { + auto machine = create_machine({LDA_ZPG, 123}); + + machine->get_memory().set_at(123, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG_ZeroFlag) { + auto machine = create_machine({LDA_ZPG, 123}); + + machine->get_memory().set_at(123, 0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG_NegativeFlag) { + auto machine = create_machine({LDA_ZPG, 123}); + + // ensure bit 7 set + machine->get_memory().set_at(123, 150); + machine->execute(); + + ASSERT_EQ(150, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG_X) { + auto machine = create_machine({LDA_ZPG_X, 0x10}); + machine->get_cpu().get_x().set_value(0x15); + + machine->get_memory().set_at(0x25, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG_X_Wrap) { + auto machine = create_machine({LDA_ZPG_X, 0xFF}); + machine->get_cpu().get_x().set_value(0x80); + + machine->get_memory().set_at(0x7F, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG_X_ZeroFlag) { + auto machine = create_machine({LDA_ZPG_X, 0x10}); + machine->get_cpu().get_x().set_value(0x15); + + machine->get_memory().set_at(0x25, 0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ZPG_X_NegativeFlag) { + auto machine = create_machine({LDA_ZPG_X, 0x10}); + machine->get_cpu().get_x().set_value(0x15); + + // ensure bit 7 set + machine->get_memory().set_at(0x25, 0xF5); + machine->execute(); + + ASSERT_EQ(0xF5, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS) { + auto machine = create_machine({LDA_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_ZeroFlag) { + auto machine = create_machine({LDA_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDA_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_X) { + auto machine = create_machine({LDA_ABS_X, 0x55, 0x66}); + machine->get_cpu().get_x().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_X_ZeroFlag) { + auto machine = create_machine({LDA_ABS_X, 0x55, 0x66}); + machine->get_cpu().get_x().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_X_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDA_ABS_X, 0x55, 0x66}); + machine->get_cpu().get_x().set_value(0x4); + machine->get_memory().set_at(0x6659, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_Y) { + auto machine = create_machine({LDA_ABS_Y, 0x55, 0x66}); + machine->get_cpu().get_y().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_Y_ZeroFlag) { + auto machine = create_machine({LDA_ABS_Y, 0x55, 0x66}); + machine->get_cpu().get_y().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_ABS_Y_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDA_ABS_Y, 0x55, 0x66}); + machine->get_cpu().get_y().set_value(0x4); + machine->get_memory().set_at(0x6659, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_X) { + auto machine = create_machine({LDA_IND_X, 0x55}); + machine->get_cpu().get_x().set_value(0x4); + + // pointer + machine->get_memory().set_at(0x59, 0x3c); + machine->get_memory().set_at(0x5A, 0x4d); + + // value + machine->get_memory().set_at(0x4d3c, 0x33); + + machine->execute(); + + ASSERT_EQ(0x33, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_X_Wrap) { + auto machine = create_machine({LDA_IND_X, 0xFF}); + machine->get_cpu().get_x().set_value(0x6b); + + // pointer + machine->get_memory().set_at(0x6a, 0x3c); + machine->get_memory().set_at(0x6b, 0x4d); + + // value + machine->get_memory().set_at(0x4d3c, 0x33); + + machine->execute(); + + ASSERT_EQ(0x33, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_X_ZeroFlag) { + auto machine = create_machine({LDA_IND_X, 0x55}); + machine->get_cpu().get_x().set_value(0x4); + + // pointer + machine->get_memory().set_at(0x59, 0x3c); + machine->get_memory().set_at(0x5A, 0x4d); + + // value + machine->get_memory().set_at(0x4d3c, 0); + + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_X_NegativeFlag) { + auto machine = create_machine({LDA_IND_X, 0x55}); + machine->get_cpu().get_x().set_value(0x4); + + // pointer + machine->get_memory().set_at(0x59, 0x3c); + machine->get_memory().set_at(0x5A, 0x4d); + + // value - ensure bit 7 set + machine->get_memory().set_at(0x4d3c, 0xe3); + + machine->execute(); + + ASSERT_EQ(0xe3, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_Y) { + auto machine = create_machine({LDA_IND_Y, 0x55}); + machine->get_memory().set_at(0x55, 0x12); + machine->get_memory().set_at(0x56, 0x34); + + machine->get_cpu().get_y().set_value(0x1a); + + machine->get_memory().set_at(0x342c, 0x33); + + machine->execute(); + + ASSERT_EQ(0x33, machine->get_cpu().get_a().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_Y_ZeroFlag) { + auto machine = create_machine({LDA_IND_Y, 0x55}); + machine->get_memory().set_at(0x55, 0x12); + machine->get_memory().set_at(0x56, 0x34); + + machine->get_cpu().get_y().set_value(0x1a); + + machine->get_memory().set_at(0x342c, 0); + + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDA_IND_Y_NegativeFlag) { + auto machine = create_machine({LDA_IND_Y, 0x55}); + machine->get_memory().set_at(0x55, 0x12); + machine->get_memory().set_at(0x56, 0x34); + + machine->get_cpu().get_y().set_value(0x1a); + + // ensure bit 7 set + machine->get_memory().set_at(0x342c, 0xf3); + + machine->execute(); + + ASSERT_EQ(0xf3, machine->get_cpu().get_a().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_IMM) { + auto machine = create_machine({LDX_IMM, 36}); + machine->execute(); + + ASSERT_EQ(36, machine->get_cpu().get_x().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDX_IMM_ZeroFlag) { + auto machine = create_machine({LDX_IMM, 0}); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_IMM_NegativeFlag) { + auto machine = create_machine({LDX_IMM, 0xff}); + machine->execute(); + + ASSERT_EQ(0xff, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG) { + auto machine = create_machine({LDX_ZPG, 123}); + + machine->get_memory().set_at(123, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_x().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG_ZeroFlag) { + auto machine = create_machine({LDX_ZPG, 123}); + + machine->get_memory().set_at(123, 0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG_NegativeFlag) { + auto machine = create_machine({LDX_ZPG, 123}); + + // ensure bit 7 set + machine->get_memory().set_at(123, 150); + machine->execute(); + + ASSERT_EQ(150, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG_Y) { + auto machine = create_machine({LDX_ZPG_Y, 0x10}); + machine->get_cpu().get_y().set_value(0x15); + + machine->get_memory().set_at(0x25, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_x().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG_Y_Wrap) { + auto machine = create_machine({LDX_ZPG_Y, 0xFF}); + machine->get_cpu().get_y().set_value(0x80); + + machine->get_memory().set_at(0x7F, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_x().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG_Y_ZeroFlag) { + auto machine = create_machine({LDX_ZPG_Y, 0x10}); + machine->get_cpu().get_y().set_value(0x15); + + machine->get_memory().set_at(0x25, 0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ZPG_Y_NegativeFlag) { + auto machine = create_machine({LDX_ZPG_Y, 0x10}); + machine->get_cpu().get_y().set_value(0x15); + + // ensure bit 7 set + machine->get_memory().set_at(0x25, 0xF5); + machine->execute(); + + ASSERT_EQ(0xF5, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ABS) { + auto machine = create_machine({LDX_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_x().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ABS_ZeroFlag) { + auto machine = create_machine({LDX_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ABS_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDX_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ABS_Y) { + auto machine = create_machine({LDX_ABS_Y, 0x55, 0x66}); + machine->get_cpu().get_y().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_x().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ABS_Y_ZeroFlag) { + auto machine = create_machine({LDX_ABS_Y, 0x55, 0x66}); + machine->get_cpu().get_y().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDX_ABS_Y_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDX_ABS_Y, 0x55, 0x66}); + machine->get_cpu().get_y().set_value(0x4); + machine->get_memory().set_at(0x6659, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_x().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_IMM) { + auto machine = create_machine({LDY_IMM, 36}); + machine->execute(); + + ASSERT_EQ(36, machine->get_cpu().get_y().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDY_IMM_ZeroFlag) { + auto machine = create_machine({LDY_IMM, 0}); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_IMM_NegativeFlag) { + auto machine = create_machine({LDY_IMM, 0xff}); + machine->execute(); + + ASSERT_EQ(0xff, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG) { + auto machine = create_machine({LDY_ZPG, 123}); + + machine->get_memory().set_at(123, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_y().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG_ZeroFlag) { + auto machine = create_machine({LDY_ZPG, 123}); + + machine->get_memory().set_at(123, 0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG_NegativeFlag) { + auto machine = create_machine({LDY_ZPG, 123}); + + // ensure bit 7 set + machine->get_memory().set_at(123, 150); + machine->execute(); + + ASSERT_EQ(150, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG_X) { + auto machine = create_machine({LDY_ZPG_X, 0x10}); + machine->get_cpu().get_x().set_value(0x15); + + machine->get_memory().set_at(0x25, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_y().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG_X_Wrap) { + auto machine = create_machine({LDY_ZPG_X, 0xFF}); + machine->get_cpu().get_x().set_value(0x80); + + machine->get_memory().set_at(0x7F, 78); + machine->execute(); + + ASSERT_EQ(78, machine->get_cpu().get_y().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG_X_ZeroFlag) { + auto machine = create_machine({LDY_ZPG_X, 0x10}); + machine->get_cpu().get_x().set_value(0x15); + + machine->get_memory().set_at(0x25, 0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ZPG_X_NegativeFlag) { + auto machine = create_machine({LDY_ZPG_X, 0x10}); + machine->get_cpu().get_x().set_value(0x15); + + // ensure bit 7 set + machine->get_memory().set_at(0x25, 0xF5); + machine->execute(); + + ASSERT_EQ(0xF5, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ABS) { + auto machine = create_machine({LDY_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_y().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ABS_ZeroFlag) { + auto machine = create_machine({LDY_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ABS_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDY_ABS, 0x55, 0x66}); + machine->get_memory().set_at(0x6655, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ABS_X) { + auto machine = create_machine({LDY_ABS_X, 0x55, 0x66}); + machine->get_cpu().get_x().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x10); + machine->execute(); + + ASSERT_EQ(0x10, machine->get_cpu().get_y().get_value()); + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), RegisterFlagSet{})); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ABS_X_ZeroFlag) { + auto machine = create_machine({LDY_ABS_X, 0x55, 0x66}); + machine->get_cpu().get_x().set_value(0x4); + machine->get_memory().set_at(0x6659, 0x0); + machine->execute(); + + ASSERT_EQ(0, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.zero = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} + +TEST(LoadOpcodeHandlerContainer, LDY_ABS_X_NegativeFlag) { + // ensure bit 7 set + auto machine = create_machine({LDY_ABS_X, 0x55, 0x66}); + machine->get_cpu().get_x().set_value(0x4); + machine->get_memory().set_at(0x6659, 0xF6); + machine->execute(); + + ASSERT_EQ(0xF6, machine->get_cpu().get_y().get_value()); + + RegisterFlagSet flags{}; + flags.negative = true; + ASSERT_TRUE(are_flags_set(machine->get_cpu().get_ps(), flags)); +} diff --git a/test/test-utils.h b/test/test-utils.h new file mode 100644 index 0000000..1cffa61 --- /dev/null +++ b/test/test-utils.h @@ -0,0 +1,36 @@ +#include "../src/opcode/opcode-handler-directory.h" +#include "../src/machine/status-register.h" + +#include +#include + +using namespace std; +using namespace emu_6502; + +struct RegisterFlagSet { + bool carry; + bool zero; + bool interupt_disable; + bool decimal; + bool brk; + bool overflow; + bool negative; +}; + +unique_ptr create_machine(vector code) { + auto machine = make_unique(); + machine->load(code, 0x600); + + return machine; +} + +bool are_flags_set(const StatusRegister& reg, const RegisterFlagSet& flags) { + return + flags.carry == reg.is_carry_set() && + flags.zero == reg.is_zero_set() && + flags.interupt_disable == reg.is_interupt_disable_set() && + flags.decimal == reg.is_interupt_disable_set() && + flags.brk == reg.is_break_set() && + flags.overflow == reg.is_overflow_set() && + flags.negative == reg.is_negative_set(); +} \ No newline at end of file