From 5959349f341a2380c0c3f41ec40a1e8e0d969d00 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Wed, 29 Jan 2025 14:49:20 +0100 Subject: refactor: move emulation logic into sys --- app/CMakeLists.txt | 4 +- app/mac/controller-adapter.hh | 2 +- app/mac/scene.mm | 4 +- app/test/CMakeLists.txt | 14 - app/test/main.cc | 82 --- lib/CMakeLists.txt | 6 - lib/nes/CMakeLists.txt | 20 +- lib/nes/cartridge.cc | 68 -- lib/nes/cartridge.hh | 78 --- lib/nes/common/CMakeLists.txt | 6 + lib/nes/common/debug.hh | 15 + lib/nes/common/display.hh | 44 ++ lib/nes/common/rgb.hh | 33 + lib/nes/controller.cc | 18 - lib/nes/controller.hh | 30 - lib/nes/cpu.cc | 1425 -------------------------------------- lib/nes/cpu.hh | 300 -------- lib/nes/mapper.cc | 172 ----- lib/nes/mapper.hh | 32 - lib/nes/nes.cc | 65 -- lib/nes/nes.hh | 47 -- lib/nes/ppu.cc | 697 ------------------- lib/nes/ppu.hh | 279 -------- lib/nes/ref/CMakeLists.txt | 15 - lib/nes/ref/cartridge.cc | 70 -- lib/nes/ref/cartridge.hh | 41 -- lib/nes/ref/controller.cc | 18 - lib/nes/ref/controller.hh | 30 - lib/nes/ref/cpu.cc | 1358 ------------------------------------ lib/nes/ref/cpu.hh | 298 -------- lib/nes/ref/mapper.cc | 134 ---- lib/nes/ref/mapper.hh | 64 -- lib/nes/ref/nes.cc | 74 -- lib/nes/ref/nes.hh | 49 -- lib/nes/ref/ppu.cc | 737 -------------------- lib/nes/ref/ppu.hh | 212 ------ lib/nes/sys/CMakeLists.txt | 17 + lib/nes/sys/cartridge.cc | 68 ++ lib/nes/sys/cartridge.hh | 78 +++ lib/nes/sys/controller.cc | 18 + lib/nes/sys/controller.hh | 30 + lib/nes/sys/cpu.cc | 1425 ++++++++++++++++++++++++++++++++++++++ lib/nes/sys/cpu.hh | 300 ++++++++ lib/nes/sys/mapper.cc | 172 +++++ lib/nes/sys/mapper.hh | 32 + lib/nes/sys/nes.cc | 65 ++ lib/nes/sys/nes.hh | 47 ++ lib/nes/sys/ppu.cc | 697 +++++++++++++++++++ lib/nes/sys/ppu.hh | 279 ++++++++ lib/nes/sys/types/CMakeLists.txt | 8 + lib/nes/sys/types/address.hh | 74 ++ lib/nes/sys/types/button-mask.hh | 44 ++ lib/nes/sys/types/cycle-count.hh | 87 +++ lib/nes/sys/types/snapshot.hh | 63 ++ lib/nes/sys/types/status.hh | 36 + lib/nes/util/CMakeLists.txt | 11 - lib/nes/util/address.hh | 73 -- lib/nes/util/button-mask.hh | 43 -- lib/nes/util/cycle-count.hh | 87 --- lib/nes/util/debug.hh | 15 - lib/nes/util/display.hh | 44 -- lib/nes/util/rgb.hh | 33 - lib/nes/util/snapshot.hh | 63 -- lib/nes/util/status.hh | 36 - 64 files changed, 3644 insertions(+), 6842 deletions(-) delete mode 100644 app/test/CMakeLists.txt delete mode 100644 app/test/main.cc delete mode 100644 lib/nes/cartridge.cc delete mode 100644 lib/nes/cartridge.hh create mode 100644 lib/nes/common/CMakeLists.txt create mode 100644 lib/nes/common/debug.hh create mode 100644 lib/nes/common/display.hh create mode 100644 lib/nes/common/rgb.hh delete mode 100644 lib/nes/controller.cc delete mode 100644 lib/nes/controller.hh delete mode 100644 lib/nes/cpu.cc delete mode 100644 lib/nes/cpu.hh delete mode 100644 lib/nes/mapper.cc delete mode 100644 lib/nes/mapper.hh delete mode 100644 lib/nes/nes.cc delete mode 100644 lib/nes/nes.hh delete mode 100644 lib/nes/ppu.cc delete mode 100644 lib/nes/ppu.hh delete mode 100644 lib/nes/ref/CMakeLists.txt delete mode 100644 lib/nes/ref/cartridge.cc delete mode 100644 lib/nes/ref/cartridge.hh delete mode 100644 lib/nes/ref/controller.cc delete mode 100644 lib/nes/ref/controller.hh delete mode 100644 lib/nes/ref/cpu.cc delete mode 100644 lib/nes/ref/cpu.hh delete mode 100644 lib/nes/ref/mapper.cc delete mode 100644 lib/nes/ref/mapper.hh delete mode 100644 lib/nes/ref/nes.cc delete mode 100644 lib/nes/ref/nes.hh delete mode 100644 lib/nes/ref/ppu.cc delete mode 100644 lib/nes/ref/ppu.hh create mode 100644 lib/nes/sys/CMakeLists.txt create mode 100644 lib/nes/sys/cartridge.cc create mode 100644 lib/nes/sys/cartridge.hh create mode 100644 lib/nes/sys/controller.cc create mode 100644 lib/nes/sys/controller.hh create mode 100644 lib/nes/sys/cpu.cc create mode 100644 lib/nes/sys/cpu.hh create mode 100644 lib/nes/sys/mapper.cc create mode 100644 lib/nes/sys/mapper.hh create mode 100644 lib/nes/sys/nes.cc create mode 100644 lib/nes/sys/nes.hh create mode 100644 lib/nes/sys/ppu.cc create mode 100644 lib/nes/sys/ppu.hh create mode 100644 lib/nes/sys/types/CMakeLists.txt create mode 100644 lib/nes/sys/types/address.hh create mode 100644 lib/nes/sys/types/button-mask.hh create mode 100644 lib/nes/sys/types/cycle-count.hh create mode 100644 lib/nes/sys/types/snapshot.hh create mode 100644 lib/nes/sys/types/status.hh delete mode 100644 lib/nes/util/CMakeLists.txt delete mode 100644 lib/nes/util/address.hh delete mode 100644 lib/nes/util/button-mask.hh delete mode 100644 lib/nes/util/cycle-count.hh delete mode 100644 lib/nes/util/debug.hh delete mode 100644 lib/nes/util/display.hh delete mode 100644 lib/nes/util/rgb.hh delete mode 100644 lib/nes/util/snapshot.hh delete mode 100644 lib/nes/util/status.hh diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 8ff9434..f3e864f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,5 +1,3 @@ if(APPLE) add_subdirectory(mac) -endif() - -add_subdirectory(test) \ No newline at end of file +endif() \ No newline at end of file diff --git a/app/mac/controller-adapter.hh b/app/mac/controller-adapter.hh index 7a4c4b4..09e8b6d 100644 --- a/app/mac/controller-adapter.hh +++ b/app/mac/controller-adapter.hh @@ -1,6 +1,6 @@ #pragma once -#import "nes/util/button-mask.hh" +#import "nes/sys/types/button-mask.hh" #import diff --git a/app/mac/scene.mm b/app/mac/scene.mm index 4ede6e0..97caadf 100644 --- a/app/mac/scene.mm +++ b/app/mac/scene.mm @@ -1,6 +1,6 @@ #import "scene.hh" -#import "nes/nes.hh" -#import "nes/util/display.hh" +#import "nes/sys/nes.hh" +#import "nes/common/display.hh" #import #import #import diff --git a/app/test/CMakeLists.txt b/app/test/CMakeLists.txt deleted file mode 100644 index b88acc2..0000000 --- a/app/test/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -add_executable(nes_test) - -target_include_directories(nes_test PRIVATE .) -target_link_libraries( - nes_test - PRIVATE - nes::options - nes::nes - nes::ref) - -target_sources( - nes_test - PRIVATE - main.cc) \ No newline at end of file diff --git a/app/test/main.cc b/app/test/main.cc deleted file mode 100644 index 70a66ac..0000000 --- a/app/test/main.cc +++ /dev/null @@ -1,82 +0,0 @@ -#include "nes/util/display.hh" -#include "nes/util/rgb.hh" -#include "nes/util/snapshot.hh" -#include "nes/nes.hh" -#include "nes/ref/nes.hh" -#include - -namespace -{ - class dummy_display final : public nes::display - { - std::uint8_t buffer_a_[width * height * 3]{}; - std::uint8_t buffer_b_[width * height * 3]{}; - std::uint8_t* front_buffer_{ buffer_a_ }; - std::uint8_t* back_buffer_{ buffer_b_ }; - - public: - explicit dummy_display() = default; - - auto get(unsigned const x, unsigned const y) const -> nes::rgb override - { - auto const offset = ((y * width) + x) * 3; - return nes::rgb{ back_buffer_[offset + 0], back_buffer_[offset + 1], back_buffer_[offset + 2] }; - } - - auto set(unsigned const x, unsigned const y, nes::rgb const value) -> void override - { - auto const offset = ((y * width) + x) * 3; - back_buffer_[offset + 0] = value.r; - back_buffer_[offset + 1] = value.g; - back_buffer_[offset + 2] = value.b; - } - - auto switch_buffers() -> void override - { - std::swap(front_buffer_, back_buffer_); - } - }; -} // namespace - -int main() -{ - auto const path = "/Users/hannes/Documents/temp/supermario.nes"; - - // Initialize the system to be tested. - auto cartridge_a = nes::cartridge::from_file(path); - if (cartridge_a.get_status() != nes::status::success) - { - std::cerr << "Could not load cartridge: " << to_string(cartridge_a.get_status()) << std::endl; - std::abort(); - } - auto display_a = std::make_unique(); - auto sys_a = nes::nes{ std::move(cartridge_a), *display_a }; - - // Initialize the (assumed to be working) reference implementation. - auto cartridge_b = nes::ref::cartridge::from_file(path); - if (!cartridge_b.is_valid()) - { - std::cerr << "Could not load cartridge!" << std::endl; - std::abort(); - } - auto display_b = std::make_unique(); - auto sys_b = nes::ref::nes{ std::move(cartridge_b), *display_b }; - - // Run. - while (true) - { - auto const status_a = sys_a.get_snapshot(); - auto const status_b = sys_b.get_snapshot(); - - if (status_a != status_b) - { - std::cout << "Different snapshot!\n"; - return 1; - } - - std::cout << "Cycle: " << status_a.cpu_cycle.to_cpu() << ", " << status_b.cpu_cycle.to_cpu() << '\n'; - - sys_a.step_to_nmi(); - sys_b.step_to_nmi(); - } -} \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e98002c..7dc5b9d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -12,10 +12,4 @@ if(NES_ENABLE_DEBUG_OUTPUT) target_link_libraries(nes PUBLIC spdlog::spdlog) endif() -add_library(nes_ref STATIC) -add_library(nes::ref ALIAS nes_ref) - -target_link_libraries(nes_ref PRIVATE nes::options) -target_link_libraries(nes_ref PUBLIC nes::nes) - add_subdirectory(nes) \ No newline at end of file diff --git a/lib/nes/CMakeLists.txt b/lib/nes/CMakeLists.txt index 3fb7a73..78c09fe 100644 --- a/lib/nes/CMakeLists.txt +++ b/lib/nes/CMakeLists.txt @@ -1,18 +1,2 @@ -target_sources( - nes - PRIVATE - nes.hh - nes.cc - cartridge.hh - cartridge.cc - controller.hh - controller.cc - cpu.hh - cpu.cc - ppu.hh - ppu.cc - mapper.hh - mapper.cc) - -add_subdirectory(util) -add_subdirectory(ref) \ No newline at end of file +add_subdirectory(common) +add_subdirectory(sys) \ No newline at end of file diff --git a/lib/nes/cartridge.cc b/lib/nes/cartridge.cc deleted file mode 100644 index bd8e7d3..0000000 --- a/lib/nes/cartridge.cc +++ /dev/null @@ -1,68 +0,0 @@ -#include "nes/cartridge.hh" -#include "nes/util/status.hh" -#include - -namespace nes -{ - cartridge::cartridge(status const status) - : status_{ status } - { - } - - auto cartridge::from_data(void const* data, std::size_t const length) -> cartridge - { - // See: https://www.nesdev.org/wiki/INES - - auto const bytes = static_cast(data); - auto res = cartridge{ status::success }; - - // Parse header. - if (length < header_length) { return cartridge{ status::error_invalid_ines_data }; } - auto const h = header{ bytes }; - if (h.get_magic_0() != 'N' || h.get_magic_1() != 'E' || h.get_magic_2() != 'S' || h.get_magic_3() != 0x1A) - { - return cartridge{ status::error_invalid_ines_data }; - } - auto const mapper_number = static_cast((h.get_mapper_high() << 4) | (h.get_mapper_low() << 0)); - - res.mapper_ = &mapper::get(mapper_number); - res.name_table_arrangement_ = h.get_name_table_arrangement(); - - // Load program data. - auto offset = std::size_t{ 16 }; - if (h.get_has_trainer()) { offset += 512; } - - auto const prg_rom_size = h.get_prg_rom_banks() * prg_rom_bank_size; - if (length < offset + prg_rom_size) { return cartridge{ status::error_invalid_ines_data }; } - res.prg_rom_ = std::vector(&bytes[offset], &bytes[offset + prg_rom_size]); - - offset += prg_rom_size; - - auto const chr_rom_size = h.get_chr_rom_banks() * chr_rom_bank_size; - if (length < offset + chr_rom_size) { return cartridge{ status::error_invalid_ines_data }; } - res.chr_rom_ = std::vector(&bytes[offset], &bytes[offset + chr_rom_size]); - - res.ram_ = std::vector(std::max(h.get_ram_banks(), 1u) * ram_bank_size, std::uint8_t{ 0 }); - - if (auto const status = res.get_mapper().validate(res); status != status::success) - { - return cartridge{ status }; - } - - return res; - } - - auto cartridge::from_file(std::string_view const path) -> cartridge - { - auto stream = std::ifstream{ path, std::ios::binary }; - if (!stream.good()) { return cartridge{ status::error_system_error }; } - - stream.seekg(0, std::ios::end); - auto const length = stream.tellg(); - stream.seekg(0, std::ios::beg); - auto data = std::vector(static_cast(length)); - stream.read(reinterpret_cast(data.data()), length); - - return from_data(data.data(), data.size()); - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/cartridge.hh b/lib/nes/cartridge.hh deleted file mode 100644 index fb75946..0000000 --- a/lib/nes/cartridge.hh +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "nes/util/status.hh" -#include "nes/mapper.hh" -#include -#include -#include - -namespace nes -{ - enum class name_table_arrangement - { - horizontal, - vertical, - }; - - class cartridge - { - static constexpr auto prg_rom_bank_size = std::size_t{ 16 * 1024 }; - static constexpr auto chr_rom_bank_size = std::size_t{ 8 * 1024 }; - static constexpr auto ram_bank_size = std::size_t{ 8 * 1024 }; - static constexpr auto header_length = std::size_t{ 16 }; - - struct header - { - auto get_magic_0() const -> std::uint8_t { return value[0]; } - auto get_magic_1() const -> std::uint8_t { return value[1]; } - auto get_magic_2() const -> std::uint8_t { return value[2]; } - auto get_magic_3() const -> std::uint8_t { return value[3]; } - auto get_prg_rom_banks() const -> unsigned { return value[4]; } - auto get_chr_rom_banks() const -> unsigned { return value[5]; } - auto get_control_1() const -> std::uint8_t { return value[6]; } - auto get_control_2() const -> std::uint8_t { return value[7]; } - auto get_ram_banks() const -> unsigned { return value[8]; } - auto get_has_trainer() const -> bool { return get_control_1() & 0b00000100; } - auto get_mapper_low() const -> unsigned { return (get_control_1() & 0b11110000) >> 4; } - auto get_mapper_high() const -> unsigned { return (get_control_2() & 0b11110000) >> 0; } - auto get_name_table_arrangement() const -> name_table_arrangement { return static_cast((get_control_1() & 0b00000001) >> 0); } - - std::uint8_t value[header_length]{}; - - explicit header(std::uint8_t const* v) - { - for (auto i = std::size_t{ 0 }; i < header_length; ++i) - { - value[i] = v[i]; - } - } - }; - - status status_{ status::error_uninitialized }; - std::vector prg_rom_; - std::vector chr_rom_; - std::vector ram_; - name_table_arrangement name_table_arrangement_{}; - mapper* mapper_{ &mapper::invalid() }; - - public: - explicit cartridge() = default; - - static auto from_data(void const* data, std::size_t length) -> cartridge; - static auto from_file(std::string_view path) -> cartridge; - - auto get_status() const -> status { return status_; } - auto get_prg_rom() const -> std::uint8_t const* { return prg_rom_.data(); } - auto get_prg_rom_length() const -> std::size_t { return prg_rom_.size(); } - auto get_chr_rom() const -> std::uint8_t const* { return chr_rom_.data(); } - auto get_chr_rom_length() const -> std::size_t { return chr_rom_.size(); } - auto get_ram() const -> std::uint8_t const* { return ram_.data(); } - auto get_ram_mut() -> std::uint8_t* { return ram_.data(); } - auto get_ram_length() const -> std::size_t { return ram_.size(); } - auto get_mapper() const -> mapper& { return *mapper_; } - auto get_name_table_arrangement() const -> name_table_arrangement { return name_table_arrangement_; } - - private: - explicit cartridge(status); - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/common/CMakeLists.txt b/lib/nes/common/CMakeLists.txt new file mode 100644 index 0000000..69ae435 --- /dev/null +++ b/lib/nes/common/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources( + nes + PRIVATE + debug.hh + rgb.hh + display.hh) \ No newline at end of file diff --git a/lib/nes/common/debug.hh b/lib/nes/common/debug.hh new file mode 100644 index 0000000..409d55a --- /dev/null +++ b/lib/nes/common/debug.hh @@ -0,0 +1,15 @@ +#pragma once + +#ifdef NES_ENABLE_DEBUG_OUTPUT + +#include + +#define NES_DEBUG_LOG(name, ...) \ + spdlog::info("[" #name "] " __VA_ARGS__) + +#else + +#define NES_DEBUG_LOG(name, fmt, ...) \ + (void)0 + +#endif \ No newline at end of file diff --git a/lib/nes/common/display.hh b/lib/nes/common/display.hh new file mode 100644 index 0000000..ca78a85 --- /dev/null +++ b/lib/nes/common/display.hh @@ -0,0 +1,44 @@ +#pragma once + +#include "nes/common/rgb.hh" + +namespace nes +{ + class rgb; + + /// Abstraction of the display logic. + class display + { + public: + static constexpr auto width = unsigned{ 256 }; + static constexpr auto height = unsigned{ 240 }; + + virtual ~display() = default; + + display(display const&) = delete; + display(display&&) = delete; + auto operator=(display const&) -> display& = delete; + auto operator=(display&&) -> display& = delete; + + /// Switch between front and back buffers. + virtual auto switch_buffers() -> void = 0; + /// Update the pixel at the given position in the back buffer. + virtual auto set(unsigned x, unsigned y, rgb value) -> void = 0; + /// Load the pixel value at the given position in the back buffer. + virtual auto get(unsigned x, unsigned y) const -> rgb = 0; + /// Clear the entire back buffer with a color. + virtual auto clear(rgb const color) -> void + { + for (auto y = unsigned{ 0 }; y < height; ++y) + { + for (auto x = unsigned{ 0 }; x < width; ++x) + { + set(x, y, color); + } + } + } + + protected: + explicit display() = default; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/common/rgb.hh b/lib/nes/common/rgb.hh new file mode 100644 index 0000000..d62c70c --- /dev/null +++ b/lib/nes/common/rgb.hh @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace nes +{ + /// An RGB value. + class rgb + { + public: + explicit rgb() = default; + + explicit rgb(std::uint8_t const r, std::uint8_t const g, std::uint8_t const b) + : r{ r } + , g{ g } + , b{ b } + { + } + + static auto from_hex(std::uint32_t const hex) -> rgb + { + auto res = rgb{}; + res.r = (hex & 0x00FF0000) >> 16; + res.g = (hex & 0x0000FF00) >> 8; + res.b = (hex & 0x000000FF) >> 0; + return res; + } + + std::uint8_t r{ 0 }; + std::uint8_t g{ 0 }; + std::uint8_t b{ 0 }; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/controller.cc b/lib/nes/controller.cc deleted file mode 100644 index d9d6946..0000000 --- a/lib/nes/controller.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "nes/controller.hh" - -namespace nes -{ - auto controller::read() -> std::uint8_t - { - auto const res = index_ < 8 ? (pressed_.get_raw_value() >> index_) & 1 : 0; - index_ += 1; - if (strobing_) { index_ = 0; } - return static_cast(res); - } - - auto controller::write(std::uint8_t const value) -> void - { - strobing_ = value & 1; - if (strobing_) { index_ = 0; } - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/controller.hh b/lib/nes/controller.hh deleted file mode 100644 index eb87545..0000000 --- a/lib/nes/controller.hh +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "nes/util/button-mask.hh" - -namespace nes -{ - class controller - { - button_mask pressed_; - unsigned index_{ 0 }; - bool strobing_{ false }; - - public: - explicit controller() = default; - - controller(controller const&) = delete; - controller(controller&&) = delete; - auto operator=(controller const&) -> controller& = delete; - auto operator=(controller&&) -> controller& = delete; - - auto get_pressed() const -> button_mask { return pressed_;} - auto ref_pressed() -> button_mask& { return pressed_; } - auto set_pressed(button_mask const value) -> void { pressed_ = value; } - - // IO register - - auto read() -> std::uint8_t; - auto write(std::uint8_t) -> void; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/cpu.cc b/lib/nes/cpu.cc deleted file mode 100644 index cf8943f..0000000 --- a/lib/nes/cpu.cc +++ /dev/null @@ -1,1425 +0,0 @@ -#include "nes/cpu.hh" -#include "nes/ppu.hh" -#include "nes/controller.hh" -#include "nes/cartridge.hh" -#include "nes/util/snapshot.hh" - -namespace nes -{ - cpu::cpu(ppu& ppu, cartridge& cartridge, controller& controller_1, controller& controller_2) - : ppu_{ ppu } - , cartridge_{ cartridge } - , controller_1_{ controller_1 } - , controller_2_{ controller_2 } - { - registers_.pc = read16(address{ 0xFFFC }); - } - - auto cpu::build_snapshot(snapshot& snapshot) -> void - { - snapshot.cpu_cycle = current_cycles_; - snapshot.ram = std::vector(std::begin(ram_), std::end(ram_)); - snapshot.registers.pc = registers_.pc; - snapshot.registers.sp = registers_.sp; - snapshot.registers.a = registers_.a; - snapshot.registers.x = registers_.x; - snapshot.registers.y = registers_.y; - snapshot.registers.p = registers_.p.value; - snapshot.registers.c = registers_.p.get_c(); - snapshot.registers.z = registers_.p.get_z(); - snapshot.registers.i = registers_.p.get_i(); - snapshot.registers.d = registers_.p.get_d(); - snapshot.registers.b = registers_.p.get_b(); - snapshot.registers.v = registers_.p.get_v(); - snapshot.registers.n = registers_.p.get_n(); - } - - auto cpu::stall_cycles(cycle_count const count) -> void - { - current_cycles_ += count; - } - - auto cpu::trigger_nmi() -> void - { - nmi_pending_ = true; - } - - auto cpu::step() -> status - { - // See https://www.nesdev.org/wiki/CPU_unofficial_opcodes - -#ifdef NES_ENABLE_PPU - if (nmi_pending_) - { - execute_interrupt(address{ 0xFFFA }); - nmi_pending_ = false; - } -#endif - - auto const opcode = advance_pc8(); - switch (opcode) - { - case 0x00: return run_brk(); - case 0x01: return run_ora(); - case 0x02: return run_stp(); - case 0x03: return run_slo(); - case 0x04: return run_nop(); - case 0x05: return run_ora(); - case 0x06: return run_asl(); - case 0x07: return run_slo(); - case 0x08: return run_php(); - case 0x09: return run_ora(); - case 0x0A: return run_asl(); - case 0x0B: return run_anc(); - case 0x0C: return run_nop(); - case 0x0D: return run_ora(); - case 0x0E: return run_asl(); - case 0x0F: return run_slo(); - case 0x10: return run_bpl(); - case 0x11: return run_ora(); - case 0x12: return run_stp(); - case 0x13: return run_slo(); - case 0x14: return run_nop(); - case 0x15: return run_ora(); - case 0x16: return run_asl(); - case 0x17: return run_slo(); - case 0x18: return run_clc(); - case 0x19: return run_ora(); - case 0x1A: return run_nop(); - case 0x1B: return run_slo(); - case 0x1C: return run_nop(); - case 0x1D: return run_ora(); - case 0x1E: return run_asl(); - case 0x1F: return run_slo(); - case 0x20: return run_jsr(); - case 0x21: return run_and(); - case 0x22: return run_stp(); - case 0x23: return run_rla(); - case 0x24: return run_bit(); - case 0x25: return run_and(); - case 0x26: return run_rol(); - case 0x27: return run_rla(); - case 0x28: return run_plp(); - case 0x29: return run_and(); - case 0x2A: return run_rol(); - case 0x2B: return run_anc(); - case 0x2C: return run_bit(); - case 0x2D: return run_and(); - case 0x2E: return run_rol(); - case 0x2F: return run_rla(); - case 0x30: return run_bmi(); - case 0x31: return run_and(); - case 0x32: return run_stp(); - case 0x33: return run_rla(); - case 0x34: return run_nop(); - case 0x35: return run_and(); - case 0x36: return run_rol(); - case 0x37: return run_rla(); - case 0x38: return run_sec(); - case 0x39: return run_and(); - case 0x3A: return run_nop(); - case 0x3B: return run_rla(); - case 0x3C: return run_nop(); - case 0x3D: return run_and(); - case 0x3E: return run_rol(); - case 0x3F: return run_rla(); - case 0x40: return run_rti(); - case 0x41: return run_eor(); - case 0x42: return run_stp(); - case 0x43: return run_sre(); - case 0x44: return run_nop(); - case 0x45: return run_eor(); - case 0x46: return run_lsr(); - case 0x47: return run_sre(); - case 0x48: return run_pha(); - case 0x49: return run_eor(); - case 0x4A: return run_lsr(); - case 0x4B: return run_alr(); - case 0x4C: return run_jmp(); - case 0x4D: return run_eor(); - case 0x4E: return run_lsr(); - case 0x4F: return run_sre(); - case 0x50: return run_bvc(); - case 0x51: return run_eor(); - case 0x52: return run_stp(); - case 0x53: return run_sre(); - case 0x54: return run_nop(); - case 0x55: return run_eor(); - case 0x56: return run_lsr(); - case 0x57: return run_sre(); - case 0x58: return run_cli(); - case 0x59: return run_eor(); - case 0x5A: return run_nop(); - case 0x5B: return run_sre(); - case 0x5C: return run_nop(); - case 0x5D: return run_eor(); - case 0x5E: return run_lsr(); - case 0x5F: return run_sre(); - case 0x60: return run_rts(); - case 0x61: return run_adc(); - case 0x62: return run_stp(); - case 0x63: return run_rra(); - case 0x64: return run_nop(); - case 0x65: return run_adc(); - case 0x66: return run_ror(); - case 0x67: return run_rra(); - case 0x68: return run_pla(); - case 0x69: return run_adc(); - case 0x6A: return run_ror(); - case 0x6B: return run_arr(); - case 0x6C: return run_jmp(); - case 0x6D: return run_adc(); - case 0x6E: return run_ror(); - case 0x6F: return run_rra(); - case 0x70: return run_bvs(); - case 0x71: return run_adc(); - case 0x72: return run_stp(); - case 0x73: return run_rra(); - case 0x74: return run_nop(); - case 0x75: return run_adc(); - case 0x76: return run_ror(); - case 0x77: return run_rra(); - case 0x78: return run_sei(); - case 0x79: return run_adc(); - case 0x7A: return run_nop(); - case 0x7B: return run_rra(); - case 0x7C: return run_nop(); - case 0x7D: return run_adc(); - case 0x7E: return run_ror(); - case 0x7F: return run_rra(); - case 0x80: return run_nop(); - case 0x81: return run_sta(); - case 0x82: return run_nop(); - case 0x83: return run_sax(); - case 0x84: return run_sty(); - case 0x85: return run_sta(); - case 0x86: return run_stx(); - case 0x87: return run_sax(); - case 0x88: return run_dey(); - case 0x89: return run_nop(); - case 0x8A: return run_txa(); - case 0x8B: return run_xaa(); - case 0x8C: return run_sty(); - case 0x8D: return run_sta(); - case 0x8E: return run_stx(); - case 0x8F: return run_sax(); - case 0x90: return run_bcc(); - case 0x91: return run_sta(); - case 0x92: return run_stp(); - case 0x93: return run_ahx(); - case 0x94: return run_sty(); - case 0x95: return run_sta(); - case 0x96: return run_stx(); - case 0x97: return run_sax(); - case 0x98: return run_tya(); - case 0x99: return run_sta(); - case 0x9A: return run_txs(); - case 0x9B: return run_tas(); - case 0x9C: return run_shy(); - case 0x9D: return run_sta(); - case 0x9E: return run_shx(); - case 0x9F: return run_ahx(); - case 0xA0: return run_ldy(); - case 0xA1: return run_lda(); - case 0xA2: return run_ldx(); - case 0xA3: return run_lax(); - case 0xA4: return run_ldy(); - case 0xA5: return run_lda(); - case 0xA6: return run_ldx(); - case 0xA7: return run_lax(); - case 0xA8: return run_tay(); - case 0xA9: return run_lda(); - case 0xAA: return run_tax(); - case 0xAB: return run_lax(); - case 0xAC: return run_ldy(); - case 0xAD: return run_lda(); - case 0xAE: return run_ldx(); - case 0xAF: return run_lax(); - case 0xB0: return run_bcs(); - case 0xB1: return run_lda(); - case 0xB2: return run_stp(); - case 0xB3: return run_lax(); - case 0xB4: return run_ldy(); - case 0xB5: return run_lda(); - case 0xB6: return run_ldx(); - case 0xB7: return run_lax(); - case 0xB8: return run_clv(); - case 0xB9: return run_lda(); - case 0xBA: return run_tsx(); - case 0xBB: return run_las(); - case 0xBC: return run_ldy(); - case 0xBD: return run_lda(); - case 0xBE: return run_ldx(); - case 0xBF: return run_lax(); - case 0xC0: return run_cpy(); - case 0xC1: return run_cmp(); - case 0xC2: return run_nop(); - case 0xC3: return run_dcp(); - case 0xC4: return run_cpy(); - case 0xC5: return run_cmp(); - case 0xC6: return run_dec(); - case 0xC7: return run_dcp(); - case 0xC8: return run_iny(); - case 0xC9: return run_cmp(); - case 0xCA: return run_dex(); - case 0xCB: return run_axs(); - case 0xCC: return run_cpy(); - case 0xCD: return run_cmp(); - case 0xCE: return run_dec(); - case 0xCF: return run_dcp(); - case 0xD0: return run_bne(); - case 0xD1: return run_cmp(); - case 0xD2: return run_stp(); - case 0xD3: return run_dcp(); - case 0xD4: return run_nop(); - case 0xD5: return run_cmp(); - case 0xD6: return run_dec(); - case 0xD7: return run_dcp(); - case 0xD8: return run_cld(); - case 0xD9: return run_cmp(); - case 0xDA: return run_nop(); - case 0xDB: return run_dcp(); - case 0xDC: return run_nop(); - case 0xDD: return run_cmp(); - case 0xDE: return run_dec(); - case 0xDF: return run_dcp(); - case 0xE0: return run_cpx(); - case 0xE1: return run_sbc(); - case 0xE2: return run_nop(); - case 0xE3: return run_isc(); - case 0xE4: return run_cpx(); - case 0xE5: return run_sbc(); - case 0xE6: return run_inc(); - case 0xE7: return run_isc(); - case 0xE8: return run_inx(); - case 0xE9: return run_sbc(); - case 0xEA: return run_nop(); - case 0xEB: return run_sbc(); - case 0xEC: return run_cpx(); - case 0xED: return run_sbc(); - case 0xEE: return run_inc(); - case 0xEF: return run_isc(); - case 0xF0: return run_beq(); - case 0xF1: return run_sbc(); - case 0xF2: return run_stp(); - case 0xF3: return run_isc(); - case 0xF4: return run_nop(); - case 0xF5: return run_sbc(); - case 0xF6: return run_inc(); - case 0xF7: return run_isc(); - case 0xF8: return run_sed(); - case 0xF9: return run_sbc(); - case 0xFA: return run_nop(); - case 0xFB: return run_isc(); - case 0xFC: return run_nop(); - case 0xFD: return run_sbc(); - case 0xFE: return run_inc(); - case 0xFF: return run_isc(); - default: return status::error_unsupported_instruction; - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // Instructions - // ----------------------------------------------------------------------------------------------------------------- - - template - auto cpu::run_brk() -> status - { - // BRK: Break - fetch_operand(); - execute_interrupt(address{ 0xFFFE }); - return status::success; - } - - template - auto cpu::run_ora() -> status - { - // ORA: Logical Inclusive OR - auto operand = fetch_operand(); - eval_ora(operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - auto cpu::run_stp() -> status - { - return status::error_unsupported_instruction; - } - - template - auto cpu::run_slo() -> status - { - // SLO: ASL + ORA - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_asl(operand.read()); - operand.write(tmp); - eval_ora(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_nop() -> status - { - // NOP: NOP - auto operand = fetch_operand(); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - auto cpu::run_nop() -> status - { - // NOP: NOP - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_php() -> status - { - // PHP: Push Processor Status - eval_php(); - current_cycles_ += cycle_count::from_cpu(3); - return status::success; - } - - template - auto cpu::run_asl() -> status - { - // ASL: Arithmetic Shift Left - auto operand = fetch_operand(); - operand.write(eval_asl(operand.read())); - current_cycles_ += shift_cycle_count(); - return status::success; - } - - template - auto cpu::run_anc() -> status - { - // ANC: AND #i + copy N to C - auto operand = fetch_operand(); - eval_and(operand.read()); - registers_.p.set_c(registers_.p.get_n()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_bpl() -> status - { - // BPL: Break If Positive - branch(!registers_.p.get_n()); - return status::success; - } - - auto cpu::run_clc() -> status - { - // CLC: Clear Carry Flag - registers_.p.set_c(false); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_jsr() -> status - { - // JSR: Jump to Subroutine - static_assert(Mode == addressing_mode::absolute, "JSR only supports absolute addressing."); - - push_stack16(registers_.pc + 1); - - auto operand = fetch_operand(); - registers_.pc = operand.get_address().get_absolute(); - current_cycles_ += cycle_count::from_cpu(6); - return status::success; - } - - template - auto cpu::run_and() -> status - { - // AND: Logical AND - auto operand = fetch_operand(); - eval_and(operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_rla() -> status - { - // RLA: ROL + AND - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_rol(operand.read()); - operand.write(tmp); - eval_and(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_bit() -> status - { - // BIT: Bit Test - auto operand = fetch_operand(); - auto const arg = operand.read(); - auto const res = (registers_.a & arg) != 0; - registers_.p.set_z(res == 0); - registers_.p.set_v((arg & 0x40) != 0); - registers_.p.set_n((arg & 0x80) != 0); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_rol() -> status - { - // ROR: Rotate Left - auto operand = fetch_operand(); - auto const result = eval_rol(operand.read()); - operand.write(result); - current_cycles_ += shift_cycle_count(); - return status::success; - } - - auto cpu::run_plp() -> status - { - // PLP: Pull Processor Status - eval_plp(); - current_cycles_ += cycle_count::from_cpu(4); - return status::success; - } - - template - auto cpu::run_bmi() -> status - { - branch(registers_.p.get_n()); - return status::success; - } - - auto cpu::run_sec() -> status - { - // SEC: Set Carry Flag - registers_.p.set_c(true); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_rti() -> status - { - // RTI: Return From Interrupt - eval_plp(); - registers_.pc = pop_stack16(); - current_cycles_ += cycle_count::from_cpu(6); - return status::success; - } - - template - auto cpu::run_eor() -> status - { - // EOR: Exclusive OR - auto operand = fetch_operand(); - eval_eor(operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_sre() -> status - { - // SRE: LSR + EOR - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_lsr(operand.read()); - operand.write(tmp); - eval_eor(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_lsr() -> status - { - // LSR: Logical Shift Right - auto operand = fetch_operand(); - auto const old_val = operand.read(); - auto const new_val = static_cast(old_val >> 1); - operand.write(new_val); - registers_.p.set_c((old_val & 0x1) != 0); - update_zn(new_val); - current_cycles_ += shift_cycle_count(); - return status::success; - } - - template - auto cpu::run_alr() -> status - { - // ALR: AND #i + LSR A - auto operand = fetch_operand(); - eval_and(operand.read()); - registers_.a = eval_lsr(registers_.a); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - auto cpu::run_pha() -> status - { - // PHA: Push Accumulator - push_stack8(registers_.a); - current_cycles_ += cycle_count::from_cpu(3); - return status::success; - } - - template - auto cpu::run_jmp() -> status - { - // JMP: Jump - auto operand = fetch_operand(); - registers_.pc = operand.get_address().get_absolute(); - current_cycles_ += jump_cycle_count(); - return status::success; - } - - template - auto cpu::run_bvc() -> status - { - // BVC: Branch If Overflow Clear - branch(!registers_.p.get_v()); - return status::success; - } - - auto cpu::run_cli() -> status - { - // CLI: Clear Interrupt Disable - registers_.p.set_i(false); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_rts() -> status - { - // RTS: Return from Subroutine - auto const addr = address{ pop_stack16() } + 1; - registers_.pc = addr.get_absolute(); - current_cycles_ += cycle_count::from_cpu(6); - return status::success; - } - - template - auto cpu::run_adc() -> status - { - // ADC: Add With Carry - auto operand = fetch_operand(); - eval_adc(operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_rra() -> status - { - // RRA: ROR + ADC - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_ror(operand.read()); - operand.write(tmp); - eval_adc(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_ror() -> status - { - // ROR: Rotate Right - auto operand = fetch_operand(); - auto const result = eval_ror(operand.read()); - operand.write(result); - current_cycles_ += shift_cycle_count(); - return status::success; - } - - template - auto cpu::run_arr() -> status - { - // ARR: AND #i + ROR A but with different flags - // TODO - return status::error_unsupported_instruction; - } - - auto cpu::run_pla() -> status - { - // PLA: Pull Accumulator - registers_.a = pop_stack8(); - update_zn(registers_.a); - current_cycles_ += cycle_count::from_cpu(4); - return status::success; - } - - template - auto cpu::run_bvs() -> status - { - // BVS: Branch If Overflow Set - branch(registers_.p.get_v()); - return status::success; - } - - auto cpu::run_sei() -> status - { - // SEI: Set Interrupt Disable - registers_.p.set_i(true); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_sta() -> status - { - // STA: Store Accumulator - auto operand = fetch_operand(force_page_crossing::yes); - operand.write(registers_.a); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_ahx() -> status - { - // AHX: ??? - // TODO - return status::error_unsupported_instruction; - } - - template - auto cpu::run_sax() -> status - { - // SAX: AND A X - auto operand = fetch_operand(); - auto const result = static_cast(registers_.a & registers_.x); - operand.write(result); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_sty() -> status - { - // STX: Store Y - auto operand = fetch_operand(); - operand.write(registers_.y); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_stx() -> status - { - // STX: Store X - auto operand = fetch_operand(); - operand.write(registers_.x); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - auto cpu::run_dey() -> status - { - // DEY: Decrement Y Register - registers_.y -= 1; - update_zn(registers_.y); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_txa() -> status - { - // TXA: Transfer X to Accumulator - registers_.a = registers_.x; - update_zn(registers_.a); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_xaa() -> status - { - // XAA: Depends on analog behavior. - run_stp(); - return status::success; - } - - template - auto cpu::run_bcc() -> status - { - // BCC: Branch If Carry Clear - branch(!registers_.p.get_c()); - return status::success; - } - - auto cpu::run_tya() -> status - { - // TYA: Transfer Y to Accumulator - registers_.a = registers_.y; - update_zn(registers_.a); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_txs() -> status - { - // TXS: Transfer X to Stack Pointer - registers_.sp = registers_.x; - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_tas() -> status - { - // TAS: ??? - // TODO - return status::error_unsupported_instruction; - } - - template - auto cpu::run_shy() -> status - { - // SHY: ??? - // TODO - return status::error_unsupported_instruction; - } - - template - auto cpu::run_shx() -> status - { - // SHY: ??? - // TODO - return status::error_unsupported_instruction; - } - - template - auto cpu::run_ldy() -> status - { - // LDY: Load Y Register - auto operand = fetch_operand(); - registers_.y = operand.read(); - update_zn(registers_.y); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_lda() -> status - { - // LDA: Load Accumulator - auto operand = fetch_operand(); - registers_.a = operand.read(); - update_zn(registers_.a); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_ldx() -> status - { - // LDX: Load X Register - auto operand = fetch_operand(); - registers_.x = operand.read(); - update_zn(registers_.x); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_lax() -> status - { - // LAX: LDA + TAX - auto operand = fetch_operand(); - registers_.x = registers_.a = operand.read(); - update_zn(registers_.a); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - auto cpu::run_tay() -> status - { - // TAY: Transfer Accumulator to Y - registers_.y = registers_.a; - update_zn(registers_.y); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_tax() -> status - { - // TAX: Transfer Accumulator to X - registers_.x = registers_.a; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_bcs() -> status - { - // BCS: Branch If Carry Set - branch(registers_.p.get_c()); - return status::success; - } - - auto cpu::run_clv() -> status - { - // CLV: Clear Overflow Flag - registers_.p.set_v(false); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_tsx() -> status - { - // TSX: Transfer Stack Pointer to X - registers_.x = registers_.sp; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_las() -> status - { - // LAS: ??? - // TODO - return status::error_unsupported_instruction; - } - - template - auto cpu::run_cpy() -> status - { - // CPY: Compare Y Register - auto operand = fetch_operand(); - eval_cmp(registers_.y, operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_axs() -> status - { - // AXS: Set X to ((A AND X) - #value without borrow), updates NZC - // TODO - return status::error_unsupported_instruction; - } - - template - auto cpu::run_cmp() -> status - { - // CMP: Compare - auto operand = fetch_operand(); - eval_cmp(registers_.a, operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_dcp() -> status - { - // DCP: DEC + CMP - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = static_cast(operand.read() - 1); - operand.write(tmp); - eval_cmp(registers_.a, tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_dec() -> status - { - // DEC: Decrement Memory - auto operand = fetch_operand(); - auto const old_val = read8(operand.get_address()); - auto const new_val = static_cast(old_val - 1); - write8(operand.get_address(), new_val); - update_zn(new_val); - current_cycles_ += inc_dec_cycle_count(); - return status::success; - } - - auto cpu::run_iny() -> status - { - // INY: Increment Y - registers_.y += 1; - update_zn(registers_.y); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - auto cpu::run_dex() -> status - { - // DEX: Decrement X Register - registers_.x -= 1; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_bne() -> status - { - // BNE: Branch If Not Equal - branch(!registers_.p.get_z()); - return status::success; - } - - auto cpu::run_cld() -> status - { - // CLD: Clear Decimal Mode - registers_.p.set_d(false); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_cpx() -> status - { - // CPX: Compare X Register - auto operand = fetch_operand(); - eval_cmp(registers_.x, operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_sbc() -> status - { - // SBC: Subtract With Carry - auto operand = fetch_operand(); - eval_adc(~operand.read()); - current_cycles_ += operand.get_cycles(); - return status::success; - } - - template - auto cpu::run_isc() -> status - { - // ISC: INC + SBC - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = static_cast(operand.read() + 1); - operand.write(tmp); - eval_adc(~tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_inc() -> status - { - // INC: Increment Memory - auto operand = fetch_operand(); - auto const old_val = read8(operand.get_address()); - auto const new_val = static_cast(old_val + 1); - write8(operand.get_address(), new_val); - update_zn(new_val); - current_cycles_ += inc_dec_cycle_count(); - return status::success; - } - - auto cpu::run_inx() -> status - { - // INX: Increment X - registers_.x += 1; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - template - auto cpu::run_beq() -> status - { - // BEQ: Branch If Equal - branch(registers_.p.get_z()); - return status::success; - } - - auto cpu::run_sed() -> status - { - // SED: Set Decimal Flag - registers_.p.set_d(true); - current_cycles_ += cycle_count::from_cpu(2); - return status::success; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Helpers - // ----------------------------------------------------------------------------------------------------------------- - - auto cpu::advance_pc8() -> std::uint8_t - { - auto const val = read8(address{ registers_.pc }); - registers_.pc += 1; - return val; - } - - auto cpu::advance_pc16() -> std::uint16_t - { - auto const val = read16(address{ registers_.pc }); - registers_.pc += 2; - return val; - } - - auto cpu::push_stack8(std::uint8_t const value) -> void - { - write8(stack_offset + registers_.sp, value); - registers_.sp -= 1; - } - - auto cpu::push_stack16(std::uint16_t const value) -> void - { - auto const low = static_cast(value >> 0 & 0xFF); - auto const high = static_cast(value >> 8 & 0xFF); - push_stack8(high); - push_stack8(low); - } - - auto cpu::pop_stack8() -> std::uint8_t - { - registers_.sp += 1; - return read8(stack_offset + registers_.sp); - } - - auto cpu::pop_stack16() -> std::uint16_t - { - auto const low = pop_stack8(); - auto const high = pop_stack8(); - return static_cast((high << 8) | (low << 0)); - } - - auto cpu::update_zn(std::uint8_t const value) -> void - { - registers_.p.set_z(value == 0); - registers_.p.set_n((value & 0x80) != 0); - } - - template - auto cpu::branch(bool const condition) -> void - { - static_assert(Mode == addressing_mode::relative, "Branch should only be used with relative operands."); - - auto operand = fetch_operand(); - if (condition) - { - registers_.pc = operand.get_address().get_absolute(); - current_cycles_ += operand.get_cycles(); - } - else - { - current_cycles_ += cycle_count::from_cpu(2); - } - } - - auto cpu::execute_interrupt(address const vector) -> void - { - push_stack16(registers_.pc); - eval_php(); - registers_.pc = read16(vector); - registers_.p.set_i(true); - current_cycles_ += cycle_count::from_cpu(7); - } - - auto cpu::eval_ror(std::uint8_t const arg) -> std::uint8_t - { - auto const res = static_cast(((registers_.p.get_c() ? 1 :0) << 7) | (arg >> 1)); - registers_.p.set_c((arg & 0x1) != 0); - update_zn(res); - return res; - } - - auto cpu::eval_rol(std::uint8_t const arg) -> std::uint8_t - { - auto const res = static_cast((arg << 1) | (registers_.p.get_c() ? 1 : 0)); - registers_.p.set_c((arg & 0x80) != 0); - update_zn(res); - return res; - } - - auto cpu::eval_asl(std::uint8_t const arg) -> std::uint8_t - { - auto const new_val = static_cast(arg << 1); - registers_.p.set_c((arg & 0x80) != 0); - update_zn(new_val); - return new_val; - } - - auto cpu::eval_lsr(std::uint8_t const arg) -> std::uint8_t - { - auto const new_val = static_cast(arg >> 1); - registers_.p.set_c((arg & 0x01) != 0); - update_zn(new_val); - return new_val; - } - - auto cpu::eval_adc(std::uint8_t const arg) -> void - { - auto const old_val = registers_.a; - auto const tmp = old_val + arg + (registers_.p.get_c() ? 1 : 0); - auto const new_val = static_cast(tmp); - - registers_.a = new_val; - registers_.p.set_c(tmp > 0xFF); - registers_.p.set_v(((old_val ^ arg) & 0x80) == 0 && ((old_val ^ new_val) & 0x80) != 0); - update_zn(registers_.a); - } - - auto cpu::eval_and(std::uint8_t const arg) -> void - { - registers_.a &= arg; - update_zn(registers_.a); - } - - auto cpu::eval_ora(std::uint8_t const arg) -> void - { - registers_.a |= arg; - update_zn(registers_.a); - } - - auto cpu::eval_eor(std::uint8_t const arg) -> void - { - registers_.a ^= arg; - update_zn(registers_.a); - } - - auto cpu::eval_cmp(std::uint8_t const a, std::uint8_t const b) -> void - { - update_zn(a - b); - registers_.p.set_c(a >= b); - } - - auto cpu::eval_plp() -> void - { - // Ignore bits 4 and 5 - registers_.p.value = - (pop_stack8() & 0b11001111) | - (registers_.p.value & 0b00110000); - } - - auto cpu::eval_php() -> void - { - // Always set bits 4 and 5. - push_stack8(registers_.p.value | 0b00110000); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Operands - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/CPU_addressing_modes - // - - template - auto cpu::fetch_operand(force_page_crossing const force_page_crossing) -> operand - { - auto addr = address{}; - auto cycles = cycle_count{}; - - switch (Mode) - { - case addressing_mode::immediate: static_assert(Mode != addressing_mode::immediate); - case addressing_mode::accumulator: static_assert(Mode != addressing_mode::accumulator); - case addressing_mode::zero_page: - { - addr = address{ advance_pc8() }; - cycles = cycle_count::from_cpu(3); - break; - } - case addressing_mode::absolute: - { - addr = address{ advance_pc16() }; - cycles = cycle_count::from_cpu(4); - break; - } - case addressing_mode::relative: - { - auto const offset = static_cast(advance_pc8()); - auto const base = address{ registers_.pc }; - addr = address{ static_cast(registers_.pc + offset) }; - auto const page_crossing = - base.get_page() != addr.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 4 : 3); - break; - } - case addressing_mode::indirect: - { - auto const arg = address{ advance_pc16() }; - // Account for buggy indirect read: https://www.nesdev.org/wiki/Errata - addr = arg.get_offset() == 0xFF - ? address{ read8(address{ arg.get_page(), 0x00 }), read8(arg) } - : address{ read16(arg) }; - cycles = cycle_count::from_cpu(6); - break; - } - case addressing_mode::zero_page_indexed_x: - { - // PEEK((arg + X) % 256) - addr = address{ 0x00, static_cast(advance_pc8() + registers_.x) }; - cycles = cycle_count::from_cpu(4); - break; - } - case addressing_mode::zero_page_indexed_y: - { - // PEEK((arg + Y) % 256) - addr = address{ 0x00, static_cast(advance_pc8() + registers_.y) }; - cycles = cycle_count::from_cpu(4); - break; - } - case addressing_mode::absolute_indexed_x: - { - // PEEK(arg + X) - auto const arg = address{ advance_pc16() }; - addr = arg + registers_.x; - auto const page_crossing = - addr.get_page() != arg.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 5 : 4); - break; - } - case addressing_mode::absolute_indexed_y: - { - // PEEK(arg + Y) - auto const arg = address{ advance_pc16() }; - addr = arg + registers_.y; - auto const page_crossing = - addr.get_page() != arg.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 5 : 4); - break; - } - case addressing_mode::indexed_indirect: - { - // PEEK(PEEK((arg + X) % 256) + PEEK((arg + X + 1) % 256) * 256) - auto const arg = advance_pc8(); - auto const page = read8(address{ 0x00, static_cast(arg + registers_.x + 1) }); - auto const offset = read8(address{ 0x00, static_cast(arg + registers_.x) }); - addr = address{ page, offset }; - cycles = cycle_count::from_cpu(6); - break; - } - case addressing_mode::indirect_indexed: - { - // PEEK(PEEK(arg) + PEEK((arg + 1) % 256) * 256 + Y) - auto const arg = advance_pc8(); - auto const page = read8(address{ 0x00, static_cast(arg + 1) }); - auto const offset = read8(address{ 0x00, static_cast(arg) }); - auto const base = address{ page, offset }; - addr = base + registers_.y; - auto const page_crossing = - addr.get_page() != base.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 6 : 5); - break; - } - } - - return operand{ *this, addr, cycles }; - } - - template<> - auto cpu::fetch_operand(force_page_crossing) - -> operand - { - return operand{ *this }; - } - - template<> - auto cpu::fetch_operand(force_page_crossing) - -> operand - { - return operand{ advance_pc8() }; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Memory Access - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/CPU_memory_map - // - - auto cpu::read8(address const addr) -> std::uint8_t - { - if (addr <= address{ 0x1FFF }) { return ram_[addr.get_absolute() % ram_size]; } - if (addr <= address{ 0x3FFF }) - { -#ifdef NES_ENABLE_PPU - switch (addr.get_absolute() % 8) - { - case 0: return ppu_.read_latch(); - case 1: return ppu_.read_latch(); - case 2: return ppu_.read_ppustatus(); - case 3: return ppu_.read_latch(); - case 4: return ppu_.read_oamdata(); - case 5: return ppu_.read_latch(); - case 6: return ppu_.read_latch(); - case 7: return ppu_.read_ppudata(); - default: return 0x0; - } -#else - return 0x0; -#endif - } - if (addr <= address{ 0x4013 }) { return 0x0; } // TODO: APU registers -#ifdef NES_ENABLE_PPU - if (addr == address{ 0x4014 }) { return ppu_.read_latch(); } -#else - if (addr == address{ 0x4014 }) { return 0x0; } -#endif - if (addr == address{ 0x4015 }) { return 0x0; } // TODO: APU registers - if (addr == address{ 0x4016 }) { return controller_1_.read(); } - if (addr == address{ 0x4017 }) { return controller_2_.read(); } - if (addr <= address{ 0x401F }) { return 0x0; } - return cartridge_.get_mapper().read_cpu(addr, cartridge_); - } - - auto cpu::read16(address const addr) -> std::uint16_t - { - auto const low = read8(addr + 0); - auto const high = read8(addr + 1); - return static_cast((high << 8) | (low << 0)); - } - - auto cpu::write8(address const addr, std::uint8_t const value) -> void - { - if (addr <= address{ 0x1FFF }) { ram_[addr.get_absolute() % ram_size] = value; return; } - if (addr <= address{ 0x3FFF }) - { -#ifdef NES_ENABLE_PPU - switch (addr.get_absolute() % 8) - { - case 0: ppu_.write_ppuctrl(value); return; - case 1: ppu_.write_ppumask(value); return; - case 2: ppu_.write_latch(value); return; - case 3: ppu_.write_oamaddr(value); return; - case 4: ppu_.write_oamdata(value); return; - case 5: ppu_.write_ppuscroll(value); return; - case 6: ppu_.write_ppuaddr(value); return; - case 7: ppu_.write_ppudata(value); return; - default: return; - } -#else - return; -#endif - } - if (addr <= address{ 0x4013 }) { return; } // TODO: APU registers -#ifdef NES_ENABLE_PPU - if (addr == address{ 0x4014 }) { ppu_.write_oamdma(value); return; } -#else - if (addr == address{ 0x4014 }) { return; } -#endif - if (addr == address{ 0x4015 }) { return; } // TODO: APU registers - if (addr == address{ 0x4016 }) { controller_1_.write(value); return; } - if (addr == address{ 0x4017 }) { controller_2_.write(value); return; } - if (addr <= address{ 0x401F }) { return; } - cartridge_.get_mapper().write_cpu(addr, value, cartridge_); - } - - auto cpu::write16(address const addr, std::uint16_t const value) -> void - { - auto const low = static_cast(value >> 0 & 0xFF); - auto const high = static_cast(value >> 8 & 0xFF); - write8(addr + 0, low); - write8(addr + 1, high); - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/cpu.hh b/lib/nes/cpu.hh deleted file mode 100644 index d5bc032..0000000 --- a/lib/nes/cpu.hh +++ /dev/null @@ -1,300 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" -#include "nes/util/address.hh" -#include "nes/util/status.hh" - -namespace nes -{ - class ppu; - class controller; - class cartridge; - struct snapshot; - - class cpu - { - static constexpr auto ram_size = std::size_t{ 0x800 }; - static constexpr auto stack_offset = address{ 0x100 }; - - cycle_count current_cycles_; - ppu& ppu_; - cartridge& cartridge_; - controller& controller_1_; - controller& controller_2_; - std::uint8_t ram_[ram_size]{}; - bool nmi_pending_{ false }; - - // Registers - struct - { - std::uint16_t pc{ 0 }; - std::uint8_t sp{ 0xFD }; - std::uint8_t a{ 0 }; - std::uint8_t x{ 0 }; - std::uint8_t y{ 0 }; - struct - { - auto get_c() const -> bool { return value & 0b00000001; } // carry - auto get_z() const -> bool { return value & 0b00000010; } // zero - auto get_i() const -> bool { return value & 0b00000100; } // interrupt inhibit - auto get_d() const -> bool { return value & 0b00001000; } // decimal - auto get_b() const -> bool { return value & 0b00010000; } // break - auto get_v() const -> bool { return value & 0b01000000; } // overflow - auto get_n() const -> bool { return value & 0b10000000; } // negative - - auto set_c(bool const v) -> void { value = (value & ~0b00000001) | (v ? 0b00000001 : 0); } - auto set_z(bool const v) -> void { value = (value & ~0b00000010) | (v ? 0b00000010 : 0); } - auto set_i(bool const v) -> void { value = (value & ~0b00000100) | (v ? 0b00000100 : 0); } - auto set_d(bool const v) -> void { value = (value & ~0b00001000) | (v ? 0b00001000 : 0); } - auto set_b(bool const v) -> void { value = (value & ~0b00010000) | (v ? 0b00010000 : 0); } - auto set_v(bool const v) -> void { value = (value & ~0b01000000) | (v ? 0b01000000 : 0); } - auto set_n(bool const v) -> void { value = (value & ~0b10000000) | (v ? 0b10000000 : 0); } - - std::uint8_t value{ 0b00100100 }; - } p{}; - } registers_{}; - - enum class addressing_mode - { - accumulator, // A - immediate, // #v - zero_page, // d - absolute, // a - relative, // *+d - indirect, // (a) - zero_page_indexed_x, // d,x - zero_page_indexed_y, // d,y - absolute_indexed_x, // a,x - absolute_indexed_y, // a,y - indexed_indirect, // (d,x) - indirect_indexed, // (d),y - }; - - enum class force_page_crossing - { - no, - yes, - }; - - template - class operand - { - cpu& cpu_; - address address_; - cycle_count cycles_; - - public: - explicit operand(cpu& cpu, address const address, cycle_count const cycles) - : cpu_{ cpu } - , address_{ address } - , cycles_{ cycles } - { - } - - auto read() -> std::uint8_t { return cpu_.read8(address_); } - auto write(std::uint8_t const value) -> void { cpu_.write8(address_, value); } - auto get_address() -> address { return address_; } - auto get_cycles() -> cycle_count { return cycles_; } - }; - - template<> - class operand - { - cpu& cpu_; - - public: - explicit operand(cpu& cpu) - : cpu_{ cpu } - { - } - - auto read() -> std::uint8_t { return cpu_.registers_.a; } - auto write(std::uint8_t const value) -> void { cpu_.registers_.a = value; } - auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); } - }; - - template<> - class operand - { - std::uint8_t value_{ 0 }; - - public: - explicit operand(std::uint8_t const value) - : value_{ value } - { - } - - auto read() -> std::uint8_t { return value_; } - auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); } - }; - - public: - explicit cpu(ppu&, cartridge&, controller& controller_1, controller& controller_2); - - cpu(cpu const&) = delete; - cpu(cpu&&) = delete; - auto operator=(cpu const&) -> cpu& = delete; - auto operator=(cpu&&) -> cpu& = delete; - - auto get_cycles() const -> cycle_count { return current_cycles_; } - auto build_snapshot(snapshot&) -> void; - auto stall_cycles(cycle_count) -> void; - auto trigger_nmi() -> void; - auto step() -> status; - auto is_nmi_pending() const -> bool { return nmi_pending_; } // XXX: Debugging - - // Memory access - - auto read8(address) -> std::uint8_t; - auto read16(address) -> std::uint16_t; - auto write8(address, std::uint8_t) -> void; - auto write16(address, std::uint16_t) -> void; - - private: - // Instructions - -#define DEFINE_SIMPLE_INSTRUCTION(name) \ - auto run_##name() -> status; -#define DEFINE_OPERAND_INSTRUCTION(name) \ - template auto run_##name() -> status; - - DEFINE_OPERAND_INSTRUCTION(brk) - DEFINE_OPERAND_INSTRUCTION(ora) - DEFINE_SIMPLE_INSTRUCTION(stp) - DEFINE_OPERAND_INSTRUCTION(slo) - DEFINE_OPERAND_INSTRUCTION(nop) - DEFINE_SIMPLE_INSTRUCTION(nop) - DEFINE_SIMPLE_INSTRUCTION(php) - DEFINE_OPERAND_INSTRUCTION(asl) - DEFINE_OPERAND_INSTRUCTION(anc) - DEFINE_OPERAND_INSTRUCTION(bpl) - DEFINE_SIMPLE_INSTRUCTION(clc) - DEFINE_OPERAND_INSTRUCTION(jsr) - DEFINE_OPERAND_INSTRUCTION(and) - DEFINE_OPERAND_INSTRUCTION(rla) - DEFINE_OPERAND_INSTRUCTION(bit) - DEFINE_OPERAND_INSTRUCTION(rol) - DEFINE_SIMPLE_INSTRUCTION(plp) - DEFINE_OPERAND_INSTRUCTION(bmi) - DEFINE_SIMPLE_INSTRUCTION(sec) - DEFINE_SIMPLE_INSTRUCTION(rti) - DEFINE_OPERAND_INSTRUCTION(eor) - DEFINE_OPERAND_INSTRUCTION(sre) - DEFINE_OPERAND_INSTRUCTION(lsr) - DEFINE_OPERAND_INSTRUCTION(alr) - DEFINE_SIMPLE_INSTRUCTION(pha) - DEFINE_OPERAND_INSTRUCTION(jmp) - DEFINE_OPERAND_INSTRUCTION(bvc) - DEFINE_SIMPLE_INSTRUCTION(cli) - DEFINE_SIMPLE_INSTRUCTION(rts) - DEFINE_OPERAND_INSTRUCTION(adc) - DEFINE_OPERAND_INSTRUCTION(rra) - DEFINE_OPERAND_INSTRUCTION(ror) - DEFINE_OPERAND_INSTRUCTION(arr) - DEFINE_SIMPLE_INSTRUCTION(pla) - DEFINE_OPERAND_INSTRUCTION(bvs) - DEFINE_SIMPLE_INSTRUCTION(sei) - DEFINE_OPERAND_INSTRUCTION(sta) - DEFINE_OPERAND_INSTRUCTION(ahx) - DEFINE_OPERAND_INSTRUCTION(sax) - DEFINE_OPERAND_INSTRUCTION(sty) - DEFINE_OPERAND_INSTRUCTION(stx) - DEFINE_SIMPLE_INSTRUCTION(dey) - DEFINE_SIMPLE_INSTRUCTION(txa) - DEFINE_OPERAND_INSTRUCTION(xaa) - DEFINE_OPERAND_INSTRUCTION(bcc) - DEFINE_SIMPLE_INSTRUCTION(tya) - DEFINE_SIMPLE_INSTRUCTION(txs) - DEFINE_OPERAND_INSTRUCTION(tas) - DEFINE_OPERAND_INSTRUCTION(shy) - DEFINE_OPERAND_INSTRUCTION(shx) - DEFINE_OPERAND_INSTRUCTION(ldy) - DEFINE_OPERAND_INSTRUCTION(lda) - DEFINE_OPERAND_INSTRUCTION(ldx) - DEFINE_OPERAND_INSTRUCTION(lax) - DEFINE_SIMPLE_INSTRUCTION(tay) - DEFINE_SIMPLE_INSTRUCTION(tax) - DEFINE_OPERAND_INSTRUCTION(bcs) - DEFINE_SIMPLE_INSTRUCTION(clv) - DEFINE_SIMPLE_INSTRUCTION(tsx) - DEFINE_OPERAND_INSTRUCTION(las) - DEFINE_OPERAND_INSTRUCTION(cpy) - DEFINE_OPERAND_INSTRUCTION(axs) - DEFINE_OPERAND_INSTRUCTION(cmp) - DEFINE_OPERAND_INSTRUCTION(dcp) - DEFINE_OPERAND_INSTRUCTION(dec) - DEFINE_SIMPLE_INSTRUCTION(iny) - DEFINE_SIMPLE_INSTRUCTION(dex) - DEFINE_OPERAND_INSTRUCTION(bne) - DEFINE_SIMPLE_INSTRUCTION(cld) - DEFINE_OPERAND_INSTRUCTION(cpx) - DEFINE_OPERAND_INSTRUCTION(sbc) - DEFINE_OPERAND_INSTRUCTION(isc) - DEFINE_OPERAND_INSTRUCTION(inc) - DEFINE_SIMPLE_INSTRUCTION(inx) - DEFINE_OPERAND_INSTRUCTION(beq) - DEFINE_SIMPLE_INSTRUCTION(sed) - -#undef DEFINE_SIMPLE_INSTRUCTION -#undef DEFINE_OPERAND_INSTRUCTION - - // Helpers - - auto advance_pc8() -> std::uint8_t; - auto advance_pc16() -> std::uint16_t; - auto push_stack8(std::uint8_t) -> void; - auto push_stack16(std::uint16_t) -> void; - auto pop_stack8() -> std::uint8_t; - auto pop_stack16() -> std::uint16_t; - auto update_zn(std::uint8_t value) -> void; - template - auto branch(bool condition) -> void; - auto execute_interrupt(address) -> void; - - auto eval_ror(std::uint8_t arg) -> std::uint8_t; - auto eval_rol(std::uint8_t arg) -> std::uint8_t; - auto eval_asl(std::uint8_t arg) -> std::uint8_t; - auto eval_lsr(std::uint8_t arg) -> std::uint8_t; - auto eval_adc(std::uint8_t arg) -> void; - auto eval_and(std::uint8_t arg) -> void; - auto eval_ora(std::uint8_t arg) -> void; - auto eval_eor(std::uint8_t arg) -> void; - auto eval_cmp(std::uint8_t a, std::uint8_t b) -> void; - auto eval_plp() -> void; - auto eval_php() -> void; - - // Operands - -#define DEFINE_CYCLE_COUNT(name) \ - template static auto name##_cycle_count() -> cycle_count; -#define DEFINE_CYCLE_COUNT_INSTANCE(name, mode, value) \ - template<> auto name##_cycle_count() -> cycle_count { return cycle_count::from_cpu(value); } - - DEFINE_CYCLE_COUNT(jump) - DEFINE_CYCLE_COUNT_INSTANCE(jump, absolute, 3) - DEFINE_CYCLE_COUNT_INSTANCE(jump, indirect, 5) - - DEFINE_CYCLE_COUNT(shift) - DEFINE_CYCLE_COUNT_INSTANCE(shift, accumulator, 2) - DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page, 5) - DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page_indexed_x, 6) - DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute, 6) - DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute_indexed_x, 7) - - DEFINE_CYCLE_COUNT(inc_dec) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page, 5) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page_indexed_x, 6) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute, 6) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute_indexed_x, 7) - -#undef DEFINE_CYCLE_COUNT -#undef DEFINE_CYCLE_COUNT_INSTANCE - - template - auto fetch_operand(force_page_crossing = force_page_crossing::no) -> operand; - template<> - auto fetch_operand(force_page_crossing) -> operand; - template<> - auto fetch_operand(force_page_crossing) -> operand; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/mapper.cc b/lib/nes/mapper.cc deleted file mode 100644 index 08af1ae..0000000 --- a/lib/nes/mapper.cc +++ /dev/null @@ -1,172 +0,0 @@ -#include "nes/mapper.hh" -#include "nes/cartridge.hh" - -namespace nes -{ - namespace - { - // ------------------------------------------------------------------------------------------------------------- - // NROM - // ------------------------------------------------------------------------------------------------------------- - - class mapper_nrom final : public mapper - { - public: - explicit mapper_nrom() = default; - - auto validate(cartridge& cartridge) -> status override - { - if (cartridge.get_ram_length() != 0x2000) - { - return status::error_invalid_ines_data; - } - - if (cartridge.get_prg_rom_length() != 0x4000 && cartridge.get_prg_rom_length() != 0x8000) - { - return status::error_invalid_ines_data; - } - - if (cartridge.get_chr_rom_length() != 0x2000) - { - return status::error_invalid_ines_data; - } - - return status::success; - } - - auto read_cpu(address const addr, cartridge& cartridge) -> std::uint8_t override - { - if (addr <= address{ 0x5FFF }) - { - return 0x0; - } - if (addr <= address{ 0x7FFF }) - { - return cartridge.get_ram()[addr.get_absolute() - 0x6000]; - } - if (addr <= address{ 0xBFFF }) - { - auto const rel = addr.get_absolute() - 0x8000u; - return cartridge.get_prg_rom()[rel]; - } - if (addr <= address{ 0xFFFF }) - { - auto rel = addr.get_absolute() - 0xC000u; - if (cartridge.get_prg_rom_length() > 0x4000) { rel += 0x4000; } - return cartridge.get_prg_rom()[rel]; - } - - return 0x0; - } - - auto write_cpu(address const addr, std::uint8_t const value, cartridge& cartridge) -> void override - { - if (addr <= address{ 0x5FFF }) - { - return; - } - if (addr <= address{ 0x7FFF }) - { - cartridge.get_ram_mut()[addr.get_absolute() - 0x6000] = value; - return; - } - } - - auto read_ppu(address const addr, cartridge& cartridge, std::uint8_t const* vram) -> std::uint8_t override - { - if (addr <= address{ 0x1FFF }) - { - auto const rel = addr.get_absolute(); - return cartridge.get_chr_rom()[rel]; - } - if (addr <= address{ 0x3EFF }) - { - return vram[mirrored_vram_address(addr, cartridge)]; - } - - return 0x0; - } - - auto write_ppu(address const addr, std::uint8_t const value, cartridge& cartridge, std::uint8_t* vram) -> void override - { - if (addr <= address{ 0x1FFF }) - { - return; - } - if (addr <= address{ 0x3EFF }) - { - vram[mirrored_vram_address(addr, cartridge)] = value; - return; - } - } - - private: - static auto mirrored_vram_address(address const addr, cartridge const& cartridge) -> unsigned - { - auto const rel = addr.get_absolute() % 0x1000u; - - switch (cartridge.get_name_table_arrangement()) - { - case name_table_arrangement::horizontal: - // $2000 = $2400, $2800 = $2c00 - if (rel <= 0x3FF) { return 0x000u + (rel % 0x400u); } - if (rel <= 0x7FF) { return 0x000u + (rel % 0x400u); } - if (rel <= 0xBFF) { return 0x400u + (rel % 0x400u); } - if (rel <= 0xFFF) { return 0x400u + (rel % 0x400u); } - break; - case name_table_arrangement::vertical: - // $2000 = $2800, $2400 = $2c00 - if (rel <= 0x3FF) { return 0x000u + (rel % 0x400u); } - if (rel <= 0x7FF) { return 0x400u + (rel % 0x400u); } - if (rel <= 0xBFF) { return 0x000u + (rel % 0x400u); } - if (rel <= 0xFFF) { return 0x400u + (rel % 0x400u); } - break; - } - - return 0; - } - }; - - // ------------------------------------------------------------------------------------------------------------- - // Invalid - // ------------------------------------------------------------------------------------------------------------- - - class mapper_invalid final : public mapper - { - public: - explicit mapper_invalid() = default; - - auto validate(cartridge&) -> status override { return status::error_unsupported_mapper; } - auto read_cpu(address, cartridge&) -> std::uint8_t override { return 0; } - auto write_cpu(address, std::uint8_t, cartridge&) -> void override {} - auto read_ppu(address, cartridge&, std::uint8_t const*) -> std::uint8_t override { return 0; } - auto write_ppu(address, std::uint8_t, cartridge&, std::uint8_t*) -> void override {} - }; - } // namespace - - // ----------------------------------------------------------------------------------------------------------------- - // Instances - // ----------------------------------------------------------------------------------------------------------------- - - auto mapper::invalid() -> mapper& - { - static auto instance = mapper_invalid{}; - return instance; - } - - auto mapper::get(std::uint8_t const number) -> mapper& - { - switch (number) - { - case 0x00: - { - static auto instance = mapper_nrom{}; - return instance; - } - default: - { - return invalid(); - } - } - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/mapper.hh b/lib/nes/mapper.hh deleted file mode 100644 index 7e41b23..0000000 --- a/lib/nes/mapper.hh +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "nes/util/address.hh" -#include "nes/util/status.hh" - -namespace nes -{ - class cartridge; - - class mapper - { - public: - static auto invalid() -> mapper&; - static auto get(std::uint8_t number) -> mapper&; - - virtual ~mapper() = default; - - mapper(mapper const&) = delete; - mapper(mapper&&) = delete; - auto operator=(mapper const&) -> mapper& = delete; - auto operator=(mapper&&) -> mapper& = delete; - - virtual auto validate(cartridge&) -> status = 0; - virtual auto read_cpu(address, cartridge&) -> std::uint8_t = 0; - virtual auto write_cpu(address, std::uint8_t, cartridge&) -> void = 0; - virtual auto read_ppu(address, cartridge&, std::uint8_t const* vram) -> std::uint8_t = 0; - virtual auto write_ppu(address, std::uint8_t, cartridge&, std::uint8_t* vram) -> void = 0; - - protected: - explicit mapper() = default; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/nes.cc b/lib/nes/nes.cc deleted file mode 100644 index 9a35bfa..0000000 --- a/lib/nes/nes.cc +++ /dev/null @@ -1,65 +0,0 @@ -#include "nes/nes.hh" -#include "nes/util/display.hh" -#include "nes/util/rgb.hh" -#include "nes/util/snapshot.hh" - -namespace nes -{ - nes::nes(cartridge cartridge, display& display) - : cartridge_{ std::move(cartridge) } - , display_{ display } - , ppu_{ cpu_, cartridge_, display_ } - , cpu_{ ppu_, cartridge_, controller_1_, controller_2_ } - , status_{ cartridge_.get_status() } - { - display_.clear(rgb{}); - display_.switch_buffers(); - display_.clear(rgb{}); - } - - auto nes::step(std::chrono::microseconds const delta) -> void - { - step(cycle_count::from_duration(delta)); - } - - auto nes::step() -> void - { - if (get_status() != status::success) { return; } - - status_ = cpu_.step(); - while (ppu_.get_cycles() < cpu_.get_cycles()) - { - ppu_.step(); - } - } - - auto nes::step(cycle_count const delta) -> void - { - current_cycles_ += delta; - while (cpu_.get_cycles() < current_cycles_ && get_status() == status::success) - { - step(); - } - } - - auto nes::step_to_nmi() -> void - { - while (cpu_.is_nmi_pending()) - { - step(); - } - while (!cpu_.is_nmi_pending()) - { - step(); - } - } - - auto nes::get_snapshot() -> snapshot - { - auto res = snapshot{}; - res.sram = std::vector(cartridge_.get_ram(), cartridge_.get_ram() + cartridge_.get_ram_length()); - cpu_.build_snapshot(res); - ppu_.build_snapshot(res); - return res; - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/nes.hh b/lib/nes/nes.hh deleted file mode 100644 index dab5ded..0000000 --- a/lib/nes/nes.hh +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" -#include "nes/cartridge.hh" -#include "nes/controller.hh" -#include "nes/cpu.hh" -#include "nes/ppu.hh" - -namespace nes -{ - class display; - struct snapshot; - - /// The main console abstraction. - class nes - { - cartridge cartridge_; - display& display_; - controller controller_1_; - controller controller_2_; - ppu ppu_; - cpu cpu_; - cycle_count current_cycles_; - status status_{ status::success }; - - public: - explicit nes(cartridge, display&); - - nes(nes const&) = delete; - nes(nes&&) = delete; - auto operator=(nes const&) -> nes& = delete; - auto operator=(nes&&) -> nes& = delete; - - auto get_status() const -> status { return status_; } - auto get_controller_1() const -> controller const& { return controller_1_; } - auto ref_controller_1() -> controller& { return controller_1_; } - auto get_controller_2() const -> controller const& { return controller_2_; } - auto ref_controller_2() -> controller& { return controller_2_; } - - auto step() -> void; - auto step(std::chrono::microseconds delta) -> void; - auto step(cycle_count delta) -> void; - auto step_to_nmi() -> void; - - auto get_snapshot() -> snapshot; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/ppu.cc b/lib/nes/ppu.cc deleted file mode 100644 index 815224f..0000000 --- a/lib/nes/ppu.cc +++ /dev/null @@ -1,697 +0,0 @@ -#include "nes/ppu.hh" -#include "nes/cpu.hh" -#include "nes/cartridge.hh" -#include "nes/util/address.hh" -#include "nes/util/display.hh" -#include "nes/util/rgb.hh" -#include "nes/util/snapshot.hh" -#include "nes/util/debug.hh" - -namespace nes -{ - ppu::ppu(cpu& cpu, cartridge& cartridge, display& display) - : cpu_{ cpu } - , cartridge_{ cartridge } - , display_{ display } - { - } - - auto ppu::build_snapshot(snapshot& snapshot) -> void - { - snapshot.vram = std::vector(std::begin(vram_), std::end(vram_)); - snapshot.oam = std::vector(std::begin(oam_), std::end(oam_)); - } - - auto ppu::step() -> void - { - // See https://www.nesdev.org/wiki/PPU_rendering - // Inspired by https://github.com/fogleman/nes/blob/master/nes/ppu.go - - auto const enable_rendering = mask_.get_enable_background() || mask_.get_enable_sprites(); - - current_cycles_ += cycle_count::from_ppu(1); - scanline_cycle_ += 1; - if (enable_rendering && !even_frame_ && scanline_ == 261 && scanline_cycle_ == 340) - { - current_cycles_ += cycle_count::from_ppu(1); - scanline_cycle_ += 1; - } - if (scanline_cycle_ == 341) - { - scanline_cycle_ = 0; - scanline_ += 1; - - if (scanline_ == 262) - { - scanline_ = 0; - even_frame_ = !even_frame_; - } - } - - auto const pre_line = scanline_ == 261; - auto const visible_line = scanline_ < 240; - auto const render_line = pre_line || visible_line; - auto const pre_fetch_cycle = scanline_cycle_ >= 321 && scanline_cycle_ <= 336; - auto const visible_cycle = scanline_cycle_ >= 1 && scanline_cycle_ <= 256; - auto const fetch_cycle = pre_fetch_cycle || visible_cycle; - - // Background Logic - if (enable_rendering) - { - if (visible_line && visible_cycle) - { - render_pixel(); - } - - if (render_line && fetch_cycle) - { - // Fetch cycle for the background. - switch (scanline_cycle_ % 8) - { - case 1: - { - // Load the tile pattern for the background from the name table. - fetch_cycle_.tile = tile{ read8(address{ 0x2000 } + internal_.v.get_tile_address()) }; - break; - } - case 3: - { - // Load the tile palette for the background from the attribute table. - auto const addr = - address{ 0x23C0 } + - (static_cast(internal_.v.get_name_table()) * 0x400u) + - ((internal_.v.get_coarse_y() & 0b11100u) << 1) + - ((internal_.v.get_coarse_x() & 0b11100u) >> 2); - auto const shift = - ((internal_.v.get_coarse_y() & 0b00010u) << 1) | - ((internal_.v.get_coarse_x() & 0b00010u) << 0); - fetch_cycle_.palette = static_cast((read8(addr) >> shift) & 0b11); - break; - } - case 5: - { - // Load bitplane 0 for the background tile's pattern. - fetch_cycle_.bitplane_0 = get_tile_bitplane( - control_.get_background_pattern_table(), - fetch_cycle_.tile, - internal_.v.get_fine_y(), - bitplane::_0); - break; - } - case 7: - { - // Load bitplane 1 for the background tile's pattern. - fetch_cycle_.bitplane_1 = get_tile_bitplane( - control_.get_background_pattern_table(), - fetch_cycle_.tile, - internal_.v.get_fine_y(), - bitplane::_1); - break; - } - case 0: - { - // Fetch cycle done -> build the tile row for the background. - current_background_ = next_background_; - next_background_ = get_tile_row( - fetch_cycle_.palette, fetch_cycle_.bitplane_0, fetch_cycle_.bitplane_1); - break; - } - default: - break; - } - } - - if (pre_line && scanline_cycle_ >= 280 && scanline_cycle_ <= 304) - { - copy_y(); - } - - if (render_line) - { - if (fetch_cycle && scanline_cycle_ % 8 == 0) - { - increment_x(); - } - if (scanline_cycle_ == 256) - { - increment_y(); - } - if (scanline_cycle_ == 257) - { - copy_x(); - } - } - } - - // Sprite Logic - if (enable_rendering) - { - if (scanline_cycle_ == 257) - { - if (visible_line) - { - evaluate_sprites(); - } - else - { - sprite_count_ = 0; - } - } - } - - // Vblank Logic - if (scanline_ == 241 && scanline_cycle_ == 1) - { - display_.switch_buffers(); - status_.set_vblank(true); - if (control_.get_vblank_nmi()) { cpu_.trigger_nmi(); } - } - if (pre_line && scanline_cycle_ == 1) - { - status_.set_vblank(false); - status_.set_sprite_zero_hit(false); - status_.set_sprite_overflow(false); - } - } - - auto ppu::render_pixel() -> void - { - auto const x = scanline_cycle_ - 1; - auto const y = scanline_; - - auto background_color = color_index{ 0 }; - if (mask_.get_enable_background()) - { - background_color = current_background_.colors[internal_.x + x % tile_size]; - background_color.set_role(role::background); - } - - auto foreground = evaluated_sprite{}; - auto foreground_color = color_index{ 0 }; - if (mask_.get_enable_sprites()) - { - for (auto i = unsigned{ 0 }; i < sprite_count_; ++i) - { - auto const s = sprites_[i]; - auto const offset = static_cast(x) - static_cast(s.x); - if (offset < 0 || static_cast(offset) >= tile_size) { continue; } - auto const color = s.pattern.colors[offset]; - if (color.get_color() == palette_color::_0) { continue; } - - foreground = s; - foreground_color = color; - foreground_color.set_role(role::foreground); - break; - } - } - - auto has_background = background_color.get_color() != palette_color::_0; - auto has_foreground = foreground_color.get_color() != palette_color::_0; - - if (x < tile_size && !mask_.get_show_background_start()) { has_background = false; } - if (x < tile_size && !mask_.get_show_sprites_start()) { has_foreground = false; } - - auto color = color_index{ 0 }; - if (!has_background && has_foreground) - { - color = foreground_color; - } - if (has_background && !has_foreground) - { - color = background_color; - } - if (has_background && has_foreground) - { - if (foreground.is_sprite_zero && x < 255) - { - status_.set_sprite_zero_hit(true); - } - - color = foreground.is_in_front ? foreground_color : background_color; - } - - display_.set(x, y, resolve_color(ref_color(color))); - } - - auto ppu::evaluate_sprites() -> void - { - auto const height = get_sprite_height(); - sprite_count_ = 0; - for (auto i = unsigned{ 0 }; i < sprite_max_count; ++i) - { - auto const s = sprite{ &oam_[i * 4] }; - auto const row = static_cast(scanline_) - static_cast(s.get_y()); - if (row < 0 || static_cast(row) >= height) { continue; } - - if (sprite_count_ < 8) - { - sprites_[sprite_count_].pattern = fetch_sprite_pattern(s, static_cast(row)); - sprites_[sprite_count_].x = s.get_x(); - sprites_[sprite_count_].is_in_front = !s.get_behind_background(); - sprites_[sprite_count_].is_sprite_zero = i == 0; - sprite_count_ += 1; - } - else - { - status_.set_sprite_overflow(true); - break; - } - } - } - - auto ppu::fetch_sprite_pattern(sprite const s, unsigned row) -> tile_row - { - if (s.get_flip_vertical()) { row = get_sprite_height() - 1 - row; } - tile tile; - pattern_table pattern_table; - switch (control_.get_sprite_size()) - { - case sprite_size::single_height: - tile = s.get_small_tile(); - pattern_table = control_.get_sprite_pattern_table(); - break; - case sprite_size::double_height: - tile = s.get_large_top_tile(); - if (row >= tile_size) - { - tile = static_cast(1); - row -= tile_size; - } - pattern_table = s.get_large_pattern_table(); - break; - default: - return tile_row{}; - } - - auto const bitplane_0 = get_tile_bitplane(pattern_table, tile, row, bitplane::_0); - auto const bitplane_1 = get_tile_bitplane(pattern_table, tile, row, bitplane::_1); - auto res = get_tile_row(s.get_palette(), bitplane_0, bitplane_1); - - if (s.get_flip_horizontal()) - { - for (auto i = unsigned{ 0 }; i < tile_size / 2; ++i) - { - std::swap(res.colors[i], res.colors[tile_size - 1 - i]); - } - } - - return res; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Memory Access - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/PPU_memory_map - // - - auto ppu::read8(address addr) -> std::uint8_t - { - addr = addr % 0x4000; // PPU only has 16 KiB addresses. - if (addr <= address{ 0x3EFF }) { return cartridge_.get_mapper().read_ppu(addr, cartridge_, vram_); } - if (addr <= address{ 0x3FFF }) - { - auto const index = color_index{ static_cast(addr.get_absolute() % 0x20) }; - auto const color = ref_color(index); - return static_cast(color); - } - - return 0x0; - } - - auto ppu::write8(address addr, std::uint8_t const value) -> void - { - addr = addr % 0x4000; // PPU only has 16 KiB addresses. - if (addr <= address{ 0x3EFF }) - { - cartridge_.get_mapper().write_ppu(addr, value, cartridge_, vram_); - return; - } - if (addr <= address{ 0x3FFF }) - { - auto const index = color_index{ static_cast(addr.get_absolute() % 0x20) }; - ref_color(index) = color{ value }; - return; - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // IO Registers - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/PPU_registers - // - - auto ppu::read_latch() -> std::uint8_t - { - return latch_; - } - - auto ppu::read_ppustatus() -> std::uint8_t - { - auto res = status_; - res.set_remaining(latch_); - NES_DEBUG_LOG(ppu, "PPUSTATUS -> {:#2x}", res.value); - - status_.set_vblank(false); - internal_.w = false; - - write_latch(res.value); - return res.value; - } - - auto ppu::read_oamdata() -> std::uint8_t - { - auto res = oam_[oamaddr_]; - if ((oamaddr_ & 0x3) == 0x2) - { - res &= 0xE3; - } - NES_DEBUG_LOG(ppu, "OAMDATA -> {:#2x}", res); - write_latch(res); - return res; - } - - auto ppu::read_ppudata() -> std::uint8_t - { - auto const addr = address{ internal_.v.value }; - auto res = read8(addr); - write_latch(ppudata_read_buffer_); - - if (addr % 0x4000 <= address{ 0x3EFF }) - { - std::swap(res, ppudata_read_buffer_); - } - else - { - // For palette buffers: buffer the mirrored nametable instead. - ppudata_read_buffer_ = read8(addr - 0x1000); - } - - NES_DEBUG_LOG(ppu, "PPUDATA -> {:#2x} (address: {:#4x})", res, internal_.v); - increment_vram(); - return res; - } - - auto ppu::write_latch(std::uint8_t const value) -> void - { - latch_ = value; - } - - auto ppu::write_ppuctrl(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUCTRL <- {:#2x}", value); - control_.value = value; - internal_.t.set_name_table(control_.get_base_name_table()); - if (control_.get_vblank_nmi() && status_.get_vblank()) { cpu_.trigger_nmi(); } - } - } - - auto ppu::write_ppuscroll(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUSCROLL <- {:#2x}", value); - if (!internal_.w) - { - // First write -> x value. - internal_.t.set_coarse_x((value & 0b11111000) >> 3); - internal_.x = (value & 0b00000111) >> 0; - } - else - { - // Second write -> y value. - internal_.t.set_coarse_y((value & 0b11111000) >> 3); - internal_.t.set_fine_y((value & 0b00000111) >> 0); - } - - internal_.w = !internal_.w; - } - } - - auto ppu::write_ppumask(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUMASK <- {:#2x}", value); - mask_.value = value; - } - } - - auto ppu::write_ppuaddr(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUADDR <- {:#2x}", value); - if (!internal_.w) - { - // First write. - internal_.t.set_address_high(value & 0b00111111); - } - else - { - // Second write. - internal_.t.set_address_low(value); - internal_.v = internal_.t; - } - - internal_.w = !internal_.w; - } - } - - auto ppu::write_ppudata(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "PPUDATA <- {:#2x} (address: {:#4x})", value, internal_.v); - write_latch(value); - write8(address{ internal_.v.value }, value); - increment_vram(); - } - - auto ppu::write_oamaddr(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "OAMADDR <- {:#2x}", value); - write_latch(value); - oamaddr_ = value; - } - - auto ppu::write_oamdata(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "OAMDATA <- {:#2x}", value); - write_latch(value); - oam_[oamaddr_] = value; - oamaddr_ += 1; - } - - auto ppu::write_oamdma(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "OAMDMA <- {:#2x}", value); - auto addr = address{ value, 0x00 }; - for (auto i = unsigned{ 0 }; i < 256; ++i) - { - oam_[oamaddr_] = cpu_.read8(addr); - oamaddr_ += 1; - addr = addr + 1; - } - - auto const stalled_cycles = cycle_count::from_cpu((cpu_.get_cycles().to_cpu() % 2 == 0) ? 513 : 514); - cpu_.stall_cycles(stalled_cycles); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Helpers - // ----------------------------------------------------------------------------------------------------------------- - - auto ppu::increment_vram() -> void - { - switch (control_.get_vram_increment()) - { - case vram_increment::forward: - internal_.v.value += 1; - break; - case vram_increment::downward: - internal_.v.value += 32; - break; - } - } - - auto ppu::increment_x() -> void - { - if (internal_.v.get_coarse_x() == 31) - { - internal_.v.set_coarse_x(0); - internal_.v.set_horizontal_name_table(1 ^ internal_.v.get_horizontal_name_table()); - } - else - { - internal_.v.set_coarse_x(internal_.v.get_coarse_x() + 1); - } - } - - auto ppu::increment_y() -> void - { - if (internal_.v.get_fine_y() == 7) - { - internal_.v.set_fine_y(0); - if (internal_.v.get_coarse_y() == 29) - { - internal_.v.set_coarse_y(0); - internal_.v.set_vertical_name_table(1 ^ internal_.v.get_vertical_name_table()); - } - else if (internal_.v.get_coarse_y() == 31) - { - internal_.v.set_coarse_y(0); - // Nametable not switched - } - else - { - internal_.v.set_coarse_y(internal_.v.get_coarse_y() + 1); - } - } - else - { - internal_.v.set_fine_y(internal_.v.get_fine_y() + 1); - } - } - - auto ppu::copy_x() -> void - { - internal_.v.set_coarse_x(internal_.t.get_coarse_x()); - internal_.v.set_horizontal_name_table(internal_.t.get_horizontal_name_table()); - } - - auto ppu::copy_y() -> void - { - internal_.v.set_coarse_y(internal_.t.get_coarse_y()); - internal_.v.set_fine_y(internal_.t.get_fine_y()); - internal_.v.set_vertical_name_table(internal_.t.get_vertical_name_table()); - } - - auto ppu::get_sprite_height() const -> unsigned - { - switch (control_.get_sprite_size()) - { - case sprite_size::single_height: - return tile_size; - case sprite_size::double_height: - return tile_size * 2; - } - - return tile_size; - } - - auto ppu::get_tile_bitplane( - pattern_table const pattern_table, tile const tile, unsigned const row, bitplane const bitplane) -> std::uint8_t - { - auto const addr = static_cast( - 0x1000 * static_cast(pattern_table) + 0x10 * static_cast(tile) + row); - return read8(address{ addr } + (static_cast(bitplane) * 8u)); - } - - auto ppu::get_tile_row(palette const palette, std::uint8_t bitplane_0, std::uint8_t bitplane_1) const -> tile_row - { - auto res = tile_row{}; - for (auto i = unsigned{ 0 }; i < tile_size; ++i) - { - auto const bit_0 = (bitplane_0 & 0b10000000u) >> 7; - auto const bit_1 = (bitplane_1 & 0b10000000u) >> 7; - bitplane_0 <<= 1; - bitplane_1 <<= 1; - res.colors[i].set_palette(palette); - res.colors[i].set_color(palette_color{ (bit_1 << 1) | (bit_0 << 0) }); - } - return res; - } - - auto ppu::ref_color(color_index index) -> color& - { - // See https://www.nesdev.org/wiki/PPU_palettes - index.value = index.value % 0x20; - if (index.get_color() == palette_color::_0) - { - // The first color is mirrored between background and foreground palettes. - index.set_role(role::background); - } - return palette_buffer_[index.value]; - } - - auto ppu::resolve_color(color const color) const -> rgb - { - auto const index = static_cast(color); - switch (index & 0x3F) - { - case 0x00: return rgb::from_hex(0x666666); - case 0x01: return rgb::from_hex(0x002A88); - case 0x02: return rgb::from_hex(0x1412A7); - case 0x03: return rgb::from_hex(0x3B00A4); - case 0x04: return rgb::from_hex(0x5C007E); - case 0x05: return rgb::from_hex(0x6E0040); - case 0x06: return rgb::from_hex(0x6C0600); - case 0x07: return rgb::from_hex(0x561D00); - case 0x08: return rgb::from_hex(0x333500); - case 0x09: return rgb::from_hex(0x0B4800); - case 0x0A: return rgb::from_hex(0x005200); - case 0x0B: return rgb::from_hex(0x004F08); - case 0x0C: return rgb::from_hex(0x00404D); - case 0x0D: return rgb::from_hex(0x000000); - case 0x0E: return rgb::from_hex(0x000000); - case 0x0F: return rgb::from_hex(0x000000); - case 0x10: return rgb::from_hex(0xADADAD); - case 0x11: return rgb::from_hex(0x155FD9); - case 0x12: return rgb::from_hex(0x4240FF); - case 0x13: return rgb::from_hex(0x7527FE); - case 0x14: return rgb::from_hex(0xA01ACC); - case 0x15: return rgb::from_hex(0xB71E7B); - case 0x16: return rgb::from_hex(0xB53120); - case 0x17: return rgb::from_hex(0x994E00); - case 0x18: return rgb::from_hex(0x6B6D00); - case 0x19: return rgb::from_hex(0x388700); - case 0x1A: return rgb::from_hex(0x0C9300); - case 0x1B: return rgb::from_hex(0x008F32); - case 0x1C: return rgb::from_hex(0x007C8D); - case 0x1D: return rgb::from_hex(0x000000); - case 0x1E: return rgb::from_hex(0x000000); - case 0x1F: return rgb::from_hex(0x000000); - case 0x20: return rgb::from_hex(0xFFFEFF); - case 0x21: return rgb::from_hex(0x64B0FF); - case 0x22: return rgb::from_hex(0x9290FF); - case 0x23: return rgb::from_hex(0xC676FF); - case 0x24: return rgb::from_hex(0xF36AFF); - case 0x25: return rgb::from_hex(0xFE6ECC); - case 0x26: return rgb::from_hex(0xFE8170); - case 0x27: return rgb::from_hex(0xEA9E22); - case 0x28: return rgb::from_hex(0xBCBE00); - case 0x29: return rgb::from_hex(0x88D800); - case 0x2A: return rgb::from_hex(0x5CE430); - case 0x2B: return rgb::from_hex(0x45E082); - case 0x2C: return rgb::from_hex(0x48CDDE); - case 0x2D: return rgb::from_hex(0x4F4F4F); - case 0x2E: return rgb::from_hex(0x000000); - case 0x2F: return rgb::from_hex(0x000000); - case 0x30: return rgb::from_hex(0xFFFEFF); - case 0x31: return rgb::from_hex(0xC0DFFF); - case 0x32: return rgb::from_hex(0xD3D2FF); - case 0x33: return rgb::from_hex(0xE8C8FF); - case 0x34: return rgb::from_hex(0xFBC2FF); - case 0x35: return rgb::from_hex(0xFEC4EA); - case 0x36: return rgb::from_hex(0xFECCC5); - case 0x37: return rgb::from_hex(0xF7D8A5); - case 0x38: return rgb::from_hex(0xE4E594); - case 0x39: return rgb::from_hex(0xCFEF96); - case 0x3A: return rgb::from_hex(0xBDF4AB); - case 0x3B: return rgb::from_hex(0xB3F3CC); - case 0x3C: return rgb::from_hex(0xB5EBF2); - case 0x3D: return rgb::from_hex(0xB8B8B8); - case 0x3E: return rgb::from_hex(0x000000); - case 0x3F: return rgb::from_hex(0x000000); - default: return rgb::from_hex(0x000000); - } - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/ppu.hh b/lib/nes/ppu.hh deleted file mode 100644 index afc295c..0000000 --- a/lib/nes/ppu.hh +++ /dev/null @@ -1,279 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" - -namespace nes -{ - class address; - class cpu; - class cartridge; - class display; - class rgb; - struct snapshot; - - class ppu - { - static constexpr auto sprite_max_count = unsigned{ 64 }; - static constexpr auto vram_size = std::size_t{ 0x800 }; - static constexpr auto oam_size = std::size_t{ sprite_max_count * 4 }; - static constexpr auto palette_buffer_size = std::size_t{ 0x20 }; - static constexpr auto tile_size = unsigned{ 8 }; - // Writes to some registers are ignored until this clock cycle. - static constexpr auto boot_up_cycles = cycle_count::from_ppu(29658); - - enum class bitplane : unsigned { _0, _1 }; - enum class palette : unsigned { _0, _1, _2, _3 }; - enum class palette_color : unsigned { _0, _1, _2, _3 }; - enum class name_table : unsigned { _0, _1, _2, _3 }; - enum class role : unsigned { background, foreground }; - enum class pattern_table : unsigned { _0, _1 }; - enum class vram_increment : unsigned { forward, downward }; - enum class sprite_size : unsigned { single_height, double_height }; - enum class tile : std::uint8_t {}; - enum class color : std::uint8_t {}; - -#define BITFIELD_VALIDATE(mask, offset_from_right) \ - static_assert((mask & (1 << offset_from_right)) != 0); \ - static_assert(offset_from_right == 0 || (mask & (1 << (offset_from_right - 1))) == 0); -#define BITFIELD_VALUE(name, base, mask, offset_from_right) \ - BITFIELD_VALIDATE(mask, offset_from_right) \ - auto get_##name() const -> unsigned { return (base & mask) >> offset_from_right; } \ - auto set_##name(unsigned const v) -> void { base = (base & ~mask) | ((v << offset_from_right) & mask); } -#define BITFIELD_ENUM(name, type, base, mask, offset_from_right) \ - BITFIELD_VALIDATE(mask, offset_from_right) \ - auto get_##name() const -> type { return static_cast((base & mask) >> offset_from_right); } \ - auto set_##name(type const v) -> void { base = (base & ~mask) | ((static_cast(v) << offset_from_right) & mask); } -#define BITFIELD_FLAG(name, base, mask, offset_from_right) \ - BITFIELD_VALIDATE(mask, offset_from_right) \ - auto get_##name() const -> bool { return base & mask; } \ - auto set_##name(unsigned const v) -> void { base = (base & ~mask) | (v ? mask : 0); } - - struct color_index - { - BITFIELD_ENUM(color, palette_color, value, 0b00000011, 0) - BITFIELD_ENUM(palette, palette, value, 0b00001100, 2) - BITFIELD_ENUM(role, role, value, 0b00010000, 4) - - std::uint8_t value{}; - - color_index() = default; - - explicit color_index(std::uint8_t const value) - : value{ value } - { - } - }; - - struct sprite - { - // Tile index (and pattern table) for sprites - BITFIELD_ENUM(large_pattern_table, pattern_table, value[1], 0b00000001, 0) - BITFIELD_ENUM(large_top_tile, tile, value[1], 0b11111110, 1) - BITFIELD_ENUM(small_tile, tile, value[1], 0b11111111, 0) - - // Attributes - BITFIELD_ENUM(palette, palette, value[2], 0b00000011, 0) - BITFIELD_FLAG(behind_background, value[2], 0b00100000, 5) - BITFIELD_FLAG(flip_horizontal, value[2], 0b01000000, 6) - BITFIELD_FLAG(flip_vertical, value[2], 0b10000000, 7) - - auto get_y() const -> std::uint8_t { return value[0]; } - auto get_tile_index() const -> std::uint8_t { return value[1]; } - auto get_attributes() const -> std::uint8_t { return value[2]; } - auto get_x() const -> std::uint8_t { return value[3]; } - - auto set_y(std::uint8_t const v) -> void { value[0] = v; } - auto set_tile_index(std::uint8_t const v) -> void { value[1] = v; } - auto set_attributes(std::uint8_t const v) -> void { value[2] = v; } - auto set_x(std::uint8_t const v) -> void { value[3] = v; } - - std::uint8_t value[4]{}; - - explicit sprite() = default; - - explicit sprite(std::uint8_t const* data) - : value{ data[0], data[1], data[2], data[3] } - { - } - }; - - struct tile_row - { - color_index colors[tile_size]{}; - }; - - struct evaluated_sprite - { - tile_row pattern; - std::uint8_t x{ 0 }; - bool is_in_front{ false }; - bool is_sprite_zero{ false }; - }; - - cycle_count current_cycles_; - cpu& cpu_; - cartridge& cartridge_; - display& display_; - std::uint8_t vram_[vram_size]{}; - std::uint8_t oam_[oam_size]{}; - color palette_buffer_[palette_buffer_size] - { - color{ 0x09 }, color{ 0x01 }, color{ 0x00 }, color{ 0x01 }, - color{ 0x00 }, color{ 0x02 }, color{ 0x02 }, color{ 0x0D }, - color{ 0x08 }, color{ 0x10 }, color{ 0x08 }, color{ 0x24 }, - color{ 0x00 }, color{ 0x00 }, color{ 0x04 }, color{ 0x2C }, - color{ 0x09 }, color{ 0x01 }, color{ 0x34 }, color{ 0x03 }, - color{ 0x00 }, color{ 0x04 }, color{ 0x00 }, color{ 0x14 }, - color{ 0x08 }, color{ 0x3A }, color{ 0x00 }, color{ 0x02 }, - color{ 0x00 }, color{ 0x20 }, color{ 0x2C }, color{ 0x08 }, - }; - std::uint8_t latch_{}; // The last read/written IO register value, returned when reading write-only registers. - struct - { - BITFIELD_FLAG(sprite_overflow, value, 0b00100000, 5) - BITFIELD_FLAG(sprite_zero_hit, value, 0b01000000, 6) - BITFIELD_FLAG(vblank, value, 0b10000000, 7) - - // Set the remaining (unused) bits based on the given value. - auto set_remaining(std::uint8_t const v) -> void { value = (value & ~0b00011111) | (v & 0b00011111); } - - std::uint8_t value{ 0b00000000 }; - } status_{}; - struct - { - // Base nametable address. - BITFIELD_ENUM(base_name_table, name_table, value, 0b00000011, 0) - // How VRAM addresses should be incremented each read/write. - BITFIELD_ENUM(vram_increment, vram_increment, value, 0b00000100, 2) - // Sprite pattern table address for 8x8 sprites. - BITFIELD_ENUM(sprite_pattern_table, pattern_table, value, 0b00001000, 3) - // Background pattern table address. - BITFIELD_ENUM(background_pattern_table, pattern_table, value, 0b00010000, 4) - // The size of the sprites used (i.e. single-height or double-height). - BITFIELD_ENUM(sprite_size, sprite_size, value, 0b00100000, 5) - // PPU master/slave select. - BITFIELD_FLAG(enable_ext_pin, value, 0b01000000, 6) - // Enable/disable vblank NMI. - BITFIELD_FLAG(vblank_nmi, value, 0b10000000, 7) - - std::uint8_t value{ 0b00000000 }; - } control_{}; - struct - { - // Enable/disable grayscale. - BITFIELD_FLAG(grayscale, value, 0b00000001, 0) - // Show background in leftmost 8 pixels of screen. - BITFIELD_FLAG(show_background_start, value, 0b00000010, 1) - // Show sprites in leftmost 8 pixels of screen. - BITFIELD_FLAG(show_sprites_start, value, 0b00000100, 2) - // Enable background rendering. - BITFIELD_FLAG(enable_background, value, 0b00001000, 3) - // Enable sprite rendering. - BITFIELD_FLAG(enable_sprites, value, 0b00010000, 4) - // Emphasize red. - BITFIELD_FLAG(emphasize_red, value, 0b00100000, 5) - // Emphasize green. - BITFIELD_FLAG(emphasize_green, value, 0b01000000, 6) - // Emphasize blue. - BITFIELD_FLAG(emphasize_blue, value, 0b10000000, 7) - - std::uint8_t value{ 0b00000000 }; - } mask_{}; - struct - { - // See https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers - struct - { - // Scroll position - BITFIELD_VALUE(coarse_x, value, 0b0000000000011111, 0) - BITFIELD_VALUE(coarse_y, value, 0b0000001111100000, 5) - BITFIELD_ENUM(name_table, name_table, value, 0b0000110000000000, 10) - BITFIELD_VALUE(horizontal_name_table, value, 0b0000010000000000, 10) - BITFIELD_VALUE(vertical_name_table, value, 0b0000100000000000, 11) - BITFIELD_VALUE(tile_address, value, 0b0000111111111111, 0) - BITFIELD_VALUE(fine_y, value, 0b0111000000000000, 12) - - // Address - BITFIELD_VALUE(address_low, value, 0b0000000011111111, 0) - BITFIELD_VALUE(address_high, value, 0b0111111100000000, 8) - - std::uint16_t value{}; - } v{}, t{}; // Current and temporary VRAM address and scroll position (15 bits) - std::uint8_t x{}; // Fine X scroll (3 bits) - bool w{}; // Second write toggle (1 bit) - } internal_{}; - std::uint8_t oamaddr_{}; // Address accessed by IO registers - std::uint8_t ppudata_read_buffer_{}; // Delays PPUDATA reads by one. - unsigned scanline_{ 240 }; - unsigned scanline_cycle_{ 340 }; - bool even_frame_{ true }; - tile_row current_background_{}; - tile_row next_background_{}; - struct - { - tile tile{ 0 }; - palette palette{ 0 }; - std::uint8_t bitplane_0{ 0 }; - std::uint8_t bitplane_1{ 0 }; - } fetch_cycle_{}; // Data populated during the fetch cycle. - evaluated_sprite sprites_[8]{}; // Evaluated sprites. - unsigned sprite_count_{ 0 }; // Number of evaluated sprites in sprites_. - -#undef BITFIELD_VALIDATE -#undef BITFIELD_VALUE -#undef BITFIELD_ENUM -#undef BITFIELD_FLAG - - public: - explicit ppu(cpu&, cartridge&, display&); - - ppu(ppu const&) = delete; - ppu(ppu&&) = delete; - auto operator=(ppu const&) -> ppu& = delete; - auto operator=(ppu&&) -> ppu& = delete; - - auto get_cycles() const -> cycle_count { return current_cycles_; } - auto build_snapshot(snapshot&) -> void; - auto step() -> void; - - // Memory access - - auto read8(address) -> std::uint8_t; - auto write8(address, std::uint8_t) -> void; - - // IO registers - - auto read_latch() -> std::uint8_t; - auto read_ppustatus() -> std::uint8_t; - auto read_oamdata() -> std::uint8_t; - auto read_ppudata() -> std::uint8_t; - - auto write_latch(std::uint8_t) -> void; - auto write_ppuctrl(std::uint8_t) -> void; - auto write_ppuscroll(std::uint8_t) -> void; - auto write_ppumask(std::uint8_t) -> void; - auto write_ppuaddr(std::uint8_t) -> void; - auto write_ppudata(std::uint8_t) -> void; - auto write_oamaddr(std::uint8_t) -> void; - auto write_oamdata(std::uint8_t) -> void; - auto write_oamdma(std::uint8_t) -> void; - - private: - auto render_pixel() -> void; - auto evaluate_sprites() -> void; - auto fetch_sprite_pattern(sprite, unsigned row) -> tile_row; - - // Helpers - - auto increment_vram() -> void; - auto increment_x() -> void; - auto increment_y() -> void; - auto copy_x() -> void; - auto copy_y() -> void; - auto get_sprite_height() const -> unsigned; - auto get_tile_bitplane(pattern_table, tile, unsigned row, bitplane) -> std::uint8_t; - auto get_tile_row(palette, std::uint8_t bitplane_0, std::uint8_t bitplane_1) const -> tile_row; - auto ref_color(color_index) -> color&; - auto resolve_color(color) const -> rgb; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/ref/CMakeLists.txt b/lib/nes/ref/CMakeLists.txt deleted file mode 100644 index ce97573..0000000 --- a/lib/nes/ref/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -target_sources( - nes_ref - PRIVATE - nes.hh - nes.cc - cartridge.hh - cartridge.cc - controller.hh - controller.cc - cpu.hh - cpu.cc - ppu.hh - ppu.cc - mapper.hh - mapper.cc) \ No newline at end of file diff --git a/lib/nes/ref/cartridge.cc b/lib/nes/ref/cartridge.cc deleted file mode 100644 index 7f8257e..0000000 --- a/lib/nes/ref/cartridge.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include "nes/ref/cartridge.hh" -#include - -namespace nes::ref -{ - namespace - { - constexpr auto prg_rom_bank_size = std::size_t{ 16 * 1024 }; - constexpr auto chr_rom_bank_size = std::size_t{ 8 * 1024 }; - constexpr auto ram_bank_size = std::size_t{ 8 * 1024 }; - } // namespace - - auto cartridge::from_data(void const* data, std::size_t const length) -> cartridge - { - // See: https://www.nesdev.org/wiki/INES - - auto const bytes = static_cast(data); - auto res = cartridge{}; - res.is_valid_ = true; - - // Parse header. - if (length < 16) { return cartridge{}; } - if (bytes[0] != 'N' || bytes[1] != 'E' || bytes[2] != 'S' || bytes[3] != 0x1A) { return cartridge{}; } - auto const prg_rom_banks = bytes[4]; - auto const chr_rom_banks = bytes[5]; - auto const control_1 = bytes[6]; - auto const control_2 = bytes[7]; - auto const ram_banks = (bytes[8] != 0) ? bytes[8] : 1; - auto const has_trainer = (control_1 & 0b00000100) != 0; - - res.mapper_number_ = - (control_1 & 0b11110000) >> 4 | - (control_2 & 0b11110000) >> 0; - res.name_table_arrangement_ = (control_1 & 0b00000001) != 0 - ? name_table_arrangement::vertical - : name_table_arrangement::horizontal; - - // Load program data. - auto offset = std::size_t{ 16 }; - if (has_trainer) { offset += 512; } - - auto const prg_rom_size = prg_rom_banks * prg_rom_bank_size; - if (length < offset + prg_rom_size) { return cartridge{}; } - res.prg_rom_ = std::vector(&bytes[offset], &bytes[offset + prg_rom_size]); - - offset += prg_rom_size; - - auto const chr_rom_size = chr_rom_banks * chr_rom_bank_size; - if (length < offset + chr_rom_size) { return cartridge{}; } - res.chr_rom_ = std::vector(&bytes[offset], &bytes[offset + chr_rom_size]); - - res.ram_ = std::vector(ram_banks * ram_bank_size, std::uint8_t{ 0 }); - - return res; - } - - auto cartridge::from_file(std::string_view const path) -> cartridge - { - auto stream = std::ifstream{ path, std::ios::binary }; - if (!stream.good()) { return cartridge{}; } - - stream.seekg(0, std::ios::end); - auto const length = stream.tellg(); - stream.seekg(0, std::ios::beg); - auto data = std::vector(length); - stream.read(reinterpret_cast(data.data()), data.size()); - - return from_data(data.data(), data.size()); - } -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/cartridge.hh b/lib/nes/ref/cartridge.hh deleted file mode 100644 index ce94757..0000000 --- a/lib/nes/ref/cartridge.hh +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace nes::ref -{ - enum class name_table_arrangement - { - horizontal, - vertical, - }; - - class cartridge - { - bool is_valid_{ false }; - std::vector prg_rom_; - std::vector chr_rom_; - std::vector ram_; - std::uint8_t mapper_number_{ 0 }; - name_table_arrangement name_table_arrangement_{}; - - public: - explicit cartridge() = default; - - static auto from_data(void const* data, std::size_t length) -> cartridge; - static auto from_file(std::string_view path) -> cartridge; - - auto is_valid() const -> bool { return is_valid_; } - auto get_prg_rom() const -> std::uint8_t const* { return prg_rom_.data(); } - auto get_prg_rom_length() const -> std::size_t { return prg_rom_.size(); } - auto get_chr_rom() const -> std::uint8_t const* { return chr_rom_.data(); } - auto get_chr_rom_length() const -> std::size_t { return chr_rom_.size(); } - auto get_ram() const -> std::uint8_t const* { return ram_.data(); } - auto get_ram_mut() -> std::uint8_t* { return ram_.data(); } - auto get_ram_length() const -> std::size_t { return ram_.size(); } - auto get_mapper_number() const -> std::uint8_t { return mapper_number_; } - auto get_name_table_arrangement() const -> name_table_arrangement { return name_table_arrangement_; } - }; -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/controller.cc b/lib/nes/ref/controller.cc deleted file mode 100644 index 70d6ba2..0000000 --- a/lib/nes/ref/controller.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "nes/ref/controller.hh" - -namespace nes::ref -{ - auto controller::read() -> std::uint8_t - { - auto const res = index_ < 8 ? (pressed_.get_raw_value() >> index_) & 1 : 0; - index_ += 1; - if (strobing_) { index_ = 0; } - return static_cast(res); - } - - auto controller::write(std::uint8_t const value) -> void - { - strobing_ = value & 1; - if (strobing_) { index_ = 0; } - } -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/controller.hh b/lib/nes/ref/controller.hh deleted file mode 100644 index 231653c..0000000 --- a/lib/nes/ref/controller.hh +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "nes/util/button-mask.hh" - -namespace nes::ref -{ - class controller - { - button_mask pressed_; - unsigned index_{ 0 }; - bool strobing_{ false }; - - public: - explicit controller() = default; - - controller(controller const&) = delete; - controller(controller&&) = delete; - auto operator=(controller const&) -> controller& = delete; - auto operator=(controller&&) -> controller& = delete; - - auto get_pressed() const -> button_mask { return pressed_;} - auto get_pressed() -> button_mask& { return pressed_; } - auto set_pressed(button_mask const value) -> void { pressed_ = value; } - - // IO register - - auto read() -> std::uint8_t; - auto write(std::uint8_t) -> void; - }; -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/cpu.cc b/lib/nes/ref/cpu.cc deleted file mode 100644 index 79b0c56..0000000 --- a/lib/nes/ref/cpu.cc +++ /dev/null @@ -1,1358 +0,0 @@ -#include "nes/ref/cpu.hh" -#include "nes/ref/ppu.hh" -#include "nes/ref/controller.hh" -#include "nes/ref/mapper.hh" -#include "nes/util/snapshot.hh" - -namespace nes::ref -{ - cpu::cpu(ppu& ppu, mapper& mapper, controller& controller_1, controller& controller_2) - : ppu_{ ppu } - , mapper_{ mapper } - , controller_1_{ controller_1 } - , controller_2_{ controller_2 } - { - registers_.pc = read16(address{ 0xFFFC }); - } - - auto cpu::build_snapshot(snapshot& snapshot) -> void - { - snapshot.cpu_cycle = current_cycles_; - snapshot.ram = std::vector(std::begin(ram_), std::end(ram_)); - snapshot.registers.pc = registers_.pc; - snapshot.registers.sp = registers_.sp; - snapshot.registers.a = registers_.a; - snapshot.registers.x = registers_.x; - snapshot.registers.y = registers_.y; - snapshot.registers.p = registers_.p; - snapshot.registers.c = registers_.c; - snapshot.registers.z = registers_.z; - snapshot.registers.i = registers_.i; - snapshot.registers.d = registers_.d; - snapshot.registers.b = registers_.b; - snapshot.registers.v = registers_.v; - snapshot.registers.n = registers_.n; - } - - auto cpu::stall_cycles(cycle_count const count) -> void - { - current_cycles_ += count; - } - - auto cpu::trigger_nmi() -> void - { - nmi_pending_ = true; - } - - auto cpu::step() -> void - { - // See https://www.nesdev.org/wiki/CPU_unofficial_opcodes - -#ifdef NES_ENABLE_PPU - if (nmi_pending_) - { - execute_interrupt(address{ 0xFFFA }); - nmi_pending_ = false; - } -#endif - - auto const opcode = advance_pc8(); - switch (opcode) - { - case 0x00: run_brk(); break; - case 0x01: run_ora(); break; - case 0x02: run_stp(); break; - case 0x03: run_slo(); break; - case 0x04: run_nop(); break; - case 0x05: run_ora(); break; - case 0x06: run_asl(); break; - case 0x07: run_slo(); break; - case 0x08: run_php(); break; - case 0x09: run_ora(); break; - case 0x0A: run_asl(); break; - case 0x0B: run_anc(); break; - case 0x0C: run_nop(); break; - case 0x0D: run_ora(); break; - case 0x0E: run_asl(); break; - case 0x0F: run_slo(); break; - case 0x10: run_bpl(); break; - case 0x11: run_ora(); break; - case 0x12: run_stp(); break; - case 0x13: run_slo(); break; - case 0x14: run_nop(); break; - case 0x15: run_ora(); break; - case 0x16: run_asl(); break; - case 0x17: run_slo(); break; - case 0x18: run_clc(); break; - case 0x19: run_ora(); break; - case 0x1A: run_nop(); break; - case 0x1B: run_slo(); break; - case 0x1C: run_nop(); break; - case 0x1D: run_ora(); break; - case 0x1E: run_asl(); break; - case 0x1F: run_slo(); break; - case 0x20: run_jsr(); break; - case 0x21: run_and(); break; - case 0x22: run_stp(); break; - case 0x23: run_rla(); break; - case 0x24: run_bit(); break; - case 0x25: run_and(); break; - case 0x26: run_rol(); break; - case 0x27: run_rla(); break; - case 0x28: run_plp(); break; - case 0x29: run_and(); break; - case 0x2A: run_rol(); break; - case 0x2B: run_anc(); break; - case 0x2C: run_bit(); break; - case 0x2D: run_and(); break; - case 0x2E: run_rol(); break; - case 0x2F: run_rla(); break; - case 0x30: run_bmi(); break; - case 0x31: run_and(); break; - case 0x32: run_stp(); break; - case 0x33: run_rla(); break; - case 0x34: run_nop(); break; - case 0x35: run_and(); break; - case 0x36: run_rol(); break; - case 0x37: run_rla(); break; - case 0x38: run_sec(); break; - case 0x39: run_and(); break; - case 0x3A: run_nop(); break; - case 0x3B: run_rla(); break; - case 0x3C: run_nop(); break; - case 0x3D: run_and(); break; - case 0x3E: run_rol(); break; - case 0x3F: run_rla(); break; - case 0x40: run_rti(); break; - case 0x41: run_eor(); break; - case 0x42: run_stp(); break; - case 0x43: run_sre(); break; - case 0x44: run_nop(); break; - case 0x45: run_eor(); break; - case 0x46: run_lsr(); break; - case 0x47: run_sre(); break; - case 0x48: run_pha(); break; - case 0x49: run_eor(); break; - case 0x4A: run_lsr(); break; - case 0x4B: run_alr(); break; - case 0x4C: run_jmp(); break; - case 0x4D: run_eor(); break; - case 0x4E: run_lsr(); break; - case 0x4F: run_sre(); break; - case 0x50: run_bvc(); break; - case 0x51: run_eor(); break; - case 0x52: run_stp(); break; - case 0x53: run_sre(); break; - case 0x54: run_nop(); break; - case 0x55: run_eor(); break; - case 0x56: run_lsr(); break; - case 0x57: run_sre(); break; - case 0x58: run_cli(); break; - case 0x59: run_eor(); break; - case 0x5A: run_nop(); break; - case 0x5B: run_sre(); break; - case 0x5C: run_nop(); break; - case 0x5D: run_eor(); break; - case 0x5E: run_lsr(); break; - case 0x5F: run_sre(); break; - case 0x60: run_rts(); break; - case 0x61: run_adc(); break; - case 0x62: run_stp(); break; - case 0x63: run_rra(); break; - case 0x64: run_nop(); break; - case 0x65: run_adc(); break; - case 0x66: run_ror(); break; - case 0x67: run_rra(); break; - case 0x68: run_pla(); break; - case 0x69: run_adc(); break; - case 0x6A: run_ror(); break; - case 0x6B: run_arr(); break; - case 0x6C: run_jmp(); break; - case 0x6D: run_adc(); break; - case 0x6E: run_ror(); break; - case 0x6F: run_rra(); break; - case 0x70: run_bvs(); break; - case 0x71: run_adc(); break; - case 0x72: run_stp(); break; - case 0x73: run_rra(); break; - case 0x74: run_nop(); break; - case 0x75: run_adc(); break; - case 0x76: run_ror(); break; - case 0x77: run_rra(); break; - case 0x78: run_sei(); break; - case 0x79: run_adc(); break; - case 0x7A: run_nop(); break; - case 0x7B: run_rra(); break; - case 0x7C: run_nop(); break; - case 0x7D: run_adc(); break; - case 0x7E: run_ror(); break; - case 0x7F: run_rra(); break; - case 0x80: run_nop(); break; - case 0x81: run_sta(); break; - case 0x82: run_nop(); break; - case 0x83: run_sax(); break; - case 0x84: run_sty(); break; - case 0x85: run_sta(); break; - case 0x86: run_stx(); break; - case 0x87: run_sax(); break; - case 0x88: run_dey(); break; - case 0x89: run_nop(); break; - case 0x8A: run_txa(); break; - case 0x8B: run_xaa(); break; - case 0x8C: run_sty(); break; - case 0x8D: run_sta(); break; - case 0x8E: run_stx(); break; - case 0x8F: run_sax(); break; - case 0x90: run_bcc(); break; - case 0x91: run_sta(); break; - case 0x92: run_stp(); break; - case 0x93: run_ahx(); break; - case 0x94: run_sty(); break; - case 0x95: run_sta(); break; - case 0x96: run_stx(); break; - case 0x97: run_sax(); break; - case 0x98: run_tya(); break; - case 0x99: run_sta(); break; - case 0x9A: run_txs(); break; - case 0x9B: run_tas(); break; - case 0x9C: run_shy(); break; - case 0x9D: run_sta(); break; - case 0x9E: run_shx(); break; - case 0x9F: run_ahx(); break; - case 0xA0: run_ldy(); break; - case 0xA1: run_lda(); break; - case 0xA2: run_ldx(); break; - case 0xA3: run_lax(); break; - case 0xA4: run_ldy(); break; - case 0xA5: run_lda(); break; - case 0xA6: run_ldx(); break; - case 0xA7: run_lax(); break; - case 0xA8: run_tay(); break; - case 0xA9: run_lda(); break; - case 0xAA: run_tax(); break; - case 0xAB: run_lax(); break; - case 0xAC: run_ldy(); break; - case 0xAD: run_lda(); break; - case 0xAE: run_ldx(); break; - case 0xAF: run_lax(); break; - case 0xB0: run_bcs(); break; - case 0xB1: run_lda(); break; - case 0xB2: run_stp(); break; - case 0xB3: run_lax(); break; - case 0xB4: run_ldy(); break; - case 0xB5: run_lda(); break; - case 0xB6: run_ldx(); break; - case 0xB7: run_lax(); break; - case 0xB8: run_clv(); break; - case 0xB9: run_lda(); break; - case 0xBA: run_tsx(); break; - case 0xBB: run_las(); break; - case 0xBC: run_ldy(); break; - case 0xBD: run_lda(); break; - case 0xBE: run_ldx(); break; - case 0xBF: run_lax(); break; - case 0xC0: run_cpy(); break; - case 0xC1: run_cmp(); break; - case 0xC2: run_nop(); break; - case 0xC3: run_dcp(); break; - case 0xC4: run_cpy(); break; - case 0xC5: run_cmp(); break; - case 0xC6: run_dec(); break; - case 0xC7: run_dcp(); break; - case 0xC8: run_iny(); break; - case 0xC9: run_cmp(); break; - case 0xCA: run_dex(); break; - case 0xCB: run_axs(); break; - case 0xCC: run_cpy(); break; - case 0xCD: run_cmp(); break; - case 0xCE: run_dec(); break; - case 0xCF: run_dcp(); break; - case 0xD0: run_bne(); break; - case 0xD1: run_cmp(); break; - case 0xD2: run_stp(); break; - case 0xD3: run_dcp(); break; - case 0xD4: run_nop(); break; - case 0xD5: run_cmp(); break; - case 0xD6: run_dec(); break; - case 0xD7: run_dcp(); break; - case 0xD8: run_cld(); break; - case 0xD9: run_cmp(); break; - case 0xDA: run_nop(); break; - case 0xDB: run_dcp(); break; - case 0xDC: run_nop(); break; - case 0xDD: run_cmp(); break; - case 0xDE: run_dec(); break; - case 0xDF: run_dcp(); break; - case 0xE0: run_cpx(); break; - case 0xE1: run_sbc(); break; - case 0xE2: run_nop(); break; - case 0xE3: run_isc(); break; - case 0xE4: run_cpx(); break; - case 0xE5: run_sbc(); break; - case 0xE6: run_inc(); break; - case 0xE7: run_isc(); break; - case 0xE8: run_inx(); break; - case 0xE9: run_sbc(); break; - case 0xEA: run_nop(); break; - case 0xEB: run_sbc(); break; - case 0xEC: run_cpx(); break; - case 0xED: run_sbc(); break; - case 0xEE: run_inc(); break; - case 0xEF: run_isc(); break; - case 0xF0: run_beq(); break; - case 0xF1: run_sbc(); break; - case 0xF2: run_stp(); break; - case 0xF3: run_isc(); break; - case 0xF4: run_nop(); break; - case 0xF5: run_sbc(); break; - case 0xF6: run_inc(); break; - case 0xF7: run_isc(); break; - case 0xF8: run_sed(); break; - case 0xF9: run_sbc(); break; - case 0xFA: run_nop(); break; - case 0xFB: run_isc(); break; - case 0xFC: run_nop(); break; - case 0xFD: run_sbc(); break; - case 0xFE: run_inc(); break; - case 0xFF: run_isc(); break; - default: std::abort(); - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // Instructions - // ----------------------------------------------------------------------------------------------------------------- - - template - auto cpu::run_brk() -> void - { - // BRK: Break - fetch_operand(); - execute_interrupt(address{ 0xFFFE }); - } - - template - auto cpu::run_ora() -> void - { - // ORA: Logical Inclusive OR - auto operand = fetch_operand(); - eval_ora(operand.read()); - current_cycles_ += operand.get_cycles(); - } - - auto cpu::run_stp() -> void - { - // TODO: Proper STP handling - std::abort(); - } - - template - auto cpu::run_slo() -> void - { - // SLO: ASL + ORA - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_asl(operand.read()); - operand.write(tmp); - eval_ora(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - } - - template - auto cpu::run_nop() -> void - { - // NOP: NOP - auto operand = fetch_operand(); - current_cycles_ += operand.get_cycles(); - } - - auto cpu::run_nop() -> void - { - // NOP: NOP - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_php() -> void - { - // PHP: Push Processor Status - eval_php(); - current_cycles_ += cycle_count::from_cpu(3); - } - - template - auto cpu::run_asl() -> void - { - // ASL: Arithmetic Shift Left - auto operand = fetch_operand(); - operand.write(eval_asl(operand.read())); - current_cycles_ += shift_cycle_count(); - } - - template - auto cpu::run_anc() -> void - { - // ANC: AND #i + copy N to C - auto operand = fetch_operand(); - eval_and(operand.read()); - registers_.c = registers_.n; - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_bpl() -> void - { - // BPL: Break If Positive - branch(!registers_.n); - } - - auto cpu::run_clc() -> void - { - // CLC: Clear Carry Flag - registers_.c = false; - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_jsr() -> void - { - // JSR: Jump to Subroutine - static_assert(Mode == addressing_mode::absolute, "JSR only supports absolute addressing."); - - push_stack16(registers_.pc + 1); - - auto operand = fetch_operand(); - registers_.pc = operand.get_address().get_absolute(); - current_cycles_ += cycle_count::from_cpu(6); - } - - template - auto cpu::run_and() -> void - { - // AND: Logical AND - auto operand = fetch_operand(); - eval_and(operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_rla() -> void - { - // RLA: ROL + AND - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_rol(operand.read()); - operand.write(tmp); - eval_and(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - } - - template - auto cpu::run_bit() -> void - { - // BIT: Bit Test - auto operand = fetch_operand(); - auto const arg = operand.read(); - auto const res = (registers_.a & arg) != 0; - registers_.z = res == 0; - registers_.v = (arg & 0x40) != 0; - registers_.n = (arg & 0x80) != 0; - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_rol() -> void - { - // ROR: Rotate Left - auto operand = fetch_operand(); - auto const result = eval_rol(operand.read()); - operand.write(result); - current_cycles_ += shift_cycle_count(); - } - - auto cpu::run_plp() -> void - { - // PLP: Pull Processor Status - eval_plp(); - current_cycles_ += cycle_count::from_cpu(4); - } - - template - auto cpu::run_bmi() -> void - { - branch(registers_.n); - } - - auto cpu::run_sec() -> void - { - // SEC: Set Carry Flag - registers_.c = true; - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_rti() -> void - { - // RTI: Return From Interrupt - eval_plp(); - registers_.pc = pop_stack16(); - current_cycles_ += cycle_count::from_cpu(6); - } - - template - auto cpu::run_eor() -> void - { - // EOR: Exclusive OR - auto operand = fetch_operand(); - eval_eor(operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_sre() -> void - { - // SRE: LSR + EOR - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_lsr(operand.read()); - operand.write(tmp); - eval_eor(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - } - - template - auto cpu::run_lsr() -> void - { - // LSR: Logical Shift Right - auto operand = fetch_operand(); - auto const old_val = operand.read(); - auto const new_val = static_cast(old_val >> 1); - operand.write(new_val); - registers_.c = (old_val & 0x1) != 0; - update_zn(new_val); - current_cycles_ += shift_cycle_count(); - } - - template - auto cpu::run_alr() -> void - { - // ALR: AND #i + LSR A - auto operand = fetch_operand(); - eval_and(operand.read()); - registers_.a = eval_lsr(registers_.a); - current_cycles_ += operand.get_cycles(); - } - - auto cpu::run_pha() -> void - { - // PHA: Push Accumulator - push_stack8(registers_.a); - current_cycles_ += cycle_count::from_cpu(3); - } - - template - auto cpu::run_jmp() -> void - { - // JMP: Jump - auto operand = fetch_operand(); - registers_.pc = operand.get_address().get_absolute(); - current_cycles_ += jump_cycle_count(); - } - - template - auto cpu::run_bvc() -> void - { - // BVC: Branch If Overflow Clear - branch(!registers_.v); - } - - auto cpu::run_cli() -> void - { - // CLI: Clear Interrupt Disable - registers_.i = false; - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_rts() -> void - { - // RTS: Return from Subroutine - auto const addr = address{ pop_stack16() } + 1; - registers_.pc = addr.get_absolute(); - current_cycles_ += cycle_count::from_cpu(6); - } - - template - auto cpu::run_adc() -> void - { - // ADC: Add With Carry - auto operand = fetch_operand(); - eval_adc(operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_rra() -> void - { - // RRA: ROR + ADC - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = eval_ror(operand.read()); - operand.write(tmp); - eval_adc(tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - } - - template - auto cpu::run_ror() -> void - { - // ROR: Rotate Right - auto operand = fetch_operand(); - auto const result = eval_ror(operand.read()); - operand.write(result); - current_cycles_ += shift_cycle_count(); - } - - template - auto cpu::run_arr() -> void - { - // ARR: AND #i + ROR A but with different flags - // TODO - std::abort(); - } - - auto cpu::run_pla() -> void - { - // PLA: Pull Accumulator - registers_.a = pop_stack8(); - update_zn(registers_.a); - current_cycles_ += cycle_count::from_cpu(4); - } - - template - auto cpu::run_bvs() -> void - { - // BVS: Branch If Overflow Set - branch(registers_.v); - } - - auto cpu::run_sei() -> void - { - // SEI: Set Interrupt Disable - registers_.i = true; - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_sta() -> void - { - // STA: Store Accumulator - auto operand = fetch_operand(force_page_crossing::yes); - operand.write(registers_.a); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_ahx() -> void - { - // AHX: ??? - // TODO - std::abort(); - } - - template - auto cpu::run_sax() -> void - { - // SAX: AND A X - auto operand = fetch_operand(); - auto const result = static_cast(registers_.a & registers_.x); - operand.write(result); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_sty() -> void - { - // STX: Store Y - auto operand = fetch_operand(); - operand.write(registers_.y); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_stx() -> void - { - // STX: Store X - auto operand = fetch_operand(); - operand.write(registers_.x); - current_cycles_ += operand.get_cycles(); - } - - auto cpu::run_dey() -> void - { - // DEY: Decrement Y Register - registers_.y -= 1; - update_zn(registers_.y); - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_txa() -> void - { - // TXA: Transfer X to Accumulator - registers_.a = registers_.x; - update_zn(registers_.a); - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_xaa() -> void - { - // XAA: Depends on analog behavior. - run_stp(); - } - - template - auto cpu::run_bcc() -> void - { - // BCC: Branch If Carry Clear - branch(!registers_.c); - } - - auto cpu::run_tya() -> void - { - // TYA: Transfer Y to Accumulator - registers_.a = registers_.y; - update_zn(registers_.a); - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_txs() -> void - { - // TXS: Transfer X to Stack Pointer - registers_.sp = registers_.x; - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_tas() -> void - { - // TAS: ??? - // TODO - std::abort(); - } - - template - auto cpu::run_shy() -> void - { - // SHY: ??? - // TODO - std::abort(); - } - - template - auto cpu::run_shx() -> void - { - // SHY: ??? - // TODO - std::abort(); - } - - template - auto cpu::run_ldy() -> void - { - // LDY: Load Y Register - auto operand = fetch_operand(); - registers_.y = operand.read(); - update_zn(registers_.y); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_lda() -> void - { - // LDA: Load Accumulator - auto operand = fetch_operand(); - registers_.a = operand.read(); - update_zn(registers_.a); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_ldx() -> void - { - // LDX: Load X Register - auto operand = fetch_operand(); - registers_.x = operand.read(); - update_zn(registers_.x); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_lax() -> void - { - // LAX: LDA + TAX - auto operand = fetch_operand(); - registers_.x = registers_.a = operand.read(); - update_zn(registers_.a); - current_cycles_ += operand.get_cycles(); - } - - auto cpu::run_tay() -> void - { - // TAY: Transfer Accumulator to Y - registers_.y = registers_.a; - update_zn(registers_.y); - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_tax() -> void - { - // TAX: Transfer Accumulator to X - registers_.x = registers_.a; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_bcs() -> void - { - // BCS: Branch If Carry Set - branch(registers_.c); - } - - auto cpu::run_clv() -> void - { - // CLV: Clear Overflow Flag - registers_.v = false; - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_tsx() -> void - { - // TSX: Transfer Stack Pointer to X - registers_.x = registers_.sp; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_las() -> void - { - // LAS: ??? - // TODO - std::abort(); - } - - template - auto cpu::run_cpy() -> void - { - // CPY: Compare Y Register - auto operand = fetch_operand(); - eval_cmp(registers_.y, operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_axs() -> void - { - // AXS: Set X to ((A AND X) - #value without borrow), updates NZC - // TODO - std::abort(); - } - - template - auto cpu::run_cmp() -> void - { - // CMP: Compare - auto operand = fetch_operand(); - eval_cmp(registers_.a, operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_dcp() -> void - { - // DCP: DEC + CMP - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = static_cast(operand.read() - 1); - operand.write(tmp); - eval_cmp(registers_.a, tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - } - - template - auto cpu::run_dec() -> void - { - // DEC: Decrement Memory - auto operand = fetch_operand(); - auto const old_val = read8(operand.get_address()); - auto const new_val = static_cast(old_val - 1); - write8(operand.get_address(), new_val); - update_zn(new_val); - current_cycles_ += inc_dec_cycle_count(); - } - - auto cpu::run_iny() -> void - { - // INY: Increment Y - registers_.y += 1; - update_zn(registers_.y); - current_cycles_ += cycle_count::from_cpu(2); - } - - auto cpu::run_dex() -> void - { - // DEX: Decrement X Register - registers_.x -= 1; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_bne() -> void - { - // BNE: Branch If Not Equal - branch(!registers_.z); - } - - auto cpu::run_cld() -> void - { - // CLD: Clear Decimal Mode - registers_.d = false; - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_cpx() -> void - { - // CPX: Compare X Register - auto operand = fetch_operand(); - eval_cmp(registers_.x, operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_sbc() -> void - { - // SBC: Subtract With Carry - auto operand = fetch_operand(); - eval_adc(~operand.read()); - current_cycles_ += operand.get_cycles(); - } - - template - auto cpu::run_isc() -> void - { - // ISC: INC + SBC - auto operand = fetch_operand(force_page_crossing::yes); - auto const tmp = static_cast(operand.read() + 1); - operand.write(tmp); - eval_adc(~tmp); - current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); - } - - template - auto cpu::run_inc() -> void - { - // INC: Increment Memory - auto operand = fetch_operand(); - auto const old_val = read8(operand.get_address()); - auto const new_val = static_cast(old_val + 1); - write8(operand.get_address(), new_val); - update_zn(new_val); - current_cycles_ += inc_dec_cycle_count(); - } - - auto cpu::run_inx() -> void - { - // INX: Increment X - registers_.x += 1; - update_zn(registers_.x); - current_cycles_ += cycle_count::from_cpu(2); - } - - template - auto cpu::run_beq() -> void - { - // BEQ: Branch If Equal - branch(registers_.z); - } - - auto cpu::run_sed() -> void - { - // SED: Set Decimal Flag - registers_.d = true; - current_cycles_ += cycle_count::from_cpu(2); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Helpers - // ----------------------------------------------------------------------------------------------------------------- - - auto cpu::advance_pc8() -> std::uint8_t - { - auto const val = read8(address{ registers_.pc }); - registers_.pc += 1; - return val; - } - - auto cpu::advance_pc16() -> std::uint16_t - { - auto const val = read16(address{ registers_.pc }); - registers_.pc += 2; - return val; - } - - auto cpu::push_stack8(std::uint8_t const value) -> void - { - write8(stack_offset + registers_.sp, value); - registers_.sp -= 1; - } - - auto cpu::push_stack16(std::uint16_t const value) -> void - { - auto const low = static_cast(value >> 0 & 0xFF); - auto const high = static_cast(value >> 8 & 0xFF); - push_stack8(high); - push_stack8(low); - } - - auto cpu::pop_stack8() -> std::uint8_t - { - registers_.sp += 1; - return read8(stack_offset + registers_.sp); - } - - auto cpu::pop_stack16() -> std::uint16_t - { - auto const low = pop_stack8(); - auto const high = pop_stack8(); - return static_cast((high << 8) | (low << 0)); - } - - auto cpu::update_zn(std::uint8_t const value) -> void - { - registers_.z = value == 0; - registers_.n = (value & 0x80) != 0; - } - - template - auto cpu::branch(bool const condition) -> void - { - static_assert(Mode == addressing_mode::relative, "Branch should only be used with relative operands."); - - auto operand = fetch_operand(); - if (condition) - { - registers_.pc = operand.get_address().get_absolute(); - current_cycles_ += operand.get_cycles(); - } - else - { - current_cycles_ += cycle_count::from_cpu(2); - } - } - - auto cpu::execute_interrupt(address const vector) -> void - { - push_stack16(registers_.pc); - eval_php(); - registers_.pc = read16(vector); - registers_.i = true; - current_cycles_ += cycle_count::from_cpu(7); - } - - auto cpu::eval_ror(std::uint8_t const arg) -> std::uint8_t - { - auto const res = static_cast((registers_.c << 7) | (arg >> 1)); - registers_.c = (arg & 0x1) != 0; - update_zn(res); - return res; - } - - auto cpu::eval_rol(std::uint8_t const arg) -> std::uint8_t - { - auto const res = static_cast((arg << 1) | (registers_.c ? 1 : 0)); - registers_.c = (arg & 0x80) != 0; - update_zn(res); - return res; - } - - auto cpu::eval_asl(std::uint8_t const arg) -> std::uint8_t - { - auto const new_val = static_cast(arg << 1); - registers_.c = (arg & 0x80) != 0; - update_zn(new_val); - return new_val; - } - - auto cpu::eval_lsr(std::uint8_t const arg) -> std::uint8_t - { - auto const new_val = static_cast(arg >> 1); - registers_.c = (arg & 0x01) != 0; - update_zn(new_val); - return new_val; - } - - auto cpu::eval_adc(std::uint8_t const arg) -> void - { - auto const old_val = registers_.a; - auto const tmp = old_val + arg + (registers_.c ? 1 : 0); - auto const new_val = static_cast(tmp); - - registers_.a = new_val; - registers_.c = tmp > 0xFF; - registers_.v = ((old_val ^ arg) & 0x80) == 0 && ((old_val ^ new_val) & 0x80) != 0; - update_zn(registers_.a); - } - - auto cpu::eval_and(std::uint8_t const arg) -> void - { - registers_.a &= arg; - update_zn(registers_.a); - } - - auto cpu::eval_ora(std::uint8_t const arg) -> void - { - registers_.a |= arg; - update_zn(registers_.a); - } - - auto cpu::eval_eor(std::uint8_t const arg) -> void - { - registers_.a ^= arg; - update_zn(registers_.a); - } - - auto cpu::eval_cmp(std::uint8_t const a, std::uint8_t const b) -> void - { - update_zn(a - b); - registers_.c = a >= b; - } - - auto cpu::eval_plp() -> void - { - // Ignore bits 4 and 5 - registers_.p = - (pop_stack8() & 0b11001111) | - (registers_.p & 0b00110000); - } - - auto cpu::eval_php() -> void - { - // Always set bits 4 and 5. - push_stack8(registers_.p | 0b00110000); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Operands - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/CPU_addressing_modes - // - - template - auto cpu::fetch_operand(force_page_crossing const force_page_crossing) -> operand - { - auto addr = address{}; - auto cycles = cycle_count{}; - - switch (Mode) - { - case addressing_mode::immediate: static_assert(Mode != addressing_mode::immediate); - case addressing_mode::accumulator: static_assert(Mode != addressing_mode::accumulator); - case addressing_mode::zero_page: - { - addr = address{ advance_pc8() }; - cycles = cycle_count::from_cpu(3); - break; - } - case addressing_mode::absolute: - { - addr = address{ advance_pc16() }; - cycles = cycle_count::from_cpu(4); - break; - } - case addressing_mode::relative: - { - auto const offset = static_cast(advance_pc8()); - auto const base = address{ registers_.pc }; - addr = address{ static_cast(registers_.pc + offset) }; - auto const page_crossing = - base.get_page() != addr.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 4 : 3); - break; - } - case addressing_mode::indirect: - { - auto const arg = address{ advance_pc16() }; - // Account for buggy indirect read: https://www.nesdev.org/wiki/Errata - addr = arg.get_offset() == 0xFF - ? address{ read8(address{ arg.get_page(), 0x00 }), read8(arg) } - : address{ read16(arg) }; - cycles = cycle_count::from_cpu(6); - break; - } - case addressing_mode::zero_page_indexed_x: - { - // PEEK((arg + X) % 256) - addr = address{ 0x00, static_cast(advance_pc8() + registers_.x) }; - cycles = cycle_count::from_cpu(4); - break; - } - case addressing_mode::zero_page_indexed_y: - { - // PEEK((arg + Y) % 256) - addr = address{ 0x00, static_cast(advance_pc8() + registers_.y) }; - cycles = cycle_count::from_cpu(4); - break; - } - case addressing_mode::absolute_indexed_x: - { - // PEEK(arg + X) - auto const arg = address{ advance_pc16() }; - addr = arg + registers_.x; - auto const page_crossing = - addr.get_page() != arg.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 5 : 4); - break; - } - case addressing_mode::absolute_indexed_y: - { - // PEEK(arg + Y) - auto const arg = address{ advance_pc16() }; - addr = arg + registers_.y; - auto const page_crossing = - addr.get_page() != arg.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 5 : 4); - break; - } - case addressing_mode::indexed_indirect: - { - // PEEK(PEEK((arg + X) % 256) + PEEK((arg + X + 1) % 256) * 256) - auto const arg = advance_pc8(); - auto const page = read8(address{ 0x00, static_cast(arg + registers_.x + 1) }); - auto const offset = read8(address{ 0x00, static_cast(arg + registers_.x) }); - addr = address{ page, offset }; - cycles = cycle_count::from_cpu(6); - break; - } - case addressing_mode::indirect_indexed: - { - // PEEK(PEEK(arg) + PEEK((arg + 1) % 256) * 256 + Y) - auto const arg = advance_pc8(); - auto const page = read8(address{ 0x00, static_cast(arg + 1) }); - auto const offset = read8(address{ 0x00, static_cast(arg) }); - auto const base = address{ page, offset }; - addr = base + registers_.y; - auto const page_crossing = - addr.get_page() != base.get_page() || force_page_crossing == force_page_crossing::yes; - cycles = cycle_count::from_cpu(page_crossing ? 6 : 5); - break; - } - } - - return operand{ *this, addr, cycles }; - } - - template<> - auto cpu::fetch_operand(force_page_crossing) - -> operand - { - return operand{ *this }; - } - - template<> - auto cpu::fetch_operand(force_page_crossing) - -> operand - { - return operand{ advance_pc8() }; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Memory Access - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/CPU_memory_map - // - - auto cpu::read8(address const addr) -> std::uint8_t - { - if (addr <= address{ 0x1FFF }) { return ram_[addr.get_absolute() % ram_size]; } - if (addr <= address{ 0x3FFF }) - { -#ifdef NES_ENABLE_PPU - switch (addr.get_absolute() % 8) - { - case 0: return ppu_.read_latch(); - case 1: return ppu_.read_latch(); - case 2: return ppu_.read_ppustatus(); - case 3: return ppu_.read_latch(); - case 4: return ppu_.read_oamdata(); - case 5: return ppu_.read_latch(); - case 6: return ppu_.read_latch(); - case 7: return ppu_.read_ppudata(); - default: return 0x0; - } -#else - return 0x0; -#endif - } - if (addr <= address{ 0x4013 }) { return 0x0; } // TODO: APU registers -#ifdef NES_ENABLE_PPU - if (addr == address{ 0x4014 }) { return ppu_.read_latch(); } -#else - if (addr == address{ 0x4014 }) { return 0x0; } -#endif - if (addr == address{ 0x4015 }) { return 0x0; } // TODO: APU registers - if (addr == address{ 0x4016 }) { return controller_1_.read(); } - if (addr == address{ 0x4017 }) { return controller_2_.read(); } - if (addr <= address{ 0x401F }) { return 0x0; } - return mapper_.read_cpu(addr); - } - - auto cpu::read16(address const addr) -> std::uint16_t - { - auto const low = read8(addr + 0); - auto const high = read8(addr + 1); - return static_cast((high << 8) | (low << 0)); - } - - auto cpu::write8(address const addr, std::uint8_t const value) -> void - { - if (addr <= address{ 0x1FFF }) { ram_[addr.get_absolute() % ram_size] = value; return; } - if (addr <= address{ 0x3FFF }) - { -#ifdef NES_ENABLE_PPU - switch (addr.get_absolute() % 8) - { - case 0: ppu_.write_ppuctrl(value); return; - case 1: ppu_.write_ppumask(value); return; - case 2: ppu_.write_latch(value); return; - case 3: ppu_.write_oamaddr(value); return; - case 4: ppu_.write_oamdata(value); return; - case 5: ppu_.write_ppuscroll(value); return; - case 6: ppu_.write_ppuaddr(value); return; - case 7: ppu_.write_ppudata(value); return; - default: return; - } -#else - return; -#endif - } - if (addr <= address{ 0x4013 }) { return; } // TODO: APU registers -#ifdef NES_ENABLE_PPU - if (addr == address{ 0x4014 }) { ppu_.write_oamdma(value); return; } -#else - if (addr == address{ 0x4014 }) { return; } -#endif - if (addr == address{ 0x4015 }) { return; } // TODO: APU registers - if (addr == address{ 0x4016 }) { controller_1_.write(value); return; } - if (addr == address{ 0x4017 }) { controller_2_.write(value); return; } - if (addr <= address{ 0x401F }) { return; } - mapper_.write_cpu(addr, value); - } - - auto cpu::write16(address const addr, std::uint16_t const value) -> void - { - auto const low = static_cast(value >> 0 & 0xFF); - auto const high = static_cast(value >> 8 & 0xFF); - write8(addr + 0, low); - write8(addr + 1, high); - } -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/cpu.hh b/lib/nes/ref/cpu.hh deleted file mode 100644 index 05efe56..0000000 --- a/lib/nes/ref/cpu.hh +++ /dev/null @@ -1,298 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" -#include "nes/util/address.hh" - -namespace nes -{ - struct snapshot; -} // namespace nes - -namespace nes::ref -{ - class ppu; - class controller; - class mapper; - - class cpu - { - static constexpr auto ram_size = std::size_t{ 0x800 }; - static constexpr auto stack_offset = address{ 0x100 }; - - cycle_count current_cycles_; - ppu& ppu_; - mapper& mapper_; - controller& controller_1_; - controller& controller_2_; - std::uint8_t ram_[ram_size]{}; - bool nmi_pending_{ false }; - - // Registers - struct - { - std::uint16_t pc{ 0 }; - std::uint8_t sp{ 0xFD }; - std::uint8_t a{ 0 }; - std::uint8_t x{ 0 }; - std::uint8_t y{ 0 }; - union - { - struct - { - bool c : 1; // carry - bool z : 1; // zero - bool i : 1; // interrupt inhibit - bool d : 1; // decimal - bool b : 1; // break - bool : 1; // (unused, always true) - bool v : 1; // overflow - bool n : 1; // negative - }; - std::uint8_t p{ 0b00100100 }; // initially, set i = true - }; - } registers_{}; - - enum class addressing_mode - { - accumulator, // A - immediate, // #v - zero_page, // d - absolute, // a - relative, // *+d - indirect, // (a) - zero_page_indexed_x, // d,x - zero_page_indexed_y, // d,y - absolute_indexed_x, // a,x - absolute_indexed_y, // a,y - indexed_indirect, // (d,x) - indirect_indexed, // (d),y - }; - - enum class force_page_crossing - { - no, - yes, - }; - - template - class operand - { - cpu& cpu_; - address address_; - cycle_count cycles_; - - public: - explicit operand(cpu& cpu, address const address, cycle_count const cycles) - : cpu_{ cpu } - , address_{ address } - , cycles_{ cycles } - { - } - - auto read() -> std::uint8_t { return cpu_.read8(address_); } - auto write(std::uint8_t const value) -> void { cpu_.write8(address_, value); } - auto get_address() -> address { return address_; } - auto get_cycles() -> cycle_count { return cycles_; } - }; - - template<> - class operand - { - cpu& cpu_; - - public: - explicit operand(cpu& cpu) - : cpu_{ cpu } - { - } - - auto read() -> std::uint8_t { return cpu_.registers_.a; } - auto write(std::uint8_t const value) -> void { cpu_.registers_.a = value; } - auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); } - }; - - template<> - class operand - { - std::uint8_t value_{ 0 }; - - public: - explicit operand(std::uint8_t const value) - : value_{ value } - { - } - - auto read() -> std::uint8_t { return value_; } - auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); } - }; - - public: - explicit cpu(ppu&, mapper&, controller& controller_1, controller& controller_2); - - cpu(cpu const&) = delete; - cpu(cpu&&) = delete; - auto operator=(cpu const&) -> cpu& = delete; - auto operator=(cpu&&) -> cpu& = delete; - - auto get_cycles() const -> cycle_count { return current_cycles_; } - auto build_snapshot(snapshot&) -> void; - auto stall_cycles(cycle_count) -> void; - auto trigger_nmi() -> void; - auto step() -> void; - auto is_nmi_pending() const -> bool { return nmi_pending_; } // XXX: Debugging - - // Memory access - - auto read8(address) -> std::uint8_t; - auto read16(address) -> std::uint16_t; - auto write8(address, std::uint8_t) -> void; - auto write16(address, std::uint16_t) -> void; - - private: - // Instructions - -#define DEFINE_SIMPLE_INSTRUCTION(name) \ - auto run_##name() -> void; -#define DEFINE_OPERAND_INSTRUCTION(name) \ - template auto run_##name() -> void; - - DEFINE_OPERAND_INSTRUCTION(brk) - DEFINE_OPERAND_INSTRUCTION(ora) - DEFINE_SIMPLE_INSTRUCTION(stp) - DEFINE_OPERAND_INSTRUCTION(slo) - DEFINE_OPERAND_INSTRUCTION(nop) - DEFINE_SIMPLE_INSTRUCTION(nop) - DEFINE_SIMPLE_INSTRUCTION(php) - DEFINE_OPERAND_INSTRUCTION(asl) - DEFINE_OPERAND_INSTRUCTION(anc) - DEFINE_OPERAND_INSTRUCTION(bpl) - DEFINE_SIMPLE_INSTRUCTION(clc) - DEFINE_OPERAND_INSTRUCTION(jsr) - DEFINE_OPERAND_INSTRUCTION(and) - DEFINE_OPERAND_INSTRUCTION(rla) - DEFINE_OPERAND_INSTRUCTION(bit) - DEFINE_OPERAND_INSTRUCTION(rol) - DEFINE_SIMPLE_INSTRUCTION(plp) - DEFINE_OPERAND_INSTRUCTION(bmi) - DEFINE_SIMPLE_INSTRUCTION(sec) - DEFINE_SIMPLE_INSTRUCTION(rti) - DEFINE_OPERAND_INSTRUCTION(eor) - DEFINE_OPERAND_INSTRUCTION(sre) - DEFINE_OPERAND_INSTRUCTION(lsr) - DEFINE_OPERAND_INSTRUCTION(alr) - DEFINE_SIMPLE_INSTRUCTION(pha) - DEFINE_OPERAND_INSTRUCTION(jmp) - DEFINE_OPERAND_INSTRUCTION(bvc) - DEFINE_SIMPLE_INSTRUCTION(cli) - DEFINE_SIMPLE_INSTRUCTION(rts) - DEFINE_OPERAND_INSTRUCTION(adc) - DEFINE_OPERAND_INSTRUCTION(rra) - DEFINE_OPERAND_INSTRUCTION(ror) - DEFINE_OPERAND_INSTRUCTION(arr) - DEFINE_SIMPLE_INSTRUCTION(pla) - DEFINE_OPERAND_INSTRUCTION(bvs) - DEFINE_SIMPLE_INSTRUCTION(sei) - DEFINE_OPERAND_INSTRUCTION(sta) - DEFINE_OPERAND_INSTRUCTION(ahx) - DEFINE_OPERAND_INSTRUCTION(sax) - DEFINE_OPERAND_INSTRUCTION(sty) - DEFINE_OPERAND_INSTRUCTION(stx) - DEFINE_SIMPLE_INSTRUCTION(dey) - DEFINE_SIMPLE_INSTRUCTION(txa) - DEFINE_OPERAND_INSTRUCTION(xaa) - DEFINE_OPERAND_INSTRUCTION(bcc) - DEFINE_SIMPLE_INSTRUCTION(tya) - DEFINE_SIMPLE_INSTRUCTION(txs) - DEFINE_OPERAND_INSTRUCTION(tas) - DEFINE_OPERAND_INSTRUCTION(shy) - DEFINE_OPERAND_INSTRUCTION(shx) - DEFINE_OPERAND_INSTRUCTION(ldy) - DEFINE_OPERAND_INSTRUCTION(lda) - DEFINE_OPERAND_INSTRUCTION(ldx) - DEFINE_OPERAND_INSTRUCTION(lax) - DEFINE_SIMPLE_INSTRUCTION(tay) - DEFINE_SIMPLE_INSTRUCTION(tax) - DEFINE_OPERAND_INSTRUCTION(bcs) - DEFINE_SIMPLE_INSTRUCTION(clv) - DEFINE_SIMPLE_INSTRUCTION(tsx) - DEFINE_OPERAND_INSTRUCTION(las) - DEFINE_OPERAND_INSTRUCTION(cpy) - DEFINE_OPERAND_INSTRUCTION(axs) - DEFINE_OPERAND_INSTRUCTION(cmp) - DEFINE_OPERAND_INSTRUCTION(dcp) - DEFINE_OPERAND_INSTRUCTION(dec) - DEFINE_SIMPLE_INSTRUCTION(iny) - DEFINE_SIMPLE_INSTRUCTION(dex) - DEFINE_OPERAND_INSTRUCTION(bne) - DEFINE_SIMPLE_INSTRUCTION(cld) - DEFINE_OPERAND_INSTRUCTION(cpx) - DEFINE_OPERAND_INSTRUCTION(sbc) - DEFINE_OPERAND_INSTRUCTION(isc) - DEFINE_OPERAND_INSTRUCTION(inc) - DEFINE_SIMPLE_INSTRUCTION(inx) - DEFINE_OPERAND_INSTRUCTION(beq) - DEFINE_SIMPLE_INSTRUCTION(sed) - -#undef DEFINE_SIMPLE_INSTRUCTION -#undef DEFINE_OPERAND_INSTRUCTION - - // Helpers - - auto advance_pc8() -> std::uint8_t; - auto advance_pc16() -> std::uint16_t; - auto push_stack8(std::uint8_t) -> void; - auto push_stack16(std::uint16_t) -> void; - auto pop_stack8() -> std::uint8_t; - auto pop_stack16() -> std::uint16_t; - auto update_zn(std::uint8_t value) -> void; - template - auto branch(bool condition) -> void; - auto execute_interrupt(address) -> void; - - auto eval_ror(std::uint8_t arg) -> std::uint8_t; - auto eval_rol(std::uint8_t arg) -> std::uint8_t; - auto eval_asl(std::uint8_t arg) -> std::uint8_t; - auto eval_lsr(std::uint8_t arg) -> std::uint8_t; - auto eval_adc(std::uint8_t arg) -> void; - auto eval_and(std::uint8_t arg) -> void; - auto eval_ora(std::uint8_t arg) -> void; - auto eval_eor(std::uint8_t arg) -> void; - auto eval_cmp(std::uint8_t a, std::uint8_t b) -> void; - auto eval_plp() -> void; - auto eval_php() -> void; - - // Operands - -#define DEFINE_CYCLE_COUNT(name) \ - template static auto name##_cycle_count() -> cycle_count; -#define DEFINE_CYCLE_COUNT_INSTANCE(name, mode, value) \ - template<> auto name##_cycle_count() -> cycle_count { return cycle_count::from_cpu(value); } - - DEFINE_CYCLE_COUNT(jump) - DEFINE_CYCLE_COUNT_INSTANCE(jump, absolute, 3) - DEFINE_CYCLE_COUNT_INSTANCE(jump, indirect, 5) - - DEFINE_CYCLE_COUNT(shift) - DEFINE_CYCLE_COUNT_INSTANCE(shift, accumulator, 2) - DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page, 5) - DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page_indexed_x, 6) - DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute, 6) - DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute_indexed_x, 7) - - DEFINE_CYCLE_COUNT(inc_dec) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page, 5) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page_indexed_x, 6) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute, 6) - DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute_indexed_x, 7) - -#undef DEFINE_CYCLE_COUNT -#undef DEFINE_CYCLE_COUNT_INSTANCE - - template - auto fetch_operand(force_page_crossing = force_page_crossing::no) -> operand; - template<> - auto fetch_operand(force_page_crossing) -> operand; - template<> - auto fetch_operand(force_page_crossing) -> operand; - }; -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/mapper.cc b/lib/nes/ref/mapper.cc deleted file mode 100644 index 7e4cffe..0000000 --- a/lib/nes/ref/mapper.cc +++ /dev/null @@ -1,134 +0,0 @@ -#include "nes/ref/mapper.hh" -#include "nes/ref/cartridge.hh" -#include - -namespace nes::ref -{ - mapper::mapper( - cartridge& cartridge, - read_cpu_func const read_cpu, write_cpu_func const write_cpu, - read_ppu_func const read_ppu, write_ppu_func const write_ppu) - : cartridge_{ cartridge } - , read_cpu_{ read_cpu } - , write_cpu_{ write_cpu } - , read_ppu_{ read_ppu } - , write_ppu_{ write_ppu } - { - } - - auto mapper::select(cartridge& cartridge) -> mapper - { - switch (cartridge.get_mapper_number()) - { - case 0x00: - return mapper{ - cartridge, - &mapper::nrom_read_cpu, &mapper::nrom_write_cpu, - &mapper::nrom_read_ppu, &mapper::nrom_write_ppu }; - default: - std::cerr << "Mapper not implemented: " << std::hex << cartridge.get_mapper_number() << std::endl; - std::abort(); - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // NROM - // ----------------------------------------------------------------------------------------------------------------- - - auto mapper::nrom_read_cpu(address const addr) -> std::uint8_t - { - if (addr <= address{ 0x5FFF }) - { - return 0x0; - } - if (addr <= address{ 0x7FFF }) - { - return cartridge_.get_ram()[addr.get_absolute() - 0x6000]; - } - if (addr <= address{ 0xBFFF }) - { - auto const rel = addr.get_absolute() - 0x8000; - if (rel >= cartridge_.get_prg_rom_length()) { return 0x0; } - - return cartridge_.get_prg_rom()[rel]; - } - if (addr <= address{ 0xFFFF }) - { - auto rel = addr.get_absolute() - 0xC000; - if (cartridge_.get_prg_rom_length() > 0x4000) { rel += 0x4000; } - if (rel >= cartridge_.get_prg_rom_length()) { return 0x0; } - - return cartridge_.get_prg_rom()[rel]; - } - - return 0x0; - } - - auto mapper::nrom_write_cpu(address const addr, std::uint8_t const value) -> void - { - if (addr <= address{ 0x5FFF }) - { - return; - } - if (addr <= address{ 0x7FFF }) - { - cartridge_.get_ram_mut()[addr.get_absolute() - 0x6000] = value; - return; - } - } - - auto mapper::nrom_read_ppu(address const addr, std::uint8_t const* vram) -> std::uint8_t - { - if (addr <= address{ 0x1FFF }) - { - auto const rel = addr.get_absolute(); - if (rel >= cartridge_.get_chr_rom_length()) { return 0x0; } - - return cartridge_.get_chr_rom()[rel]; - } - if (addr <= address{ 0x3EFF }) - { - return vram[nrom_mirrored_vram_address(addr)]; - } - - return 0x0; - } - - auto mapper::nrom_write_ppu(address const addr, std::uint8_t* vram, std::uint8_t const value) -> void - { - if (addr <= address{ 0x1FFF }) - { - return; - } - if (addr <= address{ 0x3EFF }) - { - vram[nrom_mirrored_vram_address(addr)] = value; - return; - } - } - - auto mapper::nrom_mirrored_vram_address(address const addr) const -> unsigned - { - auto const rel = addr.get_absolute() % 0x1000; - - switch (cartridge_.get_name_table_arrangement()) - { - case name_table_arrangement::horizontal: - // $2000 = $2400, $2800 = $2c00 - if (rel <= 0x3FF) { return 0x000 + (rel % 0x400); } - if (rel <= 0x7FF) { return 0x000 + (rel % 0x400); } - if (rel <= 0xBFF) { return 0x400 + (rel % 0x400); } - if (rel <= 0xFFF) { return 0x400 + (rel % 0x400); } - break; - case name_table_arrangement::vertical: - // $2000 = $2800, $2400 = $2c00 - if (rel <= 0x3FF) { return 0x000 + (rel % 0x400); } - if (rel <= 0x7FF) { return 0x400 + (rel % 0x400); } - if (rel <= 0xBFF) { return 0x000 + (rel % 0x400); } - if (rel <= 0xFFF) { return 0x400 + (rel % 0x400); } - break; - } - - return 0; - } -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/mapper.hh b/lib/nes/ref/mapper.hh deleted file mode 100644 index 671e1d4..0000000 --- a/lib/nes/ref/mapper.hh +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include "nes/util/address.hh" - -namespace nes::ref -{ - class cartridge; - - class mapper - { - using read_cpu_func = std::uint8_t(mapper::*)(address); - using write_cpu_func = void(mapper::*)(address, std::uint8_t); - using read_ppu_func = std::uint8_t(mapper::*)(address, std::uint8_t const* vram); - using write_ppu_func = void(mapper::*)(address, std::uint8_t* vram, std::uint8_t); - - cartridge& cartridge_; - read_cpu_func read_cpu_; - write_cpu_func write_cpu_; - read_ppu_func read_ppu_; - write_ppu_func write_ppu_; - - public: - static auto select(cartridge&) -> mapper; - - mapper(mapper const&) = delete; - mapper(mapper&&) = delete; - auto operator=(mapper const&) -> mapper& = delete; - auto operator=(mapper&&) -> mapper& = delete; - - auto read_cpu(address const addr) -> std::uint8_t - { - return (this->*read_cpu_)(addr); - } - - auto write_cpu(address const addr, std::uint8_t const value) -> void - { - (this->*write_cpu_)(addr, value); - } - - auto read_ppu(address const addr, std::uint8_t const* vram) -> std::uint8_t - { - return (this->*read_ppu_)(addr, vram); - } - - auto write_ppu(address const addr, std::uint8_t* vram, std::uint8_t const value) -> void - { - (this->*write_ppu_)(addr, vram, value); - } - - private: - explicit mapper( - cartridge& cartridge, - read_cpu_func read_cpu, write_cpu_func write_cpu, - read_ppu_func read_ppu, write_ppu_func write_ppu); - - // Implementations - - auto nrom_read_cpu(address) -> std::uint8_t; - auto nrom_write_cpu(address, std::uint8_t) -> void; - auto nrom_read_ppu(address, std::uint8_t const* vram) -> std::uint8_t; - auto nrom_write_ppu(address, std::uint8_t* vram, std::uint8_t) -> void; - auto nrom_mirrored_vram_address(address) const -> unsigned; - }; -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/nes.cc b/lib/nes/ref/nes.cc deleted file mode 100644 index e34ca9a..0000000 --- a/lib/nes/ref/nes.cc +++ /dev/null @@ -1,74 +0,0 @@ -#include "nes/ref/nes.hh" -#include "nes/ref/mapper.hh" -#include "nes/util/display.hh" -#include "nes/util/rgb.hh" -#include "nes/util/snapshot.hh" -#include - -namespace nes::ref -{ - nes::nes(cartridge cartridge, display& display) - : cartridge_{ std::move(cartridge) } - , display_{ display } - , mapper_{ mapper::select(cartridge_) } - , ppu_{ cpu_, mapper_, display_ } - , cpu_{ ppu_, mapper_, controller_1_, controller_2_ } - { - if (!cartridge_.is_valid()) - { - std::cerr << "Cartridge not valid!" << std::endl; - std::abort(); - } - - display_.clear(rgb{}); - display_.switch_buffers(); - display_.clear(rgb{}); - } - - auto nes::step(std::chrono::microseconds const delta) -> void - { - step(cycle_count::from_duration(delta)); - } - - auto nes::step(cycle_count const delta) -> void - { - current_cycles_ += delta; - while (cpu_.get_cycles() < current_cycles_) - { - cpu_.step(); - while (ppu_.get_cycles() < cpu_.get_cycles()) - { - ppu_.step(); - } - } - } - - auto nes::step_to_nmi() -> void - { - while (cpu_.is_nmi_pending()) - { - cpu_.step(); - while (ppu_.get_cycles() < cpu_.get_cycles()) - { - ppu_.step(); - } - } - while (!cpu_.is_nmi_pending()) - { - cpu_.step(); - while (ppu_.get_cycles() < cpu_.get_cycles()) - { - ppu_.step(); - } - } - } - - auto nes::get_snapshot() -> snapshot - { - auto res = snapshot{}; - res.sram = std::vector(cartridge_.get_ram(), cartridge_.get_ram() + cartridge_.get_ram_length()); - cpu_.build_snapshot(res); - ppu_.build_snapshot(res); - return res; - } -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/nes.hh b/lib/nes/ref/nes.hh deleted file mode 100644 index 3df8646..0000000 --- a/lib/nes/ref/nes.hh +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" -#include "nes/ref/cartridge.hh" -#include "nes/ref/controller.hh" -#include "nes/ref/cpu.hh" -#include "nes/ref/ppu.hh" -#include "nes/ref/mapper.hh" - -namespace nes -{ - class display; - struct snapshot; -} // namespace nes - -namespace nes::ref -{ - /// The main console abstraction. - class nes - { - cartridge cartridge_; - display& display_; - controller controller_1_; - controller controller_2_; - mapper mapper_; - ppu ppu_; - cpu cpu_; - cycle_count current_cycles_; - - public: - explicit nes(cartridge, display&); - - nes(nes const&) = delete; - nes(nes&&) = delete; - auto operator=(nes const&) -> nes& = delete; - auto operator=(nes&&) -> nes& = delete; - - auto get_controller_1() const -> controller const& { return controller_1_; } - auto get_controller_1() -> controller& { return controller_1_; } - auto get_controller_2() const -> controller const& { return controller_2_; } - auto get_controller_2() -> controller& { return controller_2_; } - - auto step(std::chrono::microseconds delta) -> void; - auto step(cycle_count delta) -> void; - auto step_to_nmi() -> void; // XXX: Debugging - - auto get_snapshot() -> snapshot; - }; -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/ppu.cc b/lib/nes/ref/ppu.cc deleted file mode 100644 index 0856093..0000000 --- a/lib/nes/ref/ppu.cc +++ /dev/null @@ -1,737 +0,0 @@ -#include "nes/ref/ppu.hh" -#include "nes/ref/cpu.hh" -#include "nes/ref/mapper.hh" -#include "nes/util/address.hh" -#include "nes/util/display.hh" -#include "nes/util/rgb.hh" -#include "nes/util/snapshot.hh" -#include "nes/util/debug.hh" - -namespace nes::ref -{ - ppu::ppu(cpu& cpu, mapper& mapper, display& display) - : cpu_{ cpu } - , mapper_{ mapper } - , display_{ display } - { - } - - auto ppu::build_snapshot(snapshot& snapshot) -> void - { - snapshot.vram = std::vector(std::begin(vram_), std::end(vram_)); - snapshot.oam = std::vector(std::begin(oam_), std::end(oam_)); - } - - auto ppu::step() -> void - { - // See https://www.nesdev.org/wiki/PPU_rendering - // Based on: https://github.com/fogleman/nes/blob/master/nes/ppu.go - - auto const enable_rendering = mask_.enable_background || mask_.enable_sprites; - - current_cycles_ += cycle_count::from_ppu(1); - scanline_cycle_ += 1; - if (enable_rendering && !even_frame_ && scanline_ == 261 && scanline_cycle_ == 340) - { - current_cycles_ += cycle_count::from_ppu(1); - scanline_cycle_ += 1; - } - if (scanline_cycle_ == 341) - { - scanline_cycle_ = 0; - scanline_ += 1; - - if (scanline_ == 262) - { - scanline_ = 0; - even_frame_ = !even_frame_; - } - } - - auto const pre_line = scanline_ == 261; - auto const visible_line = scanline_ < 240; - auto const render_line = pre_line || visible_line; - auto const pre_fetch_cycle = scanline_cycle_ >= 321 && scanline_cycle_ <= 336; - auto const visible_cycle = scanline_cycle_ >= 1 && scanline_cycle_ <= 256; - auto const fetch_cycle = pre_fetch_cycle || visible_cycle; - - // Background Logic - if (enable_rendering) - { - if (visible_line && visible_cycle) - { - render_pixel(); - } - - if (render_line && fetch_cycle) - { - tile_data_ <<= 4; - switch (scanline_cycle_ % 8) - { - case 1: - fetch_name_table_byte(); - break; - case 3: - fetch_attribute_table_byte(); - break; - case 5: - fetch_low_tile_byte(); - break; - case 7: - fetch_high_tile_byte(); - break; - case 0: - store_tile_data(); - break; - default: - break; - } - } - - if (pre_line && scanline_cycle_ >= 280 && scanline_cycle_ <= 304) - { - copy_y(); - } - - if (render_line) - { - if (fetch_cycle && scanline_cycle_ % 8 == 0) - { - increment_x(); - } - if (scanline_cycle_ == 256) - { - increment_y(); - } - if (scanline_cycle_ == 257) - { - copy_x(); - } - } - } - - // Sprite Logic - if (enable_rendering) - { - if (scanline_cycle_ == 257) - { - if (visible_line) - { - evaluate_sprites(); - } - else - { - sprite_count_ = 0; - } - } - } - - // Vblank Logic - if (scanline_ == 241 && scanline_cycle_ == 1) - { - display_.switch_buffers(); - status_.vblank = true; - nmi_change(); - } - if (pre_line && scanline_cycle_ == 1) - { - status_.vblank = false; - status_.sprite_zero_hit = false; - status_.sprite_overflow = false; - } - } - - auto ppu::render_pixel() -> void - { - auto const x = scanline_cycle_ - 1; - auto const y = scanline_; - - auto background_color = color_index{ 0 }; - if (mask_.enable_background) - { - auto const tile_data = static_cast(tile_data_ >> 32); - background_color = color_index{ static_cast((tile_data >> ((7 - internal_.x) * 4)) & 0xF) }; - background_color.role = role::background; - } - - auto foreground = sprite{}; - auto foreground_color = color_index{ 0 }; - if (mask_.enable_sprites) - { - for (auto i = unsigned{ 0 }; i < sprite_count_; ++i) - { - auto const s = sprites_[i]; - auto const offset = static_cast(x) - static_cast(s.position); - if (offset < 0 || offset > 7) { continue; } - auto const color = color_index{ static_cast((s.pattern >> ((7 - offset) * 4)) & 0xF) }; - if (color.color == 0) { continue; } - - foreground = s; - foreground_color = color; - foreground_color.role = role::sprite; - break; - } - } - - auto has_background = background_color.color != 0; - auto has_foreground = foreground_color.color != 0; - - if (x < 8 && !mask_.show_background_start) { has_background = false; } - if (x < 8 && !mask_.show_sprites_start) { has_foreground = false; } - - auto color = color_index{ 0 }; - if (!has_background && has_foreground) - { - color = foreground_color; - } - if (has_background && !has_foreground) - { - color = background_color; - } - if (has_background && has_foreground) - { - if (foreground.is_sprite_zero && x < 255) - { - status_.sprite_zero_hit = true; - } - - color = foreground.is_in_front ? foreground_color : background_color; - } - - display_.set(x, y, resolve_color(get_color(color))); - } - - auto ppu::fetch_name_table_byte() -> void - { - auto const addr = static_cast(0x2000 | (internal_.v & 0x0FFF)); - name_table_byte_ = read8(address{ addr }); - } - - auto ppu::fetch_attribute_table_byte() -> void - { - auto const v = internal_.v; - auto const addr = static_cast(0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)); - auto const shift = ((v >> 4) & 4) | (v & 2); - attribute_table_byte_ = static_cast(((read8(address{ addr }) >> shift) & 3) << 2); - } - - auto ppu::fetch_low_tile_byte() -> void - { - auto const fine_y = (internal_.v >> 12) & 7; - auto const table = control_.background_pattern_table; - auto const tile = name_table_byte_; - auto const addr = static_cast(0x1000 * table + 16 * tile + fine_y); - low_tile_byte_ = read8(address{ addr }); - } - - auto ppu::fetch_high_tile_byte() -> void - { - auto const fine_y = (internal_.v >> 12) & 7; - auto const table = control_.background_pattern_table; - auto const tile = name_table_byte_; - auto const addr = static_cast(0x1000 * table + 16 * tile + fine_y); - high_tile_byte_ = read8(address{ addr } + 8); - } - - auto ppu::store_tile_data() -> void - { - auto data = std::uint32_t{ 0 }; - for (auto i = unsigned{ 0 }; i < 8; ++i) - { - auto const p1 = (low_tile_byte_ & 0x80) >> 7; - auto const p2 = (high_tile_byte_ & 0x80) >> 6; - low_tile_byte_ <<= 1; - high_tile_byte_ <<= 1; - data <<= 4; - data |= attribute_table_byte_ | p1 | p2; - } - tile_data_ |= data; - } - - auto ppu::increment_x() -> void - { - // If coarse X == 31 - if ((internal_.v & 0x1F) == 0x1F) - { - // Coarse X = 0 - // TODO: Check - // internal_.v &= ~0x1F; - internal_.v &= 0xFFE0; - // Switch horizontal nametable - internal_.v ^= 0x400; - } - else - { - // Increment coarse X - internal_.v += 1; - } - } - - auto ppu::increment_y() -> void - { - // If fine Y == 7 - if ((internal_.v & 0x7000) == 0x7000) - { - // Fine Y = 0 - internal_.v &= 0x8FFF; - // Let y = coarse Y - auto y = (internal_.v & 0x03E0) >> 5; - if (y == 29) - { - // Coarse Y = 0 - y = 0; - // Switch vertical nametable - internal_.v ^= 0x0800; - } - else if (y == 31) - { - // Coarse Y = 0, nametable not switched - y = 0; - } - else - { - // Increment coarse Y - y += 1; - } - // Put coarse Y back into v - // TODO: Check - // internal_.v &= ~0x03E0; - internal_.v &= 0xFC1F; - internal_.v |= y << 5; - } - else - { - // Increment fine Y - internal_.v += 0x1000; - } - } - - auto ppu::copy_x() -> void - { - internal_.v = (internal_.v & 0xFBE0) | (internal_.t & 0x041F); - } - - auto ppu::copy_y() -> void - { - internal_.v = (internal_.v & 0x841F) | (internal_.t & 0x7BE0); - } - - auto ppu::evaluate_sprites() -> void - { - auto const height = sprite_height(); - sprite_count_ = 0; - for (auto i = unsigned{ 0 }; i < 64; ++i) - { - auto const y = oam_[i * 4 + 0]; - auto const a = oam_[i * 4 + 2]; - auto const x = oam_[i * 4 + 3]; - auto const row = static_cast(scanline_) - static_cast(y); - if (row < 0 || row >= height) { continue; } - - if (sprite_count_ < 8) - { - sprites_[sprite_count_].pattern = fetch_sprite_pattern(i, row); - sprites_[sprite_count_].position = x; - // TODO: Check - // sprites_[sprite_count_].is_in_front = a & 0b00100000; - sprites_[sprite_count_].is_in_front = ((a >> 5) & 1) == 0; - sprites_[sprite_count_].is_sprite_zero = i == 0; - sprite_count_ += 1; - } - else - { - status_.sprite_overflow = true; - break; - } - } - } - - auto ppu::fetch_sprite_pattern(unsigned const i, unsigned row) -> std::uint32_t - { - auto tile = oam_[i * 4 + 1]; - auto const attributes = oam_[i * 4 + 2]; - auto const flip_vertical = (attributes & 0x80) == 0x80; - if (flip_vertical) { row = sprite_height() - 1 - row; } - auto addr = address{}; - switch (control_.sprite_size) - { - case sprite_size::_8x8: - { - auto const table = control_.sprite_pattern_table; - addr = address{ static_cast(0x1000 * table + 16 * tile + row) }; - break; - } - case sprite_size::_8x16: - { - auto const table = tile & 1; - tile &= 0xFE; - if (row > 7) - { - tile += 1; - row -= 8; - } - addr = address{ static_cast(0x1000 * table + 16 * tile + row) }; - break; - } - } - - auto const a = (attributes & 3) << 2; - auto low_tile_byte = read8(addr); - auto high_tile_byte = read8(addr + 8); - auto data = std::uint32_t{ 0 }; - for (auto j = unsigned{ 0 }; j < 8; ++j) - { - auto const flip_horizontal = (attributes & 0x40) == 0x40; - std::uint8_t p1, p2; - if (flip_horizontal) - { - p1 = (low_tile_byte & 1) << 0; - p2 = (high_tile_byte & 1) << 1; - low_tile_byte >>= 1; - high_tile_byte >>= 1; - } - else - { - p1 = (low_tile_byte & 0x80) >> 7; - p2 = (high_tile_byte & 0x80) >> 6; - low_tile_byte <<= 1; - high_tile_byte <<= 1; - } - data <<= 4; - data |= a | p1 | p2; - } - - return data; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Memory Access - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/PPU_memory_map - // - - auto ppu::read8(address addr) -> std::uint8_t - { - addr = addr % 0x4000; // PPU only has 16 KiB addresses. - if (addr <= address{ 0x3EFF }) { return mapper_.read_ppu(addr, vram_); } - if (addr <= address{ 0x3FFF }) - { - auto const index = color_index{ static_cast(addr.get_absolute() % 0x20) }; - auto const color = get_color(index); - return static_cast(color); - } - - return 0x0; - } - - auto ppu::write8(address addr, std::uint8_t const value) -> void - { - addr = addr % 0x4000; // PPU only has 16 KiB addresses. - if (addr <= address{ 0x3EFF }) - { - mapper_.write_ppu(addr, vram_, value); - return; - } - if (addr <= address{ 0x3FFF }) - { - auto const index = color_index{ static_cast(addr.get_absolute() % 0x20) }; - get_color(index) = color{ value }; - return; - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // IO Registers - // ----------------------------------------------------------------------------------------------------------------- - - // - // See: https://www.nesdev.org/wiki/PPU_registers - // - - auto ppu::read_latch() -> std::uint8_t - { - return latch_; - } - - auto ppu::read_ppustatus() -> std::uint8_t - { - auto const base = latch_ & ~status_mask; - auto const res = base | (status_.value & status_mask); - NES_DEBUG_LOG(ppu, "PPUSTATUS -> {:#2x}", res); - - status_.vblank = false; - internal_.w = false; - - write_latch(res); - return res; - } - - auto ppu::read_oamdata() -> std::uint8_t - { - auto res = oam_[oamaddr_]; - if ((oamaddr_ & 0x3) == 0x2) - { - res &= 0xE3; - } - NES_DEBUG_LOG(ppu, "OAMDATA -> {:#2x}", res); - write_latch(res); - return res; - } - - auto ppu::read_ppudata() -> std::uint8_t - { - auto const addr = address{ internal_.v }; - auto res = read8(addr); - write_latch(ppudata_read_buffer_); - - if (addr % 0x4000 <= address{ 0x3EFF }) - { - std::swap(res, ppudata_read_buffer_); - } - else - { - // For palette buffers: buffer the mirrored nametable instead. - ppudata_read_buffer_ = read8(addr - 0x1000); - } - - NES_DEBUG_LOG(ppu, "PPUDATA -> {:#2x}", res); - increment_vram(); - return res; - } - - auto ppu::write_latch(std::uint8_t const value) -> void - { - latch_ = value; - } - - auto ppu::write_ppuctrl(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUCTRL <- {:#2x}", value); - control_.value = value; - internal_.t = (internal_.t & 0xF3FF) | ((value & 0x03) << 10); - nmi_change(); - } - } - - auto ppu::write_ppuscroll(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUSCROLL <- {:#2x}", value); - if (!internal_.w) - { - // First write -> x value. - internal_.t = (internal_.t & 0b1111111111100000) | (value >> 3); - internal_.x = value & 0b00000111; - } - else - { - // Second write -> y value. - internal_.t = (internal_.t & 0b1000111111111111) | ((value & 0b00000111) << 12); - internal_.t = (internal_.t & 0b1111110000011111) | ((value & 0b11111000) << 2); - } - - internal_.w = !internal_.w; - } - } - - auto ppu::write_ppumask(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUMASK <- {:#2x}", value); - mask_.value = value; - } - } - - auto ppu::write_ppuaddr(std::uint8_t const value) -> void - { - write_latch(value); - if (current_cycles_ > boot_up_cycles) - { - NES_DEBUG_LOG(ppu, "PPUADDR <- {:#2x}", value); - if (!internal_.w) - { - // First write. - internal_.t = (internal_.t & 0b1000000011111111) | ((value & 0b00111111) << 8); - } - else - { - // Second write. - internal_.t = (internal_.t & 0b1111111100000000) | ((value & 0b11111111) << 0); - internal_.v = internal_.t; - } - - internal_.w = !internal_.w; - } - } - - auto ppu::write_ppudata(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "PPUDATA <- {:#2x} (address: {:#4x})", value, internal_.v); - write_latch(value); - write8(address{ internal_.v }, value); - increment_vram(); - } - - auto ppu::write_oamaddr(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "OAMADDR <- {:#2x}", value); - write_latch(value); - oamaddr_ = value; - } - - auto ppu::write_oamdata(std::uint8_t const value) -> void - { - NES_DEBUG_LOG(ppu, "OAMDATA <- {:#2x}", value); - write_latch(value); - oam_[oamaddr_] = value; - oamaddr_ += 1; - } - - auto ppu::write_oamdma(std::uint8_t const value) -> void - { - // TODO: Do this in CPU::step instead in case there's an NMI? - NES_DEBUG_LOG(ppu, "OAMDMA <- {:#2x}", value); - auto addr = address{ value, 0x00 }; - for (auto i = unsigned{ 0 }; i < 256; ++i) - { - oam_[oamaddr_] = cpu_.read8(addr); - oamaddr_ += 1; - addr = addr + 1; - } - - auto const stalled_cycles = cycle_count::from_cpu((cpu_.get_cycles().to_cpu() % 2 == 0) ? 513 : 514); - cpu_.stall_cycles(stalled_cycles); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Helpers - // ----------------------------------------------------------------------------------------------------------------- - - auto ppu::nmi_change() -> void - { - if (control_.vblank_nmi && status_.vblank) - { - cpu_.trigger_nmi(); - } - } - - auto ppu::sprite_height() -> unsigned - { - switch (control_.sprite_size) - { - case sprite_size::_8x8: - return 8; - case sprite_size::_8x16: - return 16; - } - - return 8; - } - - auto ppu::increment_vram() -> void - { - switch (control_.vram_increment) - { - case vram_increment::add_1_across: - internal_.v += 1; - break; - case vram_increment::add_32_down: - internal_.v += 32; - break; - } - } - - auto ppu::get_color(color_index index) -> color& - { - // See https://www.nesdev.org/wiki/PPU_palettes - index.value = index.value % 0x20; - if (index.color == 0) - { - // The first color is mirrored between background and foreground palettes. - index.role = role::background; - } - return palette_buffer_[index.value]; - } - - auto ppu::resolve_color(color const color) -> rgb - { - auto const index = static_cast(color); - switch (index & 0x3F) - { - case 0x00: return rgb::from_hex(0x666666); - case 0x01: return rgb::from_hex(0x002A88); - case 0x02: return rgb::from_hex(0x1412A7); - case 0x03: return rgb::from_hex(0x3B00A4); - case 0x04: return rgb::from_hex(0x5C007E); - case 0x05: return rgb::from_hex(0x6E0040); - case 0x06: return rgb::from_hex(0x6C0600); - case 0x07: return rgb::from_hex(0x561D00); - case 0x08: return rgb::from_hex(0x333500); - case 0x09: return rgb::from_hex(0x0B4800); - case 0x0A: return rgb::from_hex(0x005200); - case 0x0B: return rgb::from_hex(0x004F08); - case 0x0C: return rgb::from_hex(0x00404D); - case 0x0D: return rgb::from_hex(0x000000); - case 0x0E: return rgb::from_hex(0x000000); - case 0x0F: return rgb::from_hex(0x000000); - case 0x10: return rgb::from_hex(0xADADAD); - case 0x11: return rgb::from_hex(0x155FD9); - case 0x12: return rgb::from_hex(0x4240FF); - case 0x13: return rgb::from_hex(0x7527FE); - case 0x14: return rgb::from_hex(0xA01ACC); - case 0x15: return rgb::from_hex(0xB71E7B); - case 0x16: return rgb::from_hex(0xB53120); - case 0x17: return rgb::from_hex(0x994E00); - case 0x18: return rgb::from_hex(0x6B6D00); - case 0x19: return rgb::from_hex(0x388700); - case 0x1A: return rgb::from_hex(0x0C9300); - case 0x1B: return rgb::from_hex(0x008F32); - case 0x1C: return rgb::from_hex(0x007C8D); - case 0x1D: return rgb::from_hex(0x000000); - case 0x1E: return rgb::from_hex(0x000000); - case 0x1F: return rgb::from_hex(0x000000); - case 0x20: return rgb::from_hex(0xFFFEFF); - case 0x21: return rgb::from_hex(0x64B0FF); - case 0x22: return rgb::from_hex(0x9290FF); - case 0x23: return rgb::from_hex(0xC676FF); - case 0x24: return rgb::from_hex(0xF36AFF); - case 0x25: return rgb::from_hex(0xFE6ECC); - case 0x26: return rgb::from_hex(0xFE8170); - case 0x27: return rgb::from_hex(0xEA9E22); - case 0x28: return rgb::from_hex(0xBCBE00); - case 0x29: return rgb::from_hex(0x88D800); - case 0x2A: return rgb::from_hex(0x5CE430); - case 0x2B: return rgb::from_hex(0x45E082); - case 0x2C: return rgb::from_hex(0x48CDDE); - case 0x2D: return rgb::from_hex(0x4F4F4F); - case 0x2E: return rgb::from_hex(0x000000); - case 0x2F: return rgb::from_hex(0x000000); - case 0x30: return rgb::from_hex(0xFFFEFF); - case 0x31: return rgb::from_hex(0xC0DFFF); - case 0x32: return rgb::from_hex(0xD3D2FF); - case 0x33: return rgb::from_hex(0xE8C8FF); - case 0x34: return rgb::from_hex(0xFBC2FF); - case 0x35: return rgb::from_hex(0xFEC4EA); - case 0x36: return rgb::from_hex(0xFECCC5); - case 0x37: return rgb::from_hex(0xF7D8A5); - case 0x38: return rgb::from_hex(0xE4E594); - case 0x39: return rgb::from_hex(0xCFEF96); - case 0x3A: return rgb::from_hex(0xBDF4AB); - case 0x3B: return rgb::from_hex(0xB3F3CC); - case 0x3C: return rgb::from_hex(0xB5EBF2); - case 0x3D: return rgb::from_hex(0xB8B8B8); - case 0x3E: return rgb::from_hex(0x000000); - case 0x3F: return rgb::from_hex(0x000000); - default: return rgb::from_hex(0x000000); - } - } -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/ref/ppu.hh b/lib/nes/ref/ppu.hh deleted file mode 100644 index 400ebdb..0000000 --- a/lib/nes/ref/ppu.hh +++ /dev/null @@ -1,212 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" - -namespace nes -{ - class address; - class display; - class rgb; - struct snapshot; -} // namespace nes - -namespace nes::ref -{ - class cpu; - class mapper; - - class ppu - { - static constexpr auto vram_size = std::size_t{ 0x800 }; - static constexpr auto oam_size = std::size_t{ 0x100 }; - static constexpr auto palette_buffer_size = std::size_t{ 0x20 }; - static constexpr auto status_mask = std::uint8_t{ 0b11100000 }; - // Writes to some registers are ignored until this clock cycle. - static constexpr auto boot_up_cycles = cycle_count::from_ppu(29658); - - enum class vram_increment : unsigned - { - add_1_across = 0, - add_32_down = 1, - }; - - enum class sprite_size : unsigned - { - _8x8 = 0, - _8x16 = 1, - }; - - enum class role : unsigned - { - background = 0, - sprite = 1, - }; - - enum class color : std::uint8_t - { - }; - - union color_index - { - struct __attribute__((packed)) - { - unsigned color : 2; - unsigned palette : 2; - role role : 1; - }; - std::uint8_t value; - - explicit color_index(std::uint8_t const value) - : value{ value } - { - } - }; - - struct sprite - { - std::uint32_t pattern{ 0 }; - std::uint8_t position{ 0 }; - bool is_in_front{ false }; - bool is_sprite_zero{ false }; - }; - - cycle_count current_cycles_; - cpu& cpu_; - mapper& mapper_; - display& display_; - std::uint8_t vram_[vram_size]{}; - std::uint8_t oam_[oam_size]{}; - color palette_buffer_[palette_buffer_size] - { - color{ 0x09 }, color{ 0x01 }, color{ 0x00 }, color{ 0x01 }, - color{ 0x00 }, color{ 0x02 }, color{ 0x02 }, color{ 0x0D }, - color{ 0x08 }, color{ 0x10 }, color{ 0x08 }, color{ 0x24 }, - color{ 0x00 }, color{ 0x00 }, color{ 0x04 }, color{ 0x2C }, - color{ 0x09 }, color{ 0x01 }, color{ 0x34 }, color{ 0x03 }, - color{ 0x00 }, color{ 0x04 }, color{ 0x00 }, color{ 0x14 }, - color{ 0x08 }, color{ 0x3A }, color{ 0x00 }, color{ 0x02 }, - color{ 0x00 }, color{ 0x20 }, color{ 0x2C }, color{ 0x08 }, - }; - std::uint8_t latch_{}; // The last read/written IO register value, returned when reading write-only registers. - union - { - struct __attribute__((packed)) - { - bool : 1; - bool : 1; - bool : 1; - bool : 1; - bool : 1; - bool sprite_overflow : 1; - bool sprite_zero_hit : 1; - bool vblank : 1; - }; - std::uint8_t value{ 0b00000000 }; - } status_{}; - union - { - struct __attribute__((packed)) - { - std::uint8_t base_name_table : 2; // Base nametable address. - vram_increment vram_increment : 1; // VRAM increment per CPU read/write of PPUDATA. - std::uint8_t sprite_pattern_table : 1; // Sprite pattern table address for 8x8 sprites. - std::uint8_t background_pattern_table : 1; // Background pattern table address. - sprite_size sprite_size : 1; // Sprite size. - bool enable_ext_pin : 1; // PPU master/slave select. - bool vblank_nmi : 1; // Enable/disable Vblank NMI - }; - std::uint8_t value{ 0b00000000 }; - } control_{}; - union - { - struct __attribute__((packed)) - { - bool grayscale : 1; // Enable/disable grayscale. - bool show_background_start : 1; // Show background in leftmost 8 pixels of screen. - bool show_sprites_start : 1; // Show sprites in leftmost 8 pixels of screen. - bool enable_background : 1; // Enable background rendering. - bool enable_sprites : 1; // Enable sprite rendering. - bool emphasize_red : 1; // Emphasize red. - bool emphasize_green : 1; // Emphasize green. - bool emphasize_blue : 1; // Emphasize blue. - }; - std::uint8_t value{ 0b00000000 }; - } mask_{}; - struct - { - // See https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers - std::uint16_t v{}; // Current VRAM address (15 bits) - std::uint16_t t{}; // Temporary VRAM address (15 bits) - std::uint8_t x{}; // Fine X scroll (3 bits) - bool w{}; // Second write toggle (1 bit) - } internal_{}; - std::uint8_t oamaddr_{}; // Address accessed by IO registers - std::uint8_t ppudata_read_buffer_{}; // Delays PPUDATA reads by one. - unsigned scanline_{ 240 }; - unsigned scanline_cycle_{ 340 }; - bool even_frame_{ true }; - std::uint64_t tile_data_{ 0 }; // Data for the current tile (higher 32 bits) and the next tile (lower 32 bits). - std::uint8_t name_table_byte_{ 0 }; - std::uint8_t attribute_table_byte_{ 0 }; - std::uint8_t low_tile_byte_{ 0 }; - std::uint8_t high_tile_byte_{ 0 }; - sprite sprites_[8]{}; // Evaluated sprites. - unsigned sprite_count_{ 0 }; // Number of evaluated sprites in sprites_. - - public: - explicit ppu(cpu&, mapper&, display&); - - ppu(ppu const&) = delete; - ppu(ppu&&) = delete; - auto operator=(ppu const&) -> ppu& = delete; - auto operator=(ppu&&) -> ppu& = delete; - - auto get_cycles() const -> cycle_count { return current_cycles_; } - auto build_snapshot(snapshot&) -> void; - auto step() -> void; - - // Memory access - - auto read8(address) -> std::uint8_t; - auto write8(address, std::uint8_t) -> void; - - // IO registers - - auto read_latch() -> std::uint8_t; - auto read_ppustatus() -> std::uint8_t; - auto read_oamdata() -> std::uint8_t; - auto read_ppudata() -> std::uint8_t; - - auto write_latch(std::uint8_t) -> void; - auto write_ppuctrl(std::uint8_t) -> void; - auto write_ppuscroll(std::uint8_t) -> void; - auto write_ppumask(std::uint8_t) -> void; - auto write_ppuaddr(std::uint8_t) -> void; - auto write_ppudata(std::uint8_t) -> void; - auto write_oamaddr(std::uint8_t) -> void; - auto write_oamdata(std::uint8_t) -> void; - auto write_oamdma(std::uint8_t) -> void; - - private: - auto render_pixel() -> void; - auto fetch_name_table_byte() -> void; - auto fetch_attribute_table_byte() -> void; - auto fetch_low_tile_byte() -> void; - auto fetch_high_tile_byte() -> void; - auto store_tile_data() -> void; - auto increment_x() -> void; - auto increment_y() -> void; - auto copy_x() -> void; - auto copy_y() -> void; - auto evaluate_sprites() -> void; - auto fetch_sprite_pattern(unsigned i, unsigned row) -> std::uint32_t; - - // Helpers - - auto nmi_change() -> void; - auto sprite_height() -> unsigned; - auto increment_vram() -> void; - auto get_color(color_index) -> color&; - auto resolve_color(color) -> rgb; - }; -} // namespace nes::ref \ No newline at end of file diff --git a/lib/nes/sys/CMakeLists.txt b/lib/nes/sys/CMakeLists.txt new file mode 100644 index 0000000..60f77d2 --- /dev/null +++ b/lib/nes/sys/CMakeLists.txt @@ -0,0 +1,17 @@ +target_sources( + nes + PRIVATE + cartridge.hh + cartridge.cc + controller.hh + controller.cc + cpu.hh + cpu.cc + ppu.hh + ppu.cc + mapper.hh + mapper.cc + nes.hh + nes.cc) + +add_subdirectory(types) \ No newline at end of file diff --git a/lib/nes/sys/cartridge.cc b/lib/nes/sys/cartridge.cc new file mode 100644 index 0000000..d60ea8b --- /dev/null +++ b/lib/nes/sys/cartridge.cc @@ -0,0 +1,68 @@ +#include "nes/sys/cartridge.hh" +#include "nes/sys/types/status.hh" +#include + +namespace nes +{ + cartridge::cartridge(status const status) + : status_{ status } + { + } + + auto cartridge::from_data(void const* data, std::size_t const length) -> cartridge + { + // See: https://www.nesdev.org/wiki/INES + + auto const bytes = static_cast(data); + auto res = cartridge{ status::success }; + + // Parse header. + if (length < header_length) { return cartridge{ status::error_invalid_ines_data }; } + auto const h = header{ bytes }; + if (h.get_magic_0() != 'N' || h.get_magic_1() != 'E' || h.get_magic_2() != 'S' || h.get_magic_3() != 0x1A) + { + return cartridge{ status::error_invalid_ines_data }; + } + auto const mapper_number = static_cast((h.get_mapper_high() << 4) | (h.get_mapper_low() << 0)); + + res.mapper_ = &mapper::get(mapper_number); + res.name_table_arrangement_ = h.get_name_table_arrangement(); + + // Load program data. + auto offset = std::size_t{ 16 }; + if (h.get_has_trainer()) { offset += 512; } + + auto const prg_rom_size = h.get_prg_rom_banks() * prg_rom_bank_size; + if (length < offset + prg_rom_size) { return cartridge{ status::error_invalid_ines_data }; } + res.prg_rom_ = std::vector(&bytes[offset], &bytes[offset + prg_rom_size]); + + offset += prg_rom_size; + + auto const chr_rom_size = h.get_chr_rom_banks() * chr_rom_bank_size; + if (length < offset + chr_rom_size) { return cartridge{ status::error_invalid_ines_data }; } + res.chr_rom_ = std::vector(&bytes[offset], &bytes[offset + chr_rom_size]); + + res.ram_ = std::vector(std::max(h.get_ram_banks(), 1u) * ram_bank_size, std::uint8_t{ 0 }); + + if (auto const status = res.get_mapper().validate(res); status != status::success) + { + return cartridge{ status }; + } + + return res; + } + + auto cartridge::from_file(std::string_view const path) -> cartridge + { + auto stream = std::ifstream{ path, std::ios::binary }; + if (!stream.good()) { return cartridge{ status::error_system_error }; } + + stream.seekg(0, std::ios::end); + auto const length = stream.tellg(); + stream.seekg(0, std::ios::beg); + auto data = std::vector(static_cast(length)); + stream.read(reinterpret_cast(data.data()), length); + + return from_data(data.data(), data.size()); + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/cartridge.hh b/lib/nes/sys/cartridge.hh new file mode 100644 index 0000000..a8b61a2 --- /dev/null +++ b/lib/nes/sys/cartridge.hh @@ -0,0 +1,78 @@ +#pragma once + +#include "nes/sys/types/status.hh" +#include "nes/sys/mapper.hh" +#include +#include +#include + +namespace nes +{ + enum class name_table_arrangement + { + horizontal, + vertical, + }; + + class cartridge + { + static constexpr auto prg_rom_bank_size = std::size_t{ 16 * 1024 }; + static constexpr auto chr_rom_bank_size = std::size_t{ 8 * 1024 }; + static constexpr auto ram_bank_size = std::size_t{ 8 * 1024 }; + static constexpr auto header_length = std::size_t{ 16 }; + + struct header + { + auto get_magic_0() const -> std::uint8_t { return value[0]; } + auto get_magic_1() const -> std::uint8_t { return value[1]; } + auto get_magic_2() const -> std::uint8_t { return value[2]; } + auto get_magic_3() const -> std::uint8_t { return value[3]; } + auto get_prg_rom_banks() const -> unsigned { return value[4]; } + auto get_chr_rom_banks() const -> unsigned { return value[5]; } + auto get_control_1() const -> std::uint8_t { return value[6]; } + auto get_control_2() const -> std::uint8_t { return value[7]; } + auto get_ram_banks() const -> unsigned { return value[8]; } + auto get_has_trainer() const -> bool { return get_control_1() & 0b00000100; } + auto get_mapper_low() const -> unsigned { return (get_control_1() & 0b11110000) >> 4; } + auto get_mapper_high() const -> unsigned { return (get_control_2() & 0b11110000) >> 0; } + auto get_name_table_arrangement() const -> name_table_arrangement { return static_cast((get_control_1() & 0b00000001) >> 0); } + + std::uint8_t value[header_length]{}; + + explicit header(std::uint8_t const* v) + { + for (auto i = std::size_t{ 0 }; i < header_length; ++i) + { + value[i] = v[i]; + } + } + }; + + status status_{ status::error_uninitialized }; + std::vector prg_rom_; + std::vector chr_rom_; + std::vector ram_; + name_table_arrangement name_table_arrangement_{}; + mapper* mapper_{ &mapper::invalid() }; + + public: + explicit cartridge() = default; + + static auto from_data(void const* data, std::size_t length) -> cartridge; + static auto from_file(std::string_view path) -> cartridge; + + auto get_status() const -> status { return status_; } + auto get_prg_rom() const -> std::uint8_t const* { return prg_rom_.data(); } + auto get_prg_rom_length() const -> std::size_t { return prg_rom_.size(); } + auto get_chr_rom() const -> std::uint8_t const* { return chr_rom_.data(); } + auto get_chr_rom_length() const -> std::size_t { return chr_rom_.size(); } + auto get_ram() const -> std::uint8_t const* { return ram_.data(); } + auto get_ram_mut() -> std::uint8_t* { return ram_.data(); } + auto get_ram_length() const -> std::size_t { return ram_.size(); } + auto get_mapper() const -> mapper& { return *mapper_; } + auto get_name_table_arrangement() const -> name_table_arrangement { return name_table_arrangement_; } + + private: + explicit cartridge(status); + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/controller.cc b/lib/nes/sys/controller.cc new file mode 100644 index 0000000..910ef57 --- /dev/null +++ b/lib/nes/sys/controller.cc @@ -0,0 +1,18 @@ +#include "nes/sys/controller.hh" + +namespace nes +{ + auto controller::read() -> std::uint8_t + { + auto const res = index_ < 8 ? (pressed_.get_raw_value() >> index_) & 1 : 0; + index_ += 1; + if (strobing_) { index_ = 0; } + return static_cast(res); + } + + auto controller::write(std::uint8_t const value) -> void + { + strobing_ = value & 1; + if (strobing_) { index_ = 0; } + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/controller.hh b/lib/nes/sys/controller.hh new file mode 100644 index 0000000..441e236 --- /dev/null +++ b/lib/nes/sys/controller.hh @@ -0,0 +1,30 @@ +#pragma once + +#include "nes/sys/types/button-mask.hh" + +namespace nes +{ + class controller + { + button_mask pressed_; + unsigned index_{ 0 }; + bool strobing_{ false }; + + public: + explicit controller() = default; + + controller(controller const&) = delete; + controller(controller&&) = delete; + auto operator=(controller const&) -> controller& = delete; + auto operator=(controller&&) -> controller& = delete; + + auto get_pressed() const -> button_mask { return pressed_;} + auto ref_pressed() -> button_mask& { return pressed_; } + auto set_pressed(button_mask const value) -> void { pressed_ = value; } + + // IO register + + auto read() -> std::uint8_t; + auto write(std::uint8_t) -> void; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/cpu.cc b/lib/nes/sys/cpu.cc new file mode 100644 index 0000000..84fbe78 --- /dev/null +++ b/lib/nes/sys/cpu.cc @@ -0,0 +1,1425 @@ +#include "nes/sys/cpu.hh" +#include "nes/sys/ppu.hh" +#include "nes/sys/controller.hh" +#include "nes/sys/cartridge.hh" +#include "nes/sys/types/snapshot.hh" + +namespace nes +{ + cpu::cpu(ppu& ppu, cartridge& cartridge, controller& controller_1, controller& controller_2) + : ppu_{ ppu } + , cartridge_{ cartridge } + , controller_1_{ controller_1 } + , controller_2_{ controller_2 } + { + registers_.pc = read16(address{ 0xFFFC }); + } + + auto cpu::build_snapshot(snapshot& snapshot) -> void + { + snapshot.cpu_cycle = current_cycles_; + snapshot.ram = std::vector(std::begin(ram_), std::end(ram_)); + snapshot.registers.pc = registers_.pc; + snapshot.registers.sp = registers_.sp; + snapshot.registers.a = registers_.a; + snapshot.registers.x = registers_.x; + snapshot.registers.y = registers_.y; + snapshot.registers.p = registers_.p.value; + snapshot.registers.c = registers_.p.get_c(); + snapshot.registers.z = registers_.p.get_z(); + snapshot.registers.i = registers_.p.get_i(); + snapshot.registers.d = registers_.p.get_d(); + snapshot.registers.b = registers_.p.get_b(); + snapshot.registers.v = registers_.p.get_v(); + snapshot.registers.n = registers_.p.get_n(); + } + + auto cpu::stall_cycles(cycle_count const count) -> void + { + current_cycles_ += count; + } + + auto cpu::trigger_nmi() -> void + { + nmi_pending_ = true; + } + + auto cpu::step() -> status + { + // See https://www.nesdev.org/wiki/CPU_unofficial_opcodes + +#ifdef NES_ENABLE_PPU + if (nmi_pending_) + { + execute_interrupt(address{ 0xFFFA }); + nmi_pending_ = false; + } +#endif + + auto const opcode = advance_pc8(); + switch (opcode) + { + case 0x00: return run_brk(); + case 0x01: return run_ora(); + case 0x02: return run_stp(); + case 0x03: return run_slo(); + case 0x04: return run_nop(); + case 0x05: return run_ora(); + case 0x06: return run_asl(); + case 0x07: return run_slo(); + case 0x08: return run_php(); + case 0x09: return run_ora(); + case 0x0A: return run_asl(); + case 0x0B: return run_anc(); + case 0x0C: return run_nop(); + case 0x0D: return run_ora(); + case 0x0E: return run_asl(); + case 0x0F: return run_slo(); + case 0x10: return run_bpl(); + case 0x11: return run_ora(); + case 0x12: return run_stp(); + case 0x13: return run_slo(); + case 0x14: return run_nop(); + case 0x15: return run_ora(); + case 0x16: return run_asl(); + case 0x17: return run_slo(); + case 0x18: return run_clc(); + case 0x19: return run_ora(); + case 0x1A: return run_nop(); + case 0x1B: return run_slo(); + case 0x1C: return run_nop(); + case 0x1D: return run_ora(); + case 0x1E: return run_asl(); + case 0x1F: return run_slo(); + case 0x20: return run_jsr(); + case 0x21: return run_and(); + case 0x22: return run_stp(); + case 0x23: return run_rla(); + case 0x24: return run_bit(); + case 0x25: return run_and(); + case 0x26: return run_rol(); + case 0x27: return run_rla(); + case 0x28: return run_plp(); + case 0x29: return run_and(); + case 0x2A: return run_rol(); + case 0x2B: return run_anc(); + case 0x2C: return run_bit(); + case 0x2D: return run_and(); + case 0x2E: return run_rol(); + case 0x2F: return run_rla(); + case 0x30: return run_bmi(); + case 0x31: return run_and(); + case 0x32: return run_stp(); + case 0x33: return run_rla(); + case 0x34: return run_nop(); + case 0x35: return run_and(); + case 0x36: return run_rol(); + case 0x37: return run_rla(); + case 0x38: return run_sec(); + case 0x39: return run_and(); + case 0x3A: return run_nop(); + case 0x3B: return run_rla(); + case 0x3C: return run_nop(); + case 0x3D: return run_and(); + case 0x3E: return run_rol(); + case 0x3F: return run_rla(); + case 0x40: return run_rti(); + case 0x41: return run_eor(); + case 0x42: return run_stp(); + case 0x43: return run_sre(); + case 0x44: return run_nop(); + case 0x45: return run_eor(); + case 0x46: return run_lsr(); + case 0x47: return run_sre(); + case 0x48: return run_pha(); + case 0x49: return run_eor(); + case 0x4A: return run_lsr(); + case 0x4B: return run_alr(); + case 0x4C: return run_jmp(); + case 0x4D: return run_eor(); + case 0x4E: return run_lsr(); + case 0x4F: return run_sre(); + case 0x50: return run_bvc(); + case 0x51: return run_eor(); + case 0x52: return run_stp(); + case 0x53: return run_sre(); + case 0x54: return run_nop(); + case 0x55: return run_eor(); + case 0x56: return run_lsr(); + case 0x57: return run_sre(); + case 0x58: return run_cli(); + case 0x59: return run_eor(); + case 0x5A: return run_nop(); + case 0x5B: return run_sre(); + case 0x5C: return run_nop(); + case 0x5D: return run_eor(); + case 0x5E: return run_lsr(); + case 0x5F: return run_sre(); + case 0x60: return run_rts(); + case 0x61: return run_adc(); + case 0x62: return run_stp(); + case 0x63: return run_rra(); + case 0x64: return run_nop(); + case 0x65: return run_adc(); + case 0x66: return run_ror(); + case 0x67: return run_rra(); + case 0x68: return run_pla(); + case 0x69: return run_adc(); + case 0x6A: return run_ror(); + case 0x6B: return run_arr(); + case 0x6C: return run_jmp(); + case 0x6D: return run_adc(); + case 0x6E: return run_ror(); + case 0x6F: return run_rra(); + case 0x70: return run_bvs(); + case 0x71: return run_adc(); + case 0x72: return run_stp(); + case 0x73: return run_rra(); + case 0x74: return run_nop(); + case 0x75: return run_adc(); + case 0x76: return run_ror(); + case 0x77: return run_rra(); + case 0x78: return run_sei(); + case 0x79: return run_adc(); + case 0x7A: return run_nop(); + case 0x7B: return run_rra(); + case 0x7C: return run_nop(); + case 0x7D: return run_adc(); + case 0x7E: return run_ror(); + case 0x7F: return run_rra(); + case 0x80: return run_nop(); + case 0x81: return run_sta(); + case 0x82: return run_nop(); + case 0x83: return run_sax(); + case 0x84: return run_sty(); + case 0x85: return run_sta(); + case 0x86: return run_stx(); + case 0x87: return run_sax(); + case 0x88: return run_dey(); + case 0x89: return run_nop(); + case 0x8A: return run_txa(); + case 0x8B: return run_xaa(); + case 0x8C: return run_sty(); + case 0x8D: return run_sta(); + case 0x8E: return run_stx(); + case 0x8F: return run_sax(); + case 0x90: return run_bcc(); + case 0x91: return run_sta(); + case 0x92: return run_stp(); + case 0x93: return run_ahx(); + case 0x94: return run_sty(); + case 0x95: return run_sta(); + case 0x96: return run_stx(); + case 0x97: return run_sax(); + case 0x98: return run_tya(); + case 0x99: return run_sta(); + case 0x9A: return run_txs(); + case 0x9B: return run_tas(); + case 0x9C: return run_shy(); + case 0x9D: return run_sta(); + case 0x9E: return run_shx(); + case 0x9F: return run_ahx(); + case 0xA0: return run_ldy(); + case 0xA1: return run_lda(); + case 0xA2: return run_ldx(); + case 0xA3: return run_lax(); + case 0xA4: return run_ldy(); + case 0xA5: return run_lda(); + case 0xA6: return run_ldx(); + case 0xA7: return run_lax(); + case 0xA8: return run_tay(); + case 0xA9: return run_lda(); + case 0xAA: return run_tax(); + case 0xAB: return run_lax(); + case 0xAC: return run_ldy(); + case 0xAD: return run_lda(); + case 0xAE: return run_ldx(); + case 0xAF: return run_lax(); + case 0xB0: return run_bcs(); + case 0xB1: return run_lda(); + case 0xB2: return run_stp(); + case 0xB3: return run_lax(); + case 0xB4: return run_ldy(); + case 0xB5: return run_lda(); + case 0xB6: return run_ldx(); + case 0xB7: return run_lax(); + case 0xB8: return run_clv(); + case 0xB9: return run_lda(); + case 0xBA: return run_tsx(); + case 0xBB: return run_las(); + case 0xBC: return run_ldy(); + case 0xBD: return run_lda(); + case 0xBE: return run_ldx(); + case 0xBF: return run_lax(); + case 0xC0: return run_cpy(); + case 0xC1: return run_cmp(); + case 0xC2: return run_nop(); + case 0xC3: return run_dcp(); + case 0xC4: return run_cpy(); + case 0xC5: return run_cmp(); + case 0xC6: return run_dec(); + case 0xC7: return run_dcp(); + case 0xC8: return run_iny(); + case 0xC9: return run_cmp(); + case 0xCA: return run_dex(); + case 0xCB: return run_axs(); + case 0xCC: return run_cpy(); + case 0xCD: return run_cmp(); + case 0xCE: return run_dec(); + case 0xCF: return run_dcp(); + case 0xD0: return run_bne(); + case 0xD1: return run_cmp(); + case 0xD2: return run_stp(); + case 0xD3: return run_dcp(); + case 0xD4: return run_nop(); + case 0xD5: return run_cmp(); + case 0xD6: return run_dec(); + case 0xD7: return run_dcp(); + case 0xD8: return run_cld(); + case 0xD9: return run_cmp(); + case 0xDA: return run_nop(); + case 0xDB: return run_dcp(); + case 0xDC: return run_nop(); + case 0xDD: return run_cmp(); + case 0xDE: return run_dec(); + case 0xDF: return run_dcp(); + case 0xE0: return run_cpx(); + case 0xE1: return run_sbc(); + case 0xE2: return run_nop(); + case 0xE3: return run_isc(); + case 0xE4: return run_cpx(); + case 0xE5: return run_sbc(); + case 0xE6: return run_inc(); + case 0xE7: return run_isc(); + case 0xE8: return run_inx(); + case 0xE9: return run_sbc(); + case 0xEA: return run_nop(); + case 0xEB: return run_sbc(); + case 0xEC: return run_cpx(); + case 0xED: return run_sbc(); + case 0xEE: return run_inc(); + case 0xEF: return run_isc(); + case 0xF0: return run_beq(); + case 0xF1: return run_sbc(); + case 0xF2: return run_stp(); + case 0xF3: return run_isc(); + case 0xF4: return run_nop(); + case 0xF5: return run_sbc(); + case 0xF6: return run_inc(); + case 0xF7: return run_isc(); + case 0xF8: return run_sed(); + case 0xF9: return run_sbc(); + case 0xFA: return run_nop(); + case 0xFB: return run_isc(); + case 0xFC: return run_nop(); + case 0xFD: return run_sbc(); + case 0xFE: return run_inc(); + case 0xFF: return run_isc(); + default: return status::error_unsupported_instruction; + } + } + + // ----------------------------------------------------------------------------------------------------------------- + // Instructions + // ----------------------------------------------------------------------------------------------------------------- + + template + auto cpu::run_brk() -> status + { + // BRK: Break + fetch_operand(); + execute_interrupt(address{ 0xFFFE }); + return status::success; + } + + template + auto cpu::run_ora() -> status + { + // ORA: Logical Inclusive OR + auto operand = fetch_operand(); + eval_ora(operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + auto cpu::run_stp() -> status + { + return status::error_unsupported_instruction; + } + + template + auto cpu::run_slo() -> status + { + // SLO: ASL + ORA + auto operand = fetch_operand(force_page_crossing::yes); + auto const tmp = eval_asl(operand.read()); + operand.write(tmp); + eval_ora(tmp); + current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_nop() -> status + { + // NOP: NOP + auto operand = fetch_operand(); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + auto cpu::run_nop() -> status + { + // NOP: NOP + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_php() -> status + { + // PHP: Push Processor Status + eval_php(); + current_cycles_ += cycle_count::from_cpu(3); + return status::success; + } + + template + auto cpu::run_asl() -> status + { + // ASL: Arithmetic Shift Left + auto operand = fetch_operand(); + operand.write(eval_asl(operand.read())); + current_cycles_ += shift_cycle_count(); + return status::success; + } + + template + auto cpu::run_anc() -> status + { + // ANC: AND #i + copy N to C + auto operand = fetch_operand(); + eval_and(operand.read()); + registers_.p.set_c(registers_.p.get_n()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_bpl() -> status + { + // BPL: Break If Positive + branch(!registers_.p.get_n()); + return status::success; + } + + auto cpu::run_clc() -> status + { + // CLC: Clear Carry Flag + registers_.p.set_c(false); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_jsr() -> status + { + // JSR: Jump to Subroutine + static_assert(Mode == addressing_mode::absolute, "JSR only supports absolute addressing."); + + push_stack16(registers_.pc + 1); + + auto operand = fetch_operand(); + registers_.pc = operand.get_address().get_absolute(); + current_cycles_ += cycle_count::from_cpu(6); + return status::success; + } + + template + auto cpu::run_and() -> status + { + // AND: Logical AND + auto operand = fetch_operand(); + eval_and(operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_rla() -> status + { + // RLA: ROL + AND + auto operand = fetch_operand(force_page_crossing::yes); + auto const tmp = eval_rol(operand.read()); + operand.write(tmp); + eval_and(tmp); + current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_bit() -> status + { + // BIT: Bit Test + auto operand = fetch_operand(); + auto const arg = operand.read(); + auto const res = (registers_.a & arg) != 0; + registers_.p.set_z(res == 0); + registers_.p.set_v((arg & 0x40) != 0); + registers_.p.set_n((arg & 0x80) != 0); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_rol() -> status + { + // ROR: Rotate Left + auto operand = fetch_operand(); + auto const result = eval_rol(operand.read()); + operand.write(result); + current_cycles_ += shift_cycle_count(); + return status::success; + } + + auto cpu::run_plp() -> status + { + // PLP: Pull Processor Status + eval_plp(); + current_cycles_ += cycle_count::from_cpu(4); + return status::success; + } + + template + auto cpu::run_bmi() -> status + { + branch(registers_.p.get_n()); + return status::success; + } + + auto cpu::run_sec() -> status + { + // SEC: Set Carry Flag + registers_.p.set_c(true); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_rti() -> status + { + // RTI: Return From Interrupt + eval_plp(); + registers_.pc = pop_stack16(); + current_cycles_ += cycle_count::from_cpu(6); + return status::success; + } + + template + auto cpu::run_eor() -> status + { + // EOR: Exclusive OR + auto operand = fetch_operand(); + eval_eor(operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_sre() -> status + { + // SRE: LSR + EOR + auto operand = fetch_operand(force_page_crossing::yes); + auto const tmp = eval_lsr(operand.read()); + operand.write(tmp); + eval_eor(tmp); + current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_lsr() -> status + { + // LSR: Logical Shift Right + auto operand = fetch_operand(); + auto const old_val = operand.read(); + auto const new_val = static_cast(old_val >> 1); + operand.write(new_val); + registers_.p.set_c((old_val & 0x1) != 0); + update_zn(new_val); + current_cycles_ += shift_cycle_count(); + return status::success; + } + + template + auto cpu::run_alr() -> status + { + // ALR: AND #i + LSR A + auto operand = fetch_operand(); + eval_and(operand.read()); + registers_.a = eval_lsr(registers_.a); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + auto cpu::run_pha() -> status + { + // PHA: Push Accumulator + push_stack8(registers_.a); + current_cycles_ += cycle_count::from_cpu(3); + return status::success; + } + + template + auto cpu::run_jmp() -> status + { + // JMP: Jump + auto operand = fetch_operand(); + registers_.pc = operand.get_address().get_absolute(); + current_cycles_ += jump_cycle_count(); + return status::success; + } + + template + auto cpu::run_bvc() -> status + { + // BVC: Branch If Overflow Clear + branch(!registers_.p.get_v()); + return status::success; + } + + auto cpu::run_cli() -> status + { + // CLI: Clear Interrupt Disable + registers_.p.set_i(false); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_rts() -> status + { + // RTS: Return from Subroutine + auto const addr = address{ pop_stack16() } + 1; + registers_.pc = addr.get_absolute(); + current_cycles_ += cycle_count::from_cpu(6); + return status::success; + } + + template + auto cpu::run_adc() -> status + { + // ADC: Add With Carry + auto operand = fetch_operand(); + eval_adc(operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_rra() -> status + { + // RRA: ROR + ADC + auto operand = fetch_operand(force_page_crossing::yes); + auto const tmp = eval_ror(operand.read()); + operand.write(tmp); + eval_adc(tmp); + current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_ror() -> status + { + // ROR: Rotate Right + auto operand = fetch_operand(); + auto const result = eval_ror(operand.read()); + operand.write(result); + current_cycles_ += shift_cycle_count(); + return status::success; + } + + template + auto cpu::run_arr() -> status + { + // ARR: AND #i + ROR A but with different flags + // TODO + return status::error_unsupported_instruction; + } + + auto cpu::run_pla() -> status + { + // PLA: Pull Accumulator + registers_.a = pop_stack8(); + update_zn(registers_.a); + current_cycles_ += cycle_count::from_cpu(4); + return status::success; + } + + template + auto cpu::run_bvs() -> status + { + // BVS: Branch If Overflow Set + branch(registers_.p.get_v()); + return status::success; + } + + auto cpu::run_sei() -> status + { + // SEI: Set Interrupt Disable + registers_.p.set_i(true); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_sta() -> status + { + // STA: Store Accumulator + auto operand = fetch_operand(force_page_crossing::yes); + operand.write(registers_.a); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_ahx() -> status + { + // AHX: ??? + // TODO + return status::error_unsupported_instruction; + } + + template + auto cpu::run_sax() -> status + { + // SAX: AND A X + auto operand = fetch_operand(); + auto const result = static_cast(registers_.a & registers_.x); + operand.write(result); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_sty() -> status + { + // STX: Store Y + auto operand = fetch_operand(); + operand.write(registers_.y); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_stx() -> status + { + // STX: Store X + auto operand = fetch_operand(); + operand.write(registers_.x); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + auto cpu::run_dey() -> status + { + // DEY: Decrement Y Register + registers_.y -= 1; + update_zn(registers_.y); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_txa() -> status + { + // TXA: Transfer X to Accumulator + registers_.a = registers_.x; + update_zn(registers_.a); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_xaa() -> status + { + // XAA: Depends on analog behavior. + run_stp(); + return status::success; + } + + template + auto cpu::run_bcc() -> status + { + // BCC: Branch If Carry Clear + branch(!registers_.p.get_c()); + return status::success; + } + + auto cpu::run_tya() -> status + { + // TYA: Transfer Y to Accumulator + registers_.a = registers_.y; + update_zn(registers_.a); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_txs() -> status + { + // TXS: Transfer X to Stack Pointer + registers_.sp = registers_.x; + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_tas() -> status + { + // TAS: ??? + // TODO + return status::error_unsupported_instruction; + } + + template + auto cpu::run_shy() -> status + { + // SHY: ??? + // TODO + return status::error_unsupported_instruction; + } + + template + auto cpu::run_shx() -> status + { + // SHY: ??? + // TODO + return status::error_unsupported_instruction; + } + + template + auto cpu::run_ldy() -> status + { + // LDY: Load Y Register + auto operand = fetch_operand(); + registers_.y = operand.read(); + update_zn(registers_.y); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_lda() -> status + { + // LDA: Load Accumulator + auto operand = fetch_operand(); + registers_.a = operand.read(); + update_zn(registers_.a); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_ldx() -> status + { + // LDX: Load X Register + auto operand = fetch_operand(); + registers_.x = operand.read(); + update_zn(registers_.x); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_lax() -> status + { + // LAX: LDA + TAX + auto operand = fetch_operand(); + registers_.x = registers_.a = operand.read(); + update_zn(registers_.a); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + auto cpu::run_tay() -> status + { + // TAY: Transfer Accumulator to Y + registers_.y = registers_.a; + update_zn(registers_.y); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_tax() -> status + { + // TAX: Transfer Accumulator to X + registers_.x = registers_.a; + update_zn(registers_.x); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_bcs() -> status + { + // BCS: Branch If Carry Set + branch(registers_.p.get_c()); + return status::success; + } + + auto cpu::run_clv() -> status + { + // CLV: Clear Overflow Flag + registers_.p.set_v(false); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_tsx() -> status + { + // TSX: Transfer Stack Pointer to X + registers_.x = registers_.sp; + update_zn(registers_.x); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_las() -> status + { + // LAS: ??? + // TODO + return status::error_unsupported_instruction; + } + + template + auto cpu::run_cpy() -> status + { + // CPY: Compare Y Register + auto operand = fetch_operand(); + eval_cmp(registers_.y, operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_axs() -> status + { + // AXS: Set X to ((A AND X) - #value without borrow), updates NZC + // TODO + return status::error_unsupported_instruction; + } + + template + auto cpu::run_cmp() -> status + { + // CMP: Compare + auto operand = fetch_operand(); + eval_cmp(registers_.a, operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_dcp() -> status + { + // DCP: DEC + CMP + auto operand = fetch_operand(force_page_crossing::yes); + auto const tmp = static_cast(operand.read() - 1); + operand.write(tmp); + eval_cmp(registers_.a, tmp); + current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_dec() -> status + { + // DEC: Decrement Memory + auto operand = fetch_operand(); + auto const old_val = read8(operand.get_address()); + auto const new_val = static_cast(old_val - 1); + write8(operand.get_address(), new_val); + update_zn(new_val); + current_cycles_ += inc_dec_cycle_count(); + return status::success; + } + + auto cpu::run_iny() -> status + { + // INY: Increment Y + registers_.y += 1; + update_zn(registers_.y); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + auto cpu::run_dex() -> status + { + // DEX: Decrement X Register + registers_.x -= 1; + update_zn(registers_.x); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_bne() -> status + { + // BNE: Branch If Not Equal + branch(!registers_.p.get_z()); + return status::success; + } + + auto cpu::run_cld() -> status + { + // CLD: Clear Decimal Mode + registers_.p.set_d(false); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_cpx() -> status + { + // CPX: Compare X Register + auto operand = fetch_operand(); + eval_cmp(registers_.x, operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_sbc() -> status + { + // SBC: Subtract With Carry + auto operand = fetch_operand(); + eval_adc(~operand.read()); + current_cycles_ += operand.get_cycles(); + return status::success; + } + + template + auto cpu::run_isc() -> status + { + // ISC: INC + SBC + auto operand = fetch_operand(force_page_crossing::yes); + auto const tmp = static_cast(operand.read() + 1); + operand.write(tmp); + eval_adc(~tmp); + current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_inc() -> status + { + // INC: Increment Memory + auto operand = fetch_operand(); + auto const old_val = read8(operand.get_address()); + auto const new_val = static_cast(old_val + 1); + write8(operand.get_address(), new_val); + update_zn(new_val); + current_cycles_ += inc_dec_cycle_count(); + return status::success; + } + + auto cpu::run_inx() -> status + { + // INX: Increment X + registers_.x += 1; + update_zn(registers_.x); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + template + auto cpu::run_beq() -> status + { + // BEQ: Branch If Equal + branch(registers_.p.get_z()); + return status::success; + } + + auto cpu::run_sed() -> status + { + // SED: Set Decimal Flag + registers_.p.set_d(true); + current_cycles_ += cycle_count::from_cpu(2); + return status::success; + } + + // ----------------------------------------------------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------------------------------------------------- + + auto cpu::advance_pc8() -> std::uint8_t + { + auto const val = read8(address{ registers_.pc }); + registers_.pc += 1; + return val; + } + + auto cpu::advance_pc16() -> std::uint16_t + { + auto const val = read16(address{ registers_.pc }); + registers_.pc += 2; + return val; + } + + auto cpu::push_stack8(std::uint8_t const value) -> void + { + write8(stack_offset + registers_.sp, value); + registers_.sp -= 1; + } + + auto cpu::push_stack16(std::uint16_t const value) -> void + { + auto const low = static_cast(value >> 0 & 0xFF); + auto const high = static_cast(value >> 8 & 0xFF); + push_stack8(high); + push_stack8(low); + } + + auto cpu::pop_stack8() -> std::uint8_t + { + registers_.sp += 1; + return read8(stack_offset + registers_.sp); + } + + auto cpu::pop_stack16() -> std::uint16_t + { + auto const low = pop_stack8(); + auto const high = pop_stack8(); + return static_cast((high << 8) | (low << 0)); + } + + auto cpu::update_zn(std::uint8_t const value) -> void + { + registers_.p.set_z(value == 0); + registers_.p.set_n((value & 0x80) != 0); + } + + template + auto cpu::branch(bool const condition) -> void + { + static_assert(Mode == addressing_mode::relative, "Branch should only be used with relative operands."); + + auto operand = fetch_operand(); + if (condition) + { + registers_.pc = operand.get_address().get_absolute(); + current_cycles_ += operand.get_cycles(); + } + else + { + current_cycles_ += cycle_count::from_cpu(2); + } + } + + auto cpu::execute_interrupt(address const vector) -> void + { + push_stack16(registers_.pc); + eval_php(); + registers_.pc = read16(vector); + registers_.p.set_i(true); + current_cycles_ += cycle_count::from_cpu(7); + } + + auto cpu::eval_ror(std::uint8_t const arg) -> std::uint8_t + { + auto const res = static_cast(((registers_.p.get_c() ? 1 :0) << 7) | (arg >> 1)); + registers_.p.set_c((arg & 0x1) != 0); + update_zn(res); + return res; + } + + auto cpu::eval_rol(std::uint8_t const arg) -> std::uint8_t + { + auto const res = static_cast((arg << 1) | (registers_.p.get_c() ? 1 : 0)); + registers_.p.set_c((arg & 0x80) != 0); + update_zn(res); + return res; + } + + auto cpu::eval_asl(std::uint8_t const arg) -> std::uint8_t + { + auto const new_val = static_cast(arg << 1); + registers_.p.set_c((arg & 0x80) != 0); + update_zn(new_val); + return new_val; + } + + auto cpu::eval_lsr(std::uint8_t const arg) -> std::uint8_t + { + auto const new_val = static_cast(arg >> 1); + registers_.p.set_c((arg & 0x01) != 0); + update_zn(new_val); + return new_val; + } + + auto cpu::eval_adc(std::uint8_t const arg) -> void + { + auto const old_val = registers_.a; + auto const tmp = old_val + arg + (registers_.p.get_c() ? 1 : 0); + auto const new_val = static_cast(tmp); + + registers_.a = new_val; + registers_.p.set_c(tmp > 0xFF); + registers_.p.set_v(((old_val ^ arg) & 0x80) == 0 && ((old_val ^ new_val) & 0x80) != 0); + update_zn(registers_.a); + } + + auto cpu::eval_and(std::uint8_t const arg) -> void + { + registers_.a &= arg; + update_zn(registers_.a); + } + + auto cpu::eval_ora(std::uint8_t const arg) -> void + { + registers_.a |= arg; + update_zn(registers_.a); + } + + auto cpu::eval_eor(std::uint8_t const arg) -> void + { + registers_.a ^= arg; + update_zn(registers_.a); + } + + auto cpu::eval_cmp(std::uint8_t const a, std::uint8_t const b) -> void + { + update_zn(a - b); + registers_.p.set_c(a >= b); + } + + auto cpu::eval_plp() -> void + { + // Ignore bits 4 and 5 + registers_.p.value = + (pop_stack8() & 0b11001111) | + (registers_.p.value & 0b00110000); + } + + auto cpu::eval_php() -> void + { + // Always set bits 4 and 5. + push_stack8(registers_.p.value | 0b00110000); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Operands + // ----------------------------------------------------------------------------------------------------------------- + + // + // See: https://www.nesdev.org/wiki/CPU_addressing_modes + // + + template + auto cpu::fetch_operand(force_page_crossing const force_page_crossing) -> operand + { + auto addr = address{}; + auto cycles = cycle_count{}; + + switch (Mode) + { + case addressing_mode::immediate: static_assert(Mode != addressing_mode::immediate); + case addressing_mode::accumulator: static_assert(Mode != addressing_mode::accumulator); + case addressing_mode::zero_page: + { + addr = address{ advance_pc8() }; + cycles = cycle_count::from_cpu(3); + break; + } + case addressing_mode::absolute: + { + addr = address{ advance_pc16() }; + cycles = cycle_count::from_cpu(4); + break; + } + case addressing_mode::relative: + { + auto const offset = static_cast(advance_pc8()); + auto const base = address{ registers_.pc }; + addr = address{ static_cast(registers_.pc + offset) }; + auto const page_crossing = + base.get_page() != addr.get_page() || force_page_crossing == force_page_crossing::yes; + cycles = cycle_count::from_cpu(page_crossing ? 4 : 3); + break; + } + case addressing_mode::indirect: + { + auto const arg = address{ advance_pc16() }; + // Account for buggy indirect read: https://www.nesdev.org/wiki/Errata + addr = arg.get_offset() == 0xFF + ? address{ read8(address{ arg.get_page(), 0x00 }), read8(arg) } + : address{ read16(arg) }; + cycles = cycle_count::from_cpu(6); + break; + } + case addressing_mode::zero_page_indexed_x: + { + // PEEK((arg + X) % 256) + addr = address{ 0x00, static_cast(advance_pc8() + registers_.x) }; + cycles = cycle_count::from_cpu(4); + break; + } + case addressing_mode::zero_page_indexed_y: + { + // PEEK((arg + Y) % 256) + addr = address{ 0x00, static_cast(advance_pc8() + registers_.y) }; + cycles = cycle_count::from_cpu(4); + break; + } + case addressing_mode::absolute_indexed_x: + { + // PEEK(arg + X) + auto const arg = address{ advance_pc16() }; + addr = arg + registers_.x; + auto const page_crossing = + addr.get_page() != arg.get_page() || force_page_crossing == force_page_crossing::yes; + cycles = cycle_count::from_cpu(page_crossing ? 5 : 4); + break; + } + case addressing_mode::absolute_indexed_y: + { + // PEEK(arg + Y) + auto const arg = address{ advance_pc16() }; + addr = arg + registers_.y; + auto const page_crossing = + addr.get_page() != arg.get_page() || force_page_crossing == force_page_crossing::yes; + cycles = cycle_count::from_cpu(page_crossing ? 5 : 4); + break; + } + case addressing_mode::indexed_indirect: + { + // PEEK(PEEK((arg + X) % 256) + PEEK((arg + X + 1) % 256) * 256) + auto const arg = advance_pc8(); + auto const page = read8(address{ 0x00, static_cast(arg + registers_.x + 1) }); + auto const offset = read8(address{ 0x00, static_cast(arg + registers_.x) }); + addr = address{ page, offset }; + cycles = cycle_count::from_cpu(6); + break; + } + case addressing_mode::indirect_indexed: + { + // PEEK(PEEK(arg) + PEEK((arg + 1) % 256) * 256 + Y) + auto const arg = advance_pc8(); + auto const page = read8(address{ 0x00, static_cast(arg + 1) }); + auto const offset = read8(address{ 0x00, static_cast(arg) }); + auto const base = address{ page, offset }; + addr = base + registers_.y; + auto const page_crossing = + addr.get_page() != base.get_page() || force_page_crossing == force_page_crossing::yes; + cycles = cycle_count::from_cpu(page_crossing ? 6 : 5); + break; + } + } + + return operand{ *this, addr, cycles }; + } + + template<> + auto cpu::fetch_operand(force_page_crossing) + -> operand + { + return operand{ *this }; + } + + template<> + auto cpu::fetch_operand(force_page_crossing) + -> operand + { + return operand{ advance_pc8() }; + } + + // ----------------------------------------------------------------------------------------------------------------- + // Memory Access + // ----------------------------------------------------------------------------------------------------------------- + + // + // See: https://www.nesdev.org/wiki/CPU_memory_map + // + + auto cpu::read8(address const addr) -> std::uint8_t + { + if (addr <= address{ 0x1FFF }) { return ram_[addr.get_absolute() % ram_size]; } + if (addr <= address{ 0x3FFF }) + { +#ifdef NES_ENABLE_PPU + switch (addr.get_absolute() % 8) + { + case 0: return ppu_.read_latch(); + case 1: return ppu_.read_latch(); + case 2: return ppu_.read_ppustatus(); + case 3: return ppu_.read_latch(); + case 4: return ppu_.read_oamdata(); + case 5: return ppu_.read_latch(); + case 6: return ppu_.read_latch(); + case 7: return ppu_.read_ppudata(); + default: return 0x0; + } +#else + return 0x0; +#endif + } + if (addr <= address{ 0x4013 }) { return 0x0; } // TODO: APU registers +#ifdef NES_ENABLE_PPU + if (addr == address{ 0x4014 }) { return ppu_.read_latch(); } +#else + if (addr == address{ 0x4014 }) { return 0x0; } +#endif + if (addr == address{ 0x4015 }) { return 0x0; } // TODO: APU registers + if (addr == address{ 0x4016 }) { return controller_1_.read(); } + if (addr == address{ 0x4017 }) { return controller_2_.read(); } + if (addr <= address{ 0x401F }) { return 0x0; } + return cartridge_.get_mapper().read_cpu(addr, cartridge_); + } + + auto cpu::read16(address const addr) -> std::uint16_t + { + auto const low = read8(addr + 0); + auto const high = read8(addr + 1); + return static_cast((high << 8) | (low << 0)); + } + + auto cpu::write8(address const addr, std::uint8_t const value) -> void + { + if (addr <= address{ 0x1FFF }) { ram_[addr.get_absolute() % ram_size] = value; return; } + if (addr <= address{ 0x3FFF }) + { +#ifdef NES_ENABLE_PPU + switch (addr.get_absolute() % 8) + { + case 0: ppu_.write_ppuctrl(value); return; + case 1: ppu_.write_ppumask(value); return; + case 2: ppu_.write_latch(value); return; + case 3: ppu_.write_oamaddr(value); return; + case 4: ppu_.write_oamdata(value); return; + case 5: ppu_.write_ppuscroll(value); return; + case 6: ppu_.write_ppuaddr(value); return; + case 7: ppu_.write_ppudata(value); return; + default: return; + } +#else + return; +#endif + } + if (addr <= address{ 0x4013 }) { return; } // TODO: APU registers +#ifdef NES_ENABLE_PPU + if (addr == address{ 0x4014 }) { ppu_.write_oamdma(value); return; } +#else + if (addr == address{ 0x4014 }) { return; } +#endif + if (addr == address{ 0x4015 }) { return; } // TODO: APU registers + if (addr == address{ 0x4016 }) { controller_1_.write(value); return; } + if (addr == address{ 0x4017 }) { controller_2_.write(value); return; } + if (addr <= address{ 0x401F }) { return; } + cartridge_.get_mapper().write_cpu(addr, value, cartridge_); + } + + auto cpu::write16(address const addr, std::uint16_t const value) -> void + { + auto const low = static_cast(value >> 0 & 0xFF); + auto const high = static_cast(value >> 8 & 0xFF); + write8(addr + 0, low); + write8(addr + 1, high); + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/cpu.hh b/lib/nes/sys/cpu.hh new file mode 100644 index 0000000..d0901c0 --- /dev/null +++ b/lib/nes/sys/cpu.hh @@ -0,0 +1,300 @@ +#pragma once + +#include "nes/sys/types/cycle-count.hh" +#include "nes/sys/types/address.hh" +#include "nes/sys/types/status.hh" + +namespace nes +{ + class ppu; + class controller; + class cartridge; + struct snapshot; + + class cpu + { + static constexpr auto ram_size = std::size_t{ 0x800 }; + static constexpr auto stack_offset = address{ 0x100 }; + + cycle_count current_cycles_; + ppu& ppu_; + cartridge& cartridge_; + controller& controller_1_; + controller& controller_2_; + std::uint8_t ram_[ram_size]{}; + bool nmi_pending_{ false }; + + // Registers + struct + { + std::uint16_t pc{ 0 }; + std::uint8_t sp{ 0xFD }; + std::uint8_t a{ 0 }; + std::uint8_t x{ 0 }; + std::uint8_t y{ 0 }; + struct + { + auto get_c() const -> bool { return value & 0b00000001; } // carry + auto get_z() const -> bool { return value & 0b00000010; } // zero + auto get_i() const -> bool { return value & 0b00000100; } // interrupt inhibit + auto get_d() const -> bool { return value & 0b00001000; } // decimal + auto get_b() const -> bool { return value & 0b00010000; } // break + auto get_v() const -> bool { return value & 0b01000000; } // overflow + auto get_n() const -> bool { return value & 0b10000000; } // negative + + auto set_c(bool const v) -> void { value = (value & ~0b00000001) | (v ? 0b00000001 : 0); } + auto set_z(bool const v) -> void { value = (value & ~0b00000010) | (v ? 0b00000010 : 0); } + auto set_i(bool const v) -> void { value = (value & ~0b00000100) | (v ? 0b00000100 : 0); } + auto set_d(bool const v) -> void { value = (value & ~0b00001000) | (v ? 0b00001000 : 0); } + auto set_b(bool const v) -> void { value = (value & ~0b00010000) | (v ? 0b00010000 : 0); } + auto set_v(bool const v) -> void { value = (value & ~0b01000000) | (v ? 0b01000000 : 0); } + auto set_n(bool const v) -> void { value = (value & ~0b10000000) | (v ? 0b10000000 : 0); } + + std::uint8_t value{ 0b00100100 }; + } p{}; + } registers_{}; + + enum class addressing_mode + { + accumulator, // A + immediate, // #v + zero_page, // d + absolute, // a + relative, // *+d + indirect, // (a) + zero_page_indexed_x, // d,x + zero_page_indexed_y, // d,y + absolute_indexed_x, // a,x + absolute_indexed_y, // a,y + indexed_indirect, // (d,x) + indirect_indexed, // (d),y + }; + + enum class force_page_crossing + { + no, + yes, + }; + + template + class operand + { + cpu& cpu_; + address address_; + cycle_count cycles_; + + public: + explicit operand(cpu& cpu, address const address, cycle_count const cycles) + : cpu_{ cpu } + , address_{ address } + , cycles_{ cycles } + { + } + + auto read() -> std::uint8_t { return cpu_.read8(address_); } + auto write(std::uint8_t const value) -> void { cpu_.write8(address_, value); } + auto get_address() -> address { return address_; } + auto get_cycles() -> cycle_count { return cycles_; } + }; + + template<> + class operand + { + cpu& cpu_; + + public: + explicit operand(cpu& cpu) + : cpu_{ cpu } + { + } + + auto read() -> std::uint8_t { return cpu_.registers_.a; } + auto write(std::uint8_t const value) -> void { cpu_.registers_.a = value; } + auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); } + }; + + template<> + class operand + { + std::uint8_t value_{ 0 }; + + public: + explicit operand(std::uint8_t const value) + : value_{ value } + { + } + + auto read() -> std::uint8_t { return value_; } + auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); } + }; + + public: + explicit cpu(ppu&, cartridge&, controller& controller_1, controller& controller_2); + + cpu(cpu const&) = delete; + cpu(cpu&&) = delete; + auto operator=(cpu const&) -> cpu& = delete; + auto operator=(cpu&&) -> cpu& = delete; + + auto get_cycles() const -> cycle_count { return current_cycles_; } + auto build_snapshot(snapshot&) -> void; + auto stall_cycles(cycle_count) -> void; + auto trigger_nmi() -> void; + auto step() -> status; + auto is_nmi_pending() const -> bool { return nmi_pending_; } // XXX: Debugging + + // Memory access + + auto read8(address) -> std::uint8_t; + auto read16(address) -> std::uint16_t; + auto write8(address, std::uint8_t) -> void; + auto write16(address, std::uint16_t) -> void; + + private: + // Instructions + +#define DEFINE_SIMPLE_INSTRUCTION(name) \ + auto run_##name() -> status; +#define DEFINE_OPERAND_INSTRUCTION(name) \ + template auto run_##name() -> status; + + DEFINE_OPERAND_INSTRUCTION(brk) + DEFINE_OPERAND_INSTRUCTION(ora) + DEFINE_SIMPLE_INSTRUCTION(stp) + DEFINE_OPERAND_INSTRUCTION(slo) + DEFINE_OPERAND_INSTRUCTION(nop) + DEFINE_SIMPLE_INSTRUCTION(nop) + DEFINE_SIMPLE_INSTRUCTION(php) + DEFINE_OPERAND_INSTRUCTION(asl) + DEFINE_OPERAND_INSTRUCTION(anc) + DEFINE_OPERAND_INSTRUCTION(bpl) + DEFINE_SIMPLE_INSTRUCTION(clc) + DEFINE_OPERAND_INSTRUCTION(jsr) + DEFINE_OPERAND_INSTRUCTION(and) + DEFINE_OPERAND_INSTRUCTION(rla) + DEFINE_OPERAND_INSTRUCTION(bit) + DEFINE_OPERAND_INSTRUCTION(rol) + DEFINE_SIMPLE_INSTRUCTION(plp) + DEFINE_OPERAND_INSTRUCTION(bmi) + DEFINE_SIMPLE_INSTRUCTION(sec) + DEFINE_SIMPLE_INSTRUCTION(rti) + DEFINE_OPERAND_INSTRUCTION(eor) + DEFINE_OPERAND_INSTRUCTION(sre) + DEFINE_OPERAND_INSTRUCTION(lsr) + DEFINE_OPERAND_INSTRUCTION(alr) + DEFINE_SIMPLE_INSTRUCTION(pha) + DEFINE_OPERAND_INSTRUCTION(jmp) + DEFINE_OPERAND_INSTRUCTION(bvc) + DEFINE_SIMPLE_INSTRUCTION(cli) + DEFINE_SIMPLE_INSTRUCTION(rts) + DEFINE_OPERAND_INSTRUCTION(adc) + DEFINE_OPERAND_INSTRUCTION(rra) + DEFINE_OPERAND_INSTRUCTION(ror) + DEFINE_OPERAND_INSTRUCTION(arr) + DEFINE_SIMPLE_INSTRUCTION(pla) + DEFINE_OPERAND_INSTRUCTION(bvs) + DEFINE_SIMPLE_INSTRUCTION(sei) + DEFINE_OPERAND_INSTRUCTION(sta) + DEFINE_OPERAND_INSTRUCTION(ahx) + DEFINE_OPERAND_INSTRUCTION(sax) + DEFINE_OPERAND_INSTRUCTION(sty) + DEFINE_OPERAND_INSTRUCTION(stx) + DEFINE_SIMPLE_INSTRUCTION(dey) + DEFINE_SIMPLE_INSTRUCTION(txa) + DEFINE_OPERAND_INSTRUCTION(xaa) + DEFINE_OPERAND_INSTRUCTION(bcc) + DEFINE_SIMPLE_INSTRUCTION(tya) + DEFINE_SIMPLE_INSTRUCTION(txs) + DEFINE_OPERAND_INSTRUCTION(tas) + DEFINE_OPERAND_INSTRUCTION(shy) + DEFINE_OPERAND_INSTRUCTION(shx) + DEFINE_OPERAND_INSTRUCTION(ldy) + DEFINE_OPERAND_INSTRUCTION(lda) + DEFINE_OPERAND_INSTRUCTION(ldx) + DEFINE_OPERAND_INSTRUCTION(lax) + DEFINE_SIMPLE_INSTRUCTION(tay) + DEFINE_SIMPLE_INSTRUCTION(tax) + DEFINE_OPERAND_INSTRUCTION(bcs) + DEFINE_SIMPLE_INSTRUCTION(clv) + DEFINE_SIMPLE_INSTRUCTION(tsx) + DEFINE_OPERAND_INSTRUCTION(las) + DEFINE_OPERAND_INSTRUCTION(cpy) + DEFINE_OPERAND_INSTRUCTION(axs) + DEFINE_OPERAND_INSTRUCTION(cmp) + DEFINE_OPERAND_INSTRUCTION(dcp) + DEFINE_OPERAND_INSTRUCTION(dec) + DEFINE_SIMPLE_INSTRUCTION(iny) + DEFINE_SIMPLE_INSTRUCTION(dex) + DEFINE_OPERAND_INSTRUCTION(bne) + DEFINE_SIMPLE_INSTRUCTION(cld) + DEFINE_OPERAND_INSTRUCTION(cpx) + DEFINE_OPERAND_INSTRUCTION(sbc) + DEFINE_OPERAND_INSTRUCTION(isc) + DEFINE_OPERAND_INSTRUCTION(inc) + DEFINE_SIMPLE_INSTRUCTION(inx) + DEFINE_OPERAND_INSTRUCTION(beq) + DEFINE_SIMPLE_INSTRUCTION(sed) + +#undef DEFINE_SIMPLE_INSTRUCTION +#undef DEFINE_OPERAND_INSTRUCTION + + // Helpers + + auto advance_pc8() -> std::uint8_t; + auto advance_pc16() -> std::uint16_t; + auto push_stack8(std::uint8_t) -> void; + auto push_stack16(std::uint16_t) -> void; + auto pop_stack8() -> std::uint8_t; + auto pop_stack16() -> std::uint16_t; + auto update_zn(std::uint8_t value) -> void; + template + auto branch(bool condition) -> void; + auto execute_interrupt(address) -> void; + + auto eval_ror(std::uint8_t arg) -> std::uint8_t; + auto eval_rol(std::uint8_t arg) -> std::uint8_t; + auto eval_asl(std::uint8_t arg) -> std::uint8_t; + auto eval_lsr(std::uint8_t arg) -> std::uint8_t; + auto eval_adc(std::uint8_t arg) -> void; + auto eval_and(std::uint8_t arg) -> void; + auto eval_ora(std::uint8_t arg) -> void; + auto eval_eor(std::uint8_t arg) -> void; + auto eval_cmp(std::uint8_t a, std::uint8_t b) -> void; + auto eval_plp() -> void; + auto eval_php() -> void; + + // Operands + +#define DEFINE_CYCLE_COUNT(name) \ + template static auto name##_cycle_count() -> cycle_count; +#define DEFINE_CYCLE_COUNT_INSTANCE(name, mode, value) \ + template<> auto name##_cycle_count() -> cycle_count { return cycle_count::from_cpu(value); } + + DEFINE_CYCLE_COUNT(jump) + DEFINE_CYCLE_COUNT_INSTANCE(jump, absolute, 3) + DEFINE_CYCLE_COUNT_INSTANCE(jump, indirect, 5) + + DEFINE_CYCLE_COUNT(shift) + DEFINE_CYCLE_COUNT_INSTANCE(shift, accumulator, 2) + DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page, 5) + DEFINE_CYCLE_COUNT_INSTANCE(shift, zero_page_indexed_x, 6) + DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute, 6) + DEFINE_CYCLE_COUNT_INSTANCE(shift, absolute_indexed_x, 7) + + DEFINE_CYCLE_COUNT(inc_dec) + DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page, 5) + DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, zero_page_indexed_x, 6) + DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute, 6) + DEFINE_CYCLE_COUNT_INSTANCE(inc_dec, absolute_indexed_x, 7) + +#undef DEFINE_CYCLE_COUNT +#undef DEFINE_CYCLE_COUNT_INSTANCE + + template + auto fetch_operand(force_page_crossing = force_page_crossing::no) -> operand; + template<> + auto fetch_operand(force_page_crossing) -> operand; + template<> + auto fetch_operand(force_page_crossing) -> operand; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/mapper.cc b/lib/nes/sys/mapper.cc new file mode 100644 index 0000000..4951966 --- /dev/null +++ b/lib/nes/sys/mapper.cc @@ -0,0 +1,172 @@ +#include "nes/sys/mapper.hh" +#include "nes/sys/cartridge.hh" + +namespace nes +{ + namespace + { + // ------------------------------------------------------------------------------------------------------------- + // NROM + // ------------------------------------------------------------------------------------------------------------- + + class mapper_nrom final : public mapper + { + public: + explicit mapper_nrom() = default; + + auto validate(cartridge& cartridge) -> status override + { + if (cartridge.get_ram_length() != 0x2000) + { + return status::error_invalid_ines_data; + } + + if (cartridge.get_prg_rom_length() != 0x4000 && cartridge.get_prg_rom_length() != 0x8000) + { + return status::error_invalid_ines_data; + } + + if (cartridge.get_chr_rom_length() != 0x2000) + { + return status::error_invalid_ines_data; + } + + return status::success; + } + + auto read_cpu(address const addr, cartridge& cartridge) -> std::uint8_t override + { + if (addr <= address{ 0x5FFF }) + { + return 0x0; + } + if (addr <= address{ 0x7FFF }) + { + return cartridge.get_ram()[addr.get_absolute() - 0x6000]; + } + if (addr <= address{ 0xBFFF }) + { + auto const rel = addr.get_absolute() - 0x8000u; + return cartridge.get_prg_rom()[rel]; + } + if (addr <= address{ 0xFFFF }) + { + auto rel = addr.get_absolute() - 0xC000u; + if (cartridge.get_prg_rom_length() > 0x4000) { rel += 0x4000; } + return cartridge.get_prg_rom()[rel]; + } + + return 0x0; + } + + auto write_cpu(address const addr, std::uint8_t const value, cartridge& cartridge) -> void override + { + if (addr <= address{ 0x5FFF }) + { + return; + } + if (addr <= address{ 0x7FFF }) + { + cartridge.get_ram_mut()[addr.get_absolute() - 0x6000] = value; + return; + } + } + + auto read_ppu(address const addr, cartridge& cartridge, std::uint8_t const* vram) -> std::uint8_t override + { + if (addr <= address{ 0x1FFF }) + { + auto const rel = addr.get_absolute(); + return cartridge.get_chr_rom()[rel]; + } + if (addr <= address{ 0x3EFF }) + { + return vram[mirrored_vram_address(addr, cartridge)]; + } + + return 0x0; + } + + auto write_ppu(address const addr, std::uint8_t const value, cartridge& cartridge, std::uint8_t* vram) -> void override + { + if (addr <= address{ 0x1FFF }) + { + return; + } + if (addr <= address{ 0x3EFF }) + { + vram[mirrored_vram_address(addr, cartridge)] = value; + return; + } + } + + private: + static auto mirrored_vram_address(address const addr, cartridge const& cartridge) -> unsigned + { + auto const rel = addr.get_absolute() % 0x1000u; + + switch (cartridge.get_name_table_arrangement()) + { + case name_table_arrangement::horizontal: + // $2000 = $2400, $2800 = $2c00 + if (rel <= 0x3FF) { return 0x000u + (rel % 0x400u); } + if (rel <= 0x7FF) { return 0x000u + (rel % 0x400u); } + if (rel <= 0xBFF) { return 0x400u + (rel % 0x400u); } + if (rel <= 0xFFF) { return 0x400u + (rel % 0x400u); } + break; + case name_table_arrangement::vertical: + // $2000 = $2800, $2400 = $2c00 + if (rel <= 0x3FF) { return 0x000u + (rel % 0x400u); } + if (rel <= 0x7FF) { return 0x400u + (rel % 0x400u); } + if (rel <= 0xBFF) { return 0x000u + (rel % 0x400u); } + if (rel <= 0xFFF) { return 0x400u + (rel % 0x400u); } + break; + } + + return 0; + } + }; + + // ------------------------------------------------------------------------------------------------------------- + // Invalid + // ------------------------------------------------------------------------------------------------------------- + + class mapper_invalid final : public mapper + { + public: + explicit mapper_invalid() = default; + + auto validate(cartridge&) -> status override { return status::error_unsupported_mapper; } + auto read_cpu(address, cartridge&) -> std::uint8_t override { return 0; } + auto write_cpu(address, std::uint8_t, cartridge&) -> void override {} + auto read_ppu(address, cartridge&, std::uint8_t const*) -> std::uint8_t override { return 0; } + auto write_ppu(address, std::uint8_t, cartridge&, std::uint8_t*) -> void override {} + }; + } // namespace + + // ----------------------------------------------------------------------------------------------------------------- + // Instances + // ----------------------------------------------------------------------------------------------------------------- + + auto mapper::invalid() -> mapper& + { + static auto instance = mapper_invalid{}; + return instance; + } + + auto mapper::get(std::uint8_t const number) -> mapper& + { + switch (number) + { + case 0x00: + { + static auto instance = mapper_nrom{}; + return instance; + } + default: + { + return invalid(); + } + } + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/mapper.hh b/lib/nes/sys/mapper.hh new file mode 100644 index 0000000..e196b25 --- /dev/null +++ b/lib/nes/sys/mapper.hh @@ -0,0 +1,32 @@ +#pragma once + +#include "nes/sys/types/address.hh" +#include "nes/sys/types/status.hh" + +namespace nes +{ + class cartridge; + + class mapper + { + public: + static auto invalid() -> mapper&; + static auto get(std::uint8_t number) -> mapper&; + + virtual ~mapper() = default; + + mapper(mapper const&) = delete; + mapper(mapper&&) = delete; + auto operator=(mapper const&) -> mapper& = delete; + auto operator=(mapper&&) -> mapper& = delete; + + virtual auto validate(cartridge&) -> status = 0; + virtual auto read_cpu(address, cartridge&) -> std::uint8_t = 0; + virtual auto write_cpu(address, std::uint8_t, cartridge&) -> void = 0; + virtual auto read_ppu(address, cartridge&, std::uint8_t const* vram) -> std::uint8_t = 0; + virtual auto write_ppu(address, std::uint8_t, cartridge&, std::uint8_t* vram) -> void = 0; + + protected: + explicit mapper() = default; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/nes.cc b/lib/nes/sys/nes.cc new file mode 100644 index 0000000..f922b7d --- /dev/null +++ b/lib/nes/sys/nes.cc @@ -0,0 +1,65 @@ +#include "nes/sys/nes.hh" +#include "nes/sys/types/snapshot.hh" +#include "nes/common/display.hh" +#include "nes/common/rgb.hh" + +namespace nes +{ + nes::nes(cartridge cartridge, display& display) + : cartridge_{ std::move(cartridge) } + , display_{ display } + , ppu_{ cpu_, cartridge_, display_ } + , cpu_{ ppu_, cartridge_, controller_1_, controller_2_ } + , status_{ cartridge_.get_status() } + { + display_.clear(rgb{}); + display_.switch_buffers(); + display_.clear(rgb{}); + } + + auto nes::step(std::chrono::microseconds const delta) -> void + { + step(cycle_count::from_duration(delta)); + } + + auto nes::step() -> void + { + if (get_status() != status::success) { return; } + + status_ = cpu_.step(); + while (ppu_.get_cycles() < cpu_.get_cycles()) + { + ppu_.step(); + } + } + + auto nes::step(cycle_count const delta) -> void + { + current_cycles_ += delta; + while (cpu_.get_cycles() < current_cycles_ && get_status() == status::success) + { + step(); + } + } + + auto nes::step_to_nmi() -> void + { + while (cpu_.is_nmi_pending()) + { + step(); + } + while (!cpu_.is_nmi_pending()) + { + step(); + } + } + + auto nes::get_snapshot() -> snapshot + { + auto res = snapshot{}; + res.sram = std::vector(cartridge_.get_ram(), cartridge_.get_ram() + cartridge_.get_ram_length()); + cpu_.build_snapshot(res); + ppu_.build_snapshot(res); + return res; + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/nes.hh b/lib/nes/sys/nes.hh new file mode 100644 index 0000000..b421a5f --- /dev/null +++ b/lib/nes/sys/nes.hh @@ -0,0 +1,47 @@ +#pragma once + +#include "nes/sys/types/cycle-count.hh" +#include "nes/sys/cartridge.hh" +#include "nes/sys/controller.hh" +#include "nes/sys/cpu.hh" +#include "nes/sys/ppu.hh" + +namespace nes +{ + class display; + struct snapshot; + + /// The main console abstraction. + class nes + { + cartridge cartridge_; + display& display_; + controller controller_1_; + controller controller_2_; + ppu ppu_; + cpu cpu_; + cycle_count current_cycles_; + status status_{ status::success }; + + public: + explicit nes(cartridge, display&); + + nes(nes const&) = delete; + nes(nes&&) = delete; + auto operator=(nes const&) -> nes& = delete; + auto operator=(nes&&) -> nes& = delete; + + auto get_status() const -> status { return status_; } + auto get_controller_1() const -> controller const& { return controller_1_; } + auto ref_controller_1() -> controller& { return controller_1_; } + auto get_controller_2() const -> controller const& { return controller_2_; } + auto ref_controller_2() -> controller& { return controller_2_; } + + auto step() -> void; + auto step(std::chrono::microseconds delta) -> void; + auto step(cycle_count delta) -> void; + auto step_to_nmi() -> void; + + auto get_snapshot() -> snapshot; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/ppu.cc b/lib/nes/sys/ppu.cc new file mode 100644 index 0000000..2a5f01c --- /dev/null +++ b/lib/nes/sys/ppu.cc @@ -0,0 +1,697 @@ +#include "nes/sys/ppu.hh" +#include "nes/sys/cpu.hh" +#include "nes/sys/cartridge.hh" +#include "nes/sys/types/address.hh" +#include "nes/sys/types/snapshot.hh" +#include "nes/common/display.hh" +#include "nes/common/rgb.hh" +#include "nes/common/debug.hh" + +namespace nes +{ + ppu::ppu(cpu& cpu, cartridge& cartridge, display& display) + : cpu_{ cpu } + , cartridge_{ cartridge } + , display_{ display } + { + } + + auto ppu::build_snapshot(snapshot& snapshot) -> void + { + snapshot.vram = std::vector(std::begin(vram_), std::end(vram_)); + snapshot.oam = std::vector(std::begin(oam_), std::end(oam_)); + } + + auto ppu::step() -> void + { + // See https://www.nesdev.org/wiki/PPU_rendering + // Inspired by https://github.com/fogleman/nes/blob/master/nes/ppu.go + + auto const enable_rendering = mask_.get_enable_background() || mask_.get_enable_sprites(); + + current_cycles_ += cycle_count::from_ppu(1); + scanline_cycle_ += 1; + if (enable_rendering && !even_frame_ && scanline_ == 261 && scanline_cycle_ == 340) + { + current_cycles_ += cycle_count::from_ppu(1); + scanline_cycle_ += 1; + } + if (scanline_cycle_ == 341) + { + scanline_cycle_ = 0; + scanline_ += 1; + + if (scanline_ == 262) + { + scanline_ = 0; + even_frame_ = !even_frame_; + } + } + + auto const pre_line = scanline_ == 261; + auto const visible_line = scanline_ < 240; + auto const render_line = pre_line || visible_line; + auto const pre_fetch_cycle = scanline_cycle_ >= 321 && scanline_cycle_ <= 336; + auto const visible_cycle = scanline_cycle_ >= 1 && scanline_cycle_ <= 256; + auto const fetch_cycle = pre_fetch_cycle || visible_cycle; + + // Background Logic + if (enable_rendering) + { + if (visible_line && visible_cycle) + { + render_pixel(); + } + + if (render_line && fetch_cycle) + { + // Fetch cycle for the background. + switch (scanline_cycle_ % 8) + { + case 1: + { + // Load the tile pattern for the background from the name table. + fetch_cycle_.tile = tile{ read8(address{ 0x2000 } + internal_.v.get_tile_address()) }; + break; + } + case 3: + { + // Load the tile palette for the background from the attribute table. + auto const addr = + address{ 0x23C0 } + + (static_cast(internal_.v.get_name_table()) * 0x400u) + + ((internal_.v.get_coarse_y() & 0b11100u) << 1) + + ((internal_.v.get_coarse_x() & 0b11100u) >> 2); + auto const shift = + ((internal_.v.get_coarse_y() & 0b00010u) << 1) | + ((internal_.v.get_coarse_x() & 0b00010u) << 0); + fetch_cycle_.palette = static_cast((read8(addr) >> shift) & 0b11); + break; + } + case 5: + { + // Load bitplane 0 for the background tile's pattern. + fetch_cycle_.bitplane_0 = get_tile_bitplane( + control_.get_background_pattern_table(), + fetch_cycle_.tile, + internal_.v.get_fine_y(), + bitplane::_0); + break; + } + case 7: + { + // Load bitplane 1 for the background tile's pattern. + fetch_cycle_.bitplane_1 = get_tile_bitplane( + control_.get_background_pattern_table(), + fetch_cycle_.tile, + internal_.v.get_fine_y(), + bitplane::_1); + break; + } + case 0: + { + // Fetch cycle done -> build the tile row for the background. + current_background_ = next_background_; + next_background_ = get_tile_row( + fetch_cycle_.palette, fetch_cycle_.bitplane_0, fetch_cycle_.bitplane_1); + break; + } + default: + break; + } + } + + if (pre_line && scanline_cycle_ >= 280 && scanline_cycle_ <= 304) + { + copy_y(); + } + + if (render_line) + { + if (fetch_cycle && scanline_cycle_ % 8 == 0) + { + increment_x(); + } + if (scanline_cycle_ == 256) + { + increment_y(); + } + if (scanline_cycle_ == 257) + { + copy_x(); + } + } + } + + // Sprite Logic + if (enable_rendering) + { + if (scanline_cycle_ == 257) + { + if (visible_line) + { + evaluate_sprites(); + } + else + { + sprite_count_ = 0; + } + } + } + + // Vblank Logic + if (scanline_ == 241 && scanline_cycle_ == 1) + { + display_.switch_buffers(); + status_.set_vblank(true); + if (control_.get_vblank_nmi()) { cpu_.trigger_nmi(); } + } + if (pre_line && scanline_cycle_ == 1) + { + status_.set_vblank(false); + status_.set_sprite_zero_hit(false); + status_.set_sprite_overflow(false); + } + } + + auto ppu::render_pixel() -> void + { + auto const x = scanline_cycle_ - 1; + auto const y = scanline_; + + auto background_color = color_index{ 0 }; + if (mask_.get_enable_background()) + { + background_color = current_background_.colors[internal_.x + x % tile_size]; + background_color.set_role(role::background); + } + + auto foreground = evaluated_sprite{}; + auto foreground_color = color_index{ 0 }; + if (mask_.get_enable_sprites()) + { + for (auto i = unsigned{ 0 }; i < sprite_count_; ++i) + { + auto const s = sprites_[i]; + auto const offset = static_cast(x) - static_cast(s.x); + if (offset < 0 || static_cast(offset) >= tile_size) { continue; } + auto const color = s.pattern.colors[offset]; + if (color.get_color() == palette_color::_0) { continue; } + + foreground = s; + foreground_color = color; + foreground_color.set_role(role::foreground); + break; + } + } + + auto has_background = background_color.get_color() != palette_color::_0; + auto has_foreground = foreground_color.get_color() != palette_color::_0; + + if (x < tile_size && !mask_.get_show_background_start()) { has_background = false; } + if (x < tile_size && !mask_.get_show_sprites_start()) { has_foreground = false; } + + auto color = color_index{ 0 }; + if (!has_background && has_foreground) + { + color = foreground_color; + } + if (has_background && !has_foreground) + { + color = background_color; + } + if (has_background && has_foreground) + { + if (foreground.is_sprite_zero && x < 255) + { + status_.set_sprite_zero_hit(true); + } + + color = foreground.is_in_front ? foreground_color : background_color; + } + + display_.set(x, y, resolve_color(ref_color(color))); + } + + auto ppu::evaluate_sprites() -> void + { + auto const height = get_sprite_height(); + sprite_count_ = 0; + for (auto i = unsigned{ 0 }; i < sprite_max_count; ++i) + { + auto const s = sprite{ &oam_[i * 4] }; + auto const row = static_cast(scanline_) - static_cast(s.get_y()); + if (row < 0 || static_cast(row) >= height) { continue; } + + if (sprite_count_ < 8) + { + sprites_[sprite_count_].pattern = fetch_sprite_pattern(s, static_cast(row)); + sprites_[sprite_count_].x = s.get_x(); + sprites_[sprite_count_].is_in_front = !s.get_behind_background(); + sprites_[sprite_count_].is_sprite_zero = i == 0; + sprite_count_ += 1; + } + else + { + status_.set_sprite_overflow(true); + break; + } + } + } + + auto ppu::fetch_sprite_pattern(sprite const s, unsigned row) -> tile_row + { + if (s.get_flip_vertical()) { row = get_sprite_height() - 1 - row; } + tile tile; + pattern_table pattern_table; + switch (control_.get_sprite_size()) + { + case sprite_size::single_height: + tile = s.get_small_tile(); + pattern_table = control_.get_sprite_pattern_table(); + break; + case sprite_size::double_height: + tile = s.get_large_top_tile(); + if (row >= tile_size) + { + tile = static_cast(1); + row -= tile_size; + } + pattern_table = s.get_large_pattern_table(); + break; + default: + return tile_row{}; + } + + auto const bitplane_0 = get_tile_bitplane(pattern_table, tile, row, bitplane::_0); + auto const bitplane_1 = get_tile_bitplane(pattern_table, tile, row, bitplane::_1); + auto res = get_tile_row(s.get_palette(), bitplane_0, bitplane_1); + + if (s.get_flip_horizontal()) + { + for (auto i = unsigned{ 0 }; i < tile_size / 2; ++i) + { + std::swap(res.colors[i], res.colors[tile_size - 1 - i]); + } + } + + return res; + } + + // ----------------------------------------------------------------------------------------------------------------- + // Memory Access + // ----------------------------------------------------------------------------------------------------------------- + + // + // See: https://www.nesdev.org/wiki/PPU_memory_map + // + + auto ppu::read8(address addr) -> std::uint8_t + { + addr = addr % 0x4000; // PPU only has 16 KiB addresses. + if (addr <= address{ 0x3EFF }) { return cartridge_.get_mapper().read_ppu(addr, cartridge_, vram_); } + if (addr <= address{ 0x3FFF }) + { + auto const index = color_index{ static_cast(addr.get_absolute() % 0x20) }; + auto const color = ref_color(index); + return static_cast(color); + } + + return 0x0; + } + + auto ppu::write8(address addr, std::uint8_t const value) -> void + { + addr = addr % 0x4000; // PPU only has 16 KiB addresses. + if (addr <= address{ 0x3EFF }) + { + cartridge_.get_mapper().write_ppu(addr, value, cartridge_, vram_); + return; + } + if (addr <= address{ 0x3FFF }) + { + auto const index = color_index{ static_cast(addr.get_absolute() % 0x20) }; + ref_color(index) = color{ value }; + return; + } + } + + // ----------------------------------------------------------------------------------------------------------------- + // IO Registers + // ----------------------------------------------------------------------------------------------------------------- + + // + // See: https://www.nesdev.org/wiki/PPU_registers + // + + auto ppu::read_latch() -> std::uint8_t + { + return latch_; + } + + auto ppu::read_ppustatus() -> std::uint8_t + { + auto res = status_; + res.set_remaining(latch_); + NES_DEBUG_LOG(ppu, "PPUSTATUS -> {:#2x}", res.value); + + status_.set_vblank(false); + internal_.w = false; + + write_latch(res.value); + return res.value; + } + + auto ppu::read_oamdata() -> std::uint8_t + { + auto res = oam_[oamaddr_]; + if ((oamaddr_ & 0x3) == 0x2) + { + res &= 0xE3; + } + NES_DEBUG_LOG(ppu, "OAMDATA -> {:#2x}", res); + write_latch(res); + return res; + } + + auto ppu::read_ppudata() -> std::uint8_t + { + auto const addr = address{ internal_.v.value }; + auto res = read8(addr); + write_latch(ppudata_read_buffer_); + + if (addr % 0x4000 <= address{ 0x3EFF }) + { + std::swap(res, ppudata_read_buffer_); + } + else + { + // For palette buffers: buffer the mirrored nametable instead. + ppudata_read_buffer_ = read8(addr - 0x1000); + } + + NES_DEBUG_LOG(ppu, "PPUDATA -> {:#2x} (address: {:#4x})", res, internal_.v); + increment_vram(); + return res; + } + + auto ppu::write_latch(std::uint8_t const value) -> void + { + latch_ = value; + } + + auto ppu::write_ppuctrl(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + NES_DEBUG_LOG(ppu, "PPUCTRL <- {:#2x}", value); + control_.value = value; + internal_.t.set_name_table(control_.get_base_name_table()); + if (control_.get_vblank_nmi() && status_.get_vblank()) { cpu_.trigger_nmi(); } + } + } + + auto ppu::write_ppuscroll(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + NES_DEBUG_LOG(ppu, "PPUSCROLL <- {:#2x}", value); + if (!internal_.w) + { + // First write -> x value. + internal_.t.set_coarse_x((value & 0b11111000) >> 3); + internal_.x = (value & 0b00000111) >> 0; + } + else + { + // Second write -> y value. + internal_.t.set_coarse_y((value & 0b11111000) >> 3); + internal_.t.set_fine_y((value & 0b00000111) >> 0); + } + + internal_.w = !internal_.w; + } + } + + auto ppu::write_ppumask(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + NES_DEBUG_LOG(ppu, "PPUMASK <- {:#2x}", value); + mask_.value = value; + } + } + + auto ppu::write_ppuaddr(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + NES_DEBUG_LOG(ppu, "PPUADDR <- {:#2x}", value); + if (!internal_.w) + { + // First write. + internal_.t.set_address_high(value & 0b00111111); + } + else + { + // Second write. + internal_.t.set_address_low(value); + internal_.v = internal_.t; + } + + internal_.w = !internal_.w; + } + } + + auto ppu::write_ppudata(std::uint8_t const value) -> void + { + NES_DEBUG_LOG(ppu, "PPUDATA <- {:#2x} (address: {:#4x})", value, internal_.v); + write_latch(value); + write8(address{ internal_.v.value }, value); + increment_vram(); + } + + auto ppu::write_oamaddr(std::uint8_t const value) -> void + { + NES_DEBUG_LOG(ppu, "OAMADDR <- {:#2x}", value); + write_latch(value); + oamaddr_ = value; + } + + auto ppu::write_oamdata(std::uint8_t const value) -> void + { + NES_DEBUG_LOG(ppu, "OAMDATA <- {:#2x}", value); + write_latch(value); + oam_[oamaddr_] = value; + oamaddr_ += 1; + } + + auto ppu::write_oamdma(std::uint8_t const value) -> void + { + NES_DEBUG_LOG(ppu, "OAMDMA <- {:#2x}", value); + auto addr = address{ value, 0x00 }; + for (auto i = unsigned{ 0 }; i < 256; ++i) + { + oam_[oamaddr_] = cpu_.read8(addr); + oamaddr_ += 1; + addr = addr + 1; + } + + auto const stalled_cycles = cycle_count::from_cpu((cpu_.get_cycles().to_cpu() % 2 == 0) ? 513 : 514); + cpu_.stall_cycles(stalled_cycles); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------------------------------------------------- + + auto ppu::increment_vram() -> void + { + switch (control_.get_vram_increment()) + { + case vram_increment::forward: + internal_.v.value += 1; + break; + case vram_increment::downward: + internal_.v.value += 32; + break; + } + } + + auto ppu::increment_x() -> void + { + if (internal_.v.get_coarse_x() == 31) + { + internal_.v.set_coarse_x(0); + internal_.v.set_horizontal_name_table(1 ^ internal_.v.get_horizontal_name_table()); + } + else + { + internal_.v.set_coarse_x(internal_.v.get_coarse_x() + 1); + } + } + + auto ppu::increment_y() -> void + { + if (internal_.v.get_fine_y() == 7) + { + internal_.v.set_fine_y(0); + if (internal_.v.get_coarse_y() == 29) + { + internal_.v.set_coarse_y(0); + internal_.v.set_vertical_name_table(1 ^ internal_.v.get_vertical_name_table()); + } + else if (internal_.v.get_coarse_y() == 31) + { + internal_.v.set_coarse_y(0); + // Nametable not switched + } + else + { + internal_.v.set_coarse_y(internal_.v.get_coarse_y() + 1); + } + } + else + { + internal_.v.set_fine_y(internal_.v.get_fine_y() + 1); + } + } + + auto ppu::copy_x() -> void + { + internal_.v.set_coarse_x(internal_.t.get_coarse_x()); + internal_.v.set_horizontal_name_table(internal_.t.get_horizontal_name_table()); + } + + auto ppu::copy_y() -> void + { + internal_.v.set_coarse_y(internal_.t.get_coarse_y()); + internal_.v.set_fine_y(internal_.t.get_fine_y()); + internal_.v.set_vertical_name_table(internal_.t.get_vertical_name_table()); + } + + auto ppu::get_sprite_height() const -> unsigned + { + switch (control_.get_sprite_size()) + { + case sprite_size::single_height: + return tile_size; + case sprite_size::double_height: + return tile_size * 2; + } + + return tile_size; + } + + auto ppu::get_tile_bitplane( + pattern_table const pattern_table, tile const tile, unsigned const row, bitplane const bitplane) -> std::uint8_t + { + auto const addr = static_cast( + 0x1000 * static_cast(pattern_table) + 0x10 * static_cast(tile) + row); + return read8(address{ addr } + (static_cast(bitplane) * 8u)); + } + + auto ppu::get_tile_row(palette const palette, std::uint8_t bitplane_0, std::uint8_t bitplane_1) const -> tile_row + { + auto res = tile_row{}; + for (auto i = unsigned{ 0 }; i < tile_size; ++i) + { + auto const bit_0 = (bitplane_0 & 0b10000000u) >> 7; + auto const bit_1 = (bitplane_1 & 0b10000000u) >> 7; + bitplane_0 <<= 1; + bitplane_1 <<= 1; + res.colors[i].set_palette(palette); + res.colors[i].set_color(palette_color{ (bit_1 << 1) | (bit_0 << 0) }); + } + return res; + } + + auto ppu::ref_color(color_index index) -> color& + { + // See https://www.nesdev.org/wiki/PPU_palettes + index.value = index.value % 0x20; + if (index.get_color() == palette_color::_0) + { + // The first color is mirrored between background and foreground palettes. + index.set_role(role::background); + } + return palette_buffer_[index.value]; + } + + auto ppu::resolve_color(color const color) const -> rgb + { + auto const index = static_cast(color); + switch (index & 0x3F) + { + case 0x00: return rgb::from_hex(0x666666); + case 0x01: return rgb::from_hex(0x002A88); + case 0x02: return rgb::from_hex(0x1412A7); + case 0x03: return rgb::from_hex(0x3B00A4); + case 0x04: return rgb::from_hex(0x5C007E); + case 0x05: return rgb::from_hex(0x6E0040); + case 0x06: return rgb::from_hex(0x6C0600); + case 0x07: return rgb::from_hex(0x561D00); + case 0x08: return rgb::from_hex(0x333500); + case 0x09: return rgb::from_hex(0x0B4800); + case 0x0A: return rgb::from_hex(0x005200); + case 0x0B: return rgb::from_hex(0x004F08); + case 0x0C: return rgb::from_hex(0x00404D); + case 0x0D: return rgb::from_hex(0x000000); + case 0x0E: return rgb::from_hex(0x000000); + case 0x0F: return rgb::from_hex(0x000000); + case 0x10: return rgb::from_hex(0xADADAD); + case 0x11: return rgb::from_hex(0x155FD9); + case 0x12: return rgb::from_hex(0x4240FF); + case 0x13: return rgb::from_hex(0x7527FE); + case 0x14: return rgb::from_hex(0xA01ACC); + case 0x15: return rgb::from_hex(0xB71E7B); + case 0x16: return rgb::from_hex(0xB53120); + case 0x17: return rgb::from_hex(0x994E00); + case 0x18: return rgb::from_hex(0x6B6D00); + case 0x19: return rgb::from_hex(0x388700); + case 0x1A: return rgb::from_hex(0x0C9300); + case 0x1B: return rgb::from_hex(0x008F32); + case 0x1C: return rgb::from_hex(0x007C8D); + case 0x1D: return rgb::from_hex(0x000000); + case 0x1E: return rgb::from_hex(0x000000); + case 0x1F: return rgb::from_hex(0x000000); + case 0x20: return rgb::from_hex(0xFFFEFF); + case 0x21: return rgb::from_hex(0x64B0FF); + case 0x22: return rgb::from_hex(0x9290FF); + case 0x23: return rgb::from_hex(0xC676FF); + case 0x24: return rgb::from_hex(0xF36AFF); + case 0x25: return rgb::from_hex(0xFE6ECC); + case 0x26: return rgb::from_hex(0xFE8170); + case 0x27: return rgb::from_hex(0xEA9E22); + case 0x28: return rgb::from_hex(0xBCBE00); + case 0x29: return rgb::from_hex(0x88D800); + case 0x2A: return rgb::from_hex(0x5CE430); + case 0x2B: return rgb::from_hex(0x45E082); + case 0x2C: return rgb::from_hex(0x48CDDE); + case 0x2D: return rgb::from_hex(0x4F4F4F); + case 0x2E: return rgb::from_hex(0x000000); + case 0x2F: return rgb::from_hex(0x000000); + case 0x30: return rgb::from_hex(0xFFFEFF); + case 0x31: return rgb::from_hex(0xC0DFFF); + case 0x32: return rgb::from_hex(0xD3D2FF); + case 0x33: return rgb::from_hex(0xE8C8FF); + case 0x34: return rgb::from_hex(0xFBC2FF); + case 0x35: return rgb::from_hex(0xFEC4EA); + case 0x36: return rgb::from_hex(0xFECCC5); + case 0x37: return rgb::from_hex(0xF7D8A5); + case 0x38: return rgb::from_hex(0xE4E594); + case 0x39: return rgb::from_hex(0xCFEF96); + case 0x3A: return rgb::from_hex(0xBDF4AB); + case 0x3B: return rgb::from_hex(0xB3F3CC); + case 0x3C: return rgb::from_hex(0xB5EBF2); + case 0x3D: return rgb::from_hex(0xB8B8B8); + case 0x3E: return rgb::from_hex(0x000000); + case 0x3F: return rgb::from_hex(0x000000); + default: return rgb::from_hex(0x000000); + } + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/ppu.hh b/lib/nes/sys/ppu.hh new file mode 100644 index 0000000..b1b8842 --- /dev/null +++ b/lib/nes/sys/ppu.hh @@ -0,0 +1,279 @@ +#pragma once + +#include "nes/sys/types/cycle-count.hh" + +namespace nes +{ + class address; + class cpu; + class cartridge; + class display; + class rgb; + struct snapshot; + + class ppu + { + static constexpr auto sprite_max_count = unsigned{ 64 }; + static constexpr auto vram_size = std::size_t{ 0x800 }; + static constexpr auto oam_size = std::size_t{ sprite_max_count * 4 }; + static constexpr auto palette_buffer_size = std::size_t{ 0x20 }; + static constexpr auto tile_size = unsigned{ 8 }; + // Writes to some registers are ignored until this clock cycle. + static constexpr auto boot_up_cycles = cycle_count::from_ppu(29658); + + enum class bitplane : unsigned { _0, _1 }; + enum class palette : unsigned { _0, _1, _2, _3 }; + enum class palette_color : unsigned { _0, _1, _2, _3 }; + enum class name_table : unsigned { _0, _1, _2, _3 }; + enum class role : unsigned { background, foreground }; + enum class pattern_table : unsigned { _0, _1 }; + enum class vram_increment : unsigned { forward, downward }; + enum class sprite_size : unsigned { single_height, double_height }; + enum class tile : std::uint8_t {}; + enum class color : std::uint8_t {}; + +#define BITFIELD_VALIDATE(mask, offset_from_right) \ + static_assert((mask & (1 << offset_from_right)) != 0); \ + static_assert(offset_from_right == 0 || (mask & (1 << (offset_from_right - 1))) == 0); +#define BITFIELD_VALUE(name, base, mask, offset_from_right) \ + BITFIELD_VALIDATE(mask, offset_from_right) \ + auto get_##name() const -> unsigned { return (base & mask) >> offset_from_right; } \ + auto set_##name(unsigned const v) -> void { base = (base & ~mask) | ((v << offset_from_right) & mask); } +#define BITFIELD_ENUM(name, type, base, mask, offset_from_right) \ + BITFIELD_VALIDATE(mask, offset_from_right) \ + auto get_##name() const -> type { return static_cast((base & mask) >> offset_from_right); } \ + auto set_##name(type const v) -> void { base = (base & ~mask) | ((static_cast(v) << offset_from_right) & mask); } +#define BITFIELD_FLAG(name, base, mask, offset_from_right) \ + BITFIELD_VALIDATE(mask, offset_from_right) \ + auto get_##name() const -> bool { return base & mask; } \ + auto set_##name(unsigned const v) -> void { base = (base & ~mask) | (v ? mask : 0); } + + struct color_index + { + BITFIELD_ENUM(color, palette_color, value, 0b00000011, 0) + BITFIELD_ENUM(palette, palette, value, 0b00001100, 2) + BITFIELD_ENUM(role, role, value, 0b00010000, 4) + + std::uint8_t value{}; + + color_index() = default; + + explicit color_index(std::uint8_t const value) + : value{ value } + { + } + }; + + struct sprite + { + // Tile index (and pattern table) for sprites + BITFIELD_ENUM(large_pattern_table, pattern_table, value[1], 0b00000001, 0) + BITFIELD_ENUM(large_top_tile, tile, value[1], 0b11111110, 1) + BITFIELD_ENUM(small_tile, tile, value[1], 0b11111111, 0) + + // Attributes + BITFIELD_ENUM(palette, palette, value[2], 0b00000011, 0) + BITFIELD_FLAG(behind_background, value[2], 0b00100000, 5) + BITFIELD_FLAG(flip_horizontal, value[2], 0b01000000, 6) + BITFIELD_FLAG(flip_vertical, value[2], 0b10000000, 7) + + auto get_y() const -> std::uint8_t { return value[0]; } + auto get_tile_index() const -> std::uint8_t { return value[1]; } + auto get_attributes() const -> std::uint8_t { return value[2]; } + auto get_x() const -> std::uint8_t { return value[3]; } + + auto set_y(std::uint8_t const v) -> void { value[0] = v; } + auto set_tile_index(std::uint8_t const v) -> void { value[1] = v; } + auto set_attributes(std::uint8_t const v) -> void { value[2] = v; } + auto set_x(std::uint8_t const v) -> void { value[3] = v; } + + std::uint8_t value[4]{}; + + explicit sprite() = default; + + explicit sprite(std::uint8_t const* data) + : value{ data[0], data[1], data[2], data[3] } + { + } + }; + + struct tile_row + { + color_index colors[tile_size]{}; + }; + + struct evaluated_sprite + { + tile_row pattern; + std::uint8_t x{ 0 }; + bool is_in_front{ false }; + bool is_sprite_zero{ false }; + }; + + cycle_count current_cycles_; + cpu& cpu_; + cartridge& cartridge_; + display& display_; + std::uint8_t vram_[vram_size]{}; + std::uint8_t oam_[oam_size]{}; + color palette_buffer_[palette_buffer_size] + { + color{ 0x09 }, color{ 0x01 }, color{ 0x00 }, color{ 0x01 }, + color{ 0x00 }, color{ 0x02 }, color{ 0x02 }, color{ 0x0D }, + color{ 0x08 }, color{ 0x10 }, color{ 0x08 }, color{ 0x24 }, + color{ 0x00 }, color{ 0x00 }, color{ 0x04 }, color{ 0x2C }, + color{ 0x09 }, color{ 0x01 }, color{ 0x34 }, color{ 0x03 }, + color{ 0x00 }, color{ 0x04 }, color{ 0x00 }, color{ 0x14 }, + color{ 0x08 }, color{ 0x3A }, color{ 0x00 }, color{ 0x02 }, + color{ 0x00 }, color{ 0x20 }, color{ 0x2C }, color{ 0x08 }, + }; + std::uint8_t latch_{}; // The last read/written IO register value, returned when reading write-only registers. + struct + { + BITFIELD_FLAG(sprite_overflow, value, 0b00100000, 5) + BITFIELD_FLAG(sprite_zero_hit, value, 0b01000000, 6) + BITFIELD_FLAG(vblank, value, 0b10000000, 7) + + // Set the remaining (unused) bits based on the given value. + auto set_remaining(std::uint8_t const v) -> void { value = (value & ~0b00011111) | (v & 0b00011111); } + + std::uint8_t value{ 0b00000000 }; + } status_{}; + struct + { + // Base nametable address. + BITFIELD_ENUM(base_name_table, name_table, value, 0b00000011, 0) + // How VRAM addresses should be incremented each read/write. + BITFIELD_ENUM(vram_increment, vram_increment, value, 0b00000100, 2) + // Sprite pattern table address for 8x8 sprites. + BITFIELD_ENUM(sprite_pattern_table, pattern_table, value, 0b00001000, 3) + // Background pattern table address. + BITFIELD_ENUM(background_pattern_table, pattern_table, value, 0b00010000, 4) + // The size of the sprites used (i.e. single-height or double-height). + BITFIELD_ENUM(sprite_size, sprite_size, value, 0b00100000, 5) + // PPU master/slave select. + BITFIELD_FLAG(enable_ext_pin, value, 0b01000000, 6) + // Enable/disable vblank NMI. + BITFIELD_FLAG(vblank_nmi, value, 0b10000000, 7) + + std::uint8_t value{ 0b00000000 }; + } control_{}; + struct + { + // Enable/disable grayscale. + BITFIELD_FLAG(grayscale, value, 0b00000001, 0) + // Show background in leftmost 8 pixels of screen. + BITFIELD_FLAG(show_background_start, value, 0b00000010, 1) + // Show sprites in leftmost 8 pixels of screen. + BITFIELD_FLAG(show_sprites_start, value, 0b00000100, 2) + // Enable background rendering. + BITFIELD_FLAG(enable_background, value, 0b00001000, 3) + // Enable sprite rendering. + BITFIELD_FLAG(enable_sprites, value, 0b00010000, 4) + // Emphasize red. + BITFIELD_FLAG(emphasize_red, value, 0b00100000, 5) + // Emphasize green. + BITFIELD_FLAG(emphasize_green, value, 0b01000000, 6) + // Emphasize blue. + BITFIELD_FLAG(emphasize_blue, value, 0b10000000, 7) + + std::uint8_t value{ 0b00000000 }; + } mask_{}; + struct + { + // See https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers + struct + { + // Scroll position + BITFIELD_VALUE(coarse_x, value, 0b0000000000011111, 0) + BITFIELD_VALUE(coarse_y, value, 0b0000001111100000, 5) + BITFIELD_ENUM(name_table, name_table, value, 0b0000110000000000, 10) + BITFIELD_VALUE(horizontal_name_table, value, 0b0000010000000000, 10) + BITFIELD_VALUE(vertical_name_table, value, 0b0000100000000000, 11) + BITFIELD_VALUE(tile_address, value, 0b0000111111111111, 0) + BITFIELD_VALUE(fine_y, value, 0b0111000000000000, 12) + + // Address + BITFIELD_VALUE(address_low, value, 0b0000000011111111, 0) + BITFIELD_VALUE(address_high, value, 0b0111111100000000, 8) + + std::uint16_t value{}; + } v{}, t{}; // Current and temporary VRAM address and scroll position (15 bits) + std::uint8_t x{}; // Fine X scroll (3 bits) + bool w{}; // Second write toggle (1 bit) + } internal_{}; + std::uint8_t oamaddr_{}; // Address accessed by IO registers + std::uint8_t ppudata_read_buffer_{}; // Delays PPUDATA reads by one. + unsigned scanline_{ 240 }; + unsigned scanline_cycle_{ 340 }; + bool even_frame_{ true }; + tile_row current_background_{}; + tile_row next_background_{}; + struct + { + tile tile{ 0 }; + palette palette{ 0 }; + std::uint8_t bitplane_0{ 0 }; + std::uint8_t bitplane_1{ 0 }; + } fetch_cycle_{}; // Data populated during the fetch cycle. + evaluated_sprite sprites_[8]{}; // Evaluated sprites. + unsigned sprite_count_{ 0 }; // Number of evaluated sprites in sprites_. + +#undef BITFIELD_VALIDATE +#undef BITFIELD_VALUE +#undef BITFIELD_ENUM +#undef BITFIELD_FLAG + + public: + explicit ppu(cpu&, cartridge&, display&); + + ppu(ppu const&) = delete; + ppu(ppu&&) = delete; + auto operator=(ppu const&) -> ppu& = delete; + auto operator=(ppu&&) -> ppu& = delete; + + auto get_cycles() const -> cycle_count { return current_cycles_; } + auto build_snapshot(snapshot&) -> void; + auto step() -> void; + + // Memory access + + auto read8(address) -> std::uint8_t; + auto write8(address, std::uint8_t) -> void; + + // IO registers + + auto read_latch() -> std::uint8_t; + auto read_ppustatus() -> std::uint8_t; + auto read_oamdata() -> std::uint8_t; + auto read_ppudata() -> std::uint8_t; + + auto write_latch(std::uint8_t) -> void; + auto write_ppuctrl(std::uint8_t) -> void; + auto write_ppuscroll(std::uint8_t) -> void; + auto write_ppumask(std::uint8_t) -> void; + auto write_ppuaddr(std::uint8_t) -> void; + auto write_ppudata(std::uint8_t) -> void; + auto write_oamaddr(std::uint8_t) -> void; + auto write_oamdata(std::uint8_t) -> void; + auto write_oamdma(std::uint8_t) -> void; + + private: + auto render_pixel() -> void; + auto evaluate_sprites() -> void; + auto fetch_sprite_pattern(sprite, unsigned row) -> tile_row; + + // Helpers + + auto increment_vram() -> void; + auto increment_x() -> void; + auto increment_y() -> void; + auto copy_x() -> void; + auto copy_y() -> void; + auto get_sprite_height() const -> unsigned; + auto get_tile_bitplane(pattern_table, tile, unsigned row, bitplane) -> std::uint8_t; + auto get_tile_row(palette, std::uint8_t bitplane_0, std::uint8_t bitplane_1) const -> tile_row; + auto ref_color(color_index) -> color&; + auto resolve_color(color) const -> rgb; + }; +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/types/CMakeLists.txt b/lib/nes/sys/types/CMakeLists.txt new file mode 100644 index 0000000..f51b0ae --- /dev/null +++ b/lib/nes/sys/types/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources( + nes + PRIVATE + address.hh + button-mask.hh + cycle-count.hh + snapshot.hh + status.hh) \ No newline at end of file diff --git a/lib/nes/sys/types/address.hh b/lib/nes/sys/types/address.hh new file mode 100644 index 0000000..c2e1352 --- /dev/null +++ b/lib/nes/sys/types/address.hh @@ -0,0 +1,74 @@ +#pragma once + +#include + +namespace nes +{ + /// An address on the NES (for simplicity, we share the same type for CPU and PPU, even though the PPU only uses + /// 14 bit addresses). + class address + { + std::uint16_t absolute_{ 0 }; + + public: + /// Create a zero address. + constexpr explicit address() = default; + + /// Create an address from its absolute value. + constexpr explicit address(std::uint16_t const absolute) + : absolute_{ absolute } + { + } + + /// Create an address from its page and offset combination. + constexpr explicit address(std::uint8_t const page, std::uint8_t const offset) + : address{ static_cast((page << 8) | (offset << 0)) } + { + } + + /// Get the absolute address value. + constexpr auto get_absolute() const -> std::uint16_t + { + return absolute_; + } + + /// Get the address page (the first 8 bits). + constexpr auto get_page() const -> std::uint8_t + { + return static_cast((get_absolute() & 0xFF00) >> 8); + } + + /// Get the offset within the page (the last 8 bits). + constexpr auto get_offset() const -> std::uint8_t + { + return static_cast((get_absolute() & 0x00FF) >> 0); + } + }; + + constexpr auto operator==(address const a, address const b) -> bool { return a.get_absolute() == b.get_absolute(); } + constexpr auto operator!=(address const a, address const b) -> bool { return a.get_absolute() != b.get_absolute(); } + constexpr auto operator<(address const a, address const b) -> bool { return a.get_absolute() < b.get_absolute(); } + constexpr auto operator>(address const a, address const b) -> bool { return a.get_absolute() > b.get_absolute(); } + constexpr auto operator<=(address const a, address const b) -> bool { return a.get_absolute() <= b.get_absolute(); } + constexpr auto operator>=(address const a, address const b) -> bool { return a.get_absolute() >= b.get_absolute(); } + + constexpr auto operator+(address const a, unsigned const b) -> address + { + return address{ static_cast(a.get_absolute() + b) }; + } + + constexpr auto operator+(unsigned const a, address const b) -> address + { + return address{ static_cast(a + b.get_absolute()) }; + } + + constexpr auto operator-(address const a, unsigned const b) -> address + { + return address{ static_cast(a.get_absolute() - b) }; + } + + constexpr auto operator%(address const a, unsigned const b) -> address + { + return address{ static_cast(a.get_absolute() % b) }; + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/types/button-mask.hh b/lib/nes/sys/types/button-mask.hh new file mode 100644 index 0000000..16b0dad --- /dev/null +++ b/lib/nes/sys/types/button-mask.hh @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace nes +{ + /// Buttons pressed on a controller. + class button_mask + { + std::uint8_t raw_value_{ 0 }; + + public: + constexpr explicit button_mask() = default; + + static constexpr auto from_raw_value(std::uint8_t const raw_value) -> button_mask + { + return button_mask{ raw_value }; + } + + constexpr auto add(button_mask const other) -> void { raw_value_ |= other.raw_value_; } + constexpr auto remove(button_mask const other) -> void { raw_value_ ^= other.raw_value_; } + constexpr auto contains(button_mask const other) const -> bool { return (raw_value_ & other.raw_value_) == other.raw_value_; } + constexpr auto is_empty() const -> bool { return raw_value_ == 0; } + constexpr auto get_raw_value() const -> std::uint8_t { return raw_value_; } + + private: + constexpr explicit button_mask(std::uint8_t const raw_value) + : raw_value_{ raw_value } + { + } + }; + + namespace buttons + { + static constexpr auto a = button_mask::from_raw_value(1 << 0); + static constexpr auto b = button_mask::from_raw_value(1 << 1); + static constexpr auto select = button_mask::from_raw_value(1 << 2); + static constexpr auto start = button_mask::from_raw_value(1 << 3); + static constexpr auto up = button_mask::from_raw_value(1 << 4); + static constexpr auto down = button_mask::from_raw_value(1 << 5); + static constexpr auto left = button_mask::from_raw_value(1 << 6); + static constexpr auto right = button_mask::from_raw_value(1 << 7); + } // namespace buttons +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/types/cycle-count.hh b/lib/nes/sys/types/cycle-count.hh new file mode 100644 index 0000000..f1a4a7e --- /dev/null +++ b/lib/nes/sys/types/cycle-count.hh @@ -0,0 +1,87 @@ +#pragma once + +#include + +namespace nes +{ + /// A cycle count that can be used for multiple components. + /// + /// Each component can have a different clock frequency and this structure offers conversions between different + /// cycle counts. + class cycle_count + { + friend constexpr auto operator+(cycle_count, cycle_count) -> cycle_count; + friend constexpr auto operator-(cycle_count, cycle_count) -> cycle_count; + friend constexpr auto operator*(std::uint64_t, cycle_count) -> cycle_count; + friend constexpr auto operator*(cycle_count, std::uint64_t) -> cycle_count; + friend constexpr auto operator/(cycle_count, cycle_count) -> std::uint64_t; + + std::uint64_t units_{ 0 }; // The most fine-granular cycle count (here: PPU cycles). + + static constexpr auto ppu_cycle() -> cycle_count { return cycle_count{ 1 }; } + static constexpr auto cpu_cycle() -> cycle_count { return cycle_count{ 3 }; } + static constexpr auto second() -> cycle_count { return 1789773 * cpu_cycle(); } + + public: + constexpr explicit cycle_count() = default; + + constexpr auto operator+=(cycle_count const other) -> cycle_count& { return *this = *this + other; } + constexpr auto operator*=(std::uint64_t const other) -> cycle_count& { return *this = *this * other; } + + static constexpr auto from_units(std::uint64_t const units) -> cycle_count + { + return cycle_count{ units }; + } + + static constexpr auto from_duration(std::chrono::microseconds const duration) -> cycle_count + { + return from_units(static_cast(duration.count()) * second().get_units() / (1000 * 1000)); + } + + static constexpr auto from_cpu(std::uint64_t const cpu_cycles) -> cycle_count + { + return cpu_cycles * cpu_cycle(); + } + + static constexpr auto from_ppu(std::uint64_t const ppu_cycles) -> cycle_count + { + return ppu_cycles * ppu_cycle(); + } + + /// Value in implementation-defined units. + constexpr auto get_units() const -> std::uint64_t { return units_; } + + constexpr auto to_duration() const -> std::chrono::microseconds + { + return std::chrono::microseconds{ get_units() * 1000 * 1000 / second().get_units() }; + } + + constexpr auto to_cpu() const -> std::uint64_t + { + return *this / cpu_cycle(); + } + + constexpr auto to_ppu() const -> std::uint64_t + { + return *this / ppu_cycle(); + } + + private: + constexpr explicit cycle_count(std::uint64_t const units) + : units_{ units } + { + } + }; + + constexpr auto operator==(cycle_count const a, cycle_count const b) -> bool { return a.get_units() == b.get_units(); } + constexpr auto operator!=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() != b.get_units(); } + constexpr auto operator<(cycle_count const a, cycle_count const b) -> bool { return a.get_units() < b.get_units(); } + constexpr auto operator>(cycle_count const a, cycle_count const b) -> bool { return a.get_units() > b.get_units(); } + constexpr auto operator<=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() <= b.get_units(); } + constexpr auto operator>=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() >= b.get_units(); } + constexpr auto operator+(cycle_count const a, cycle_count const b) -> cycle_count { return cycle_count{ a.get_units() + b.get_units() }; } + constexpr auto operator-(cycle_count const a, cycle_count const b) -> cycle_count { return cycle_count{ a.get_units() - b.get_units() }; } + constexpr auto operator*(std::uint64_t const a, cycle_count const b) -> cycle_count { return cycle_count{ a * b.get_units() }; } + constexpr auto operator*(cycle_count const a, std::uint64_t const b) -> cycle_count { return cycle_count{ a.get_units() * b }; } + constexpr auto operator/(cycle_count const a, cycle_count const b) -> std::uint64_t { return a.get_units() / b.get_units(); } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/types/snapshot.hh b/lib/nes/sys/types/snapshot.hh new file mode 100644 index 0000000..683e862 --- /dev/null +++ b/lib/nes/sys/types/snapshot.hh @@ -0,0 +1,63 @@ +#pragma once + +#include "nes/sys/types/cycle-count.hh" +#include +#include + +namespace nes +{ + /// System snapshot used for comparison with a reference implementation. + struct snapshot + { + explicit snapshot() = default; + + cycle_count cpu_cycle; + cycle_count ppu_cycle; + std::vector ram; + std::vector sram; + std::vector vram; + std::vector oam; + struct + { + std::uint16_t pc{}; + std::uint8_t sp{}; + std::uint8_t a{}; + std::uint8_t x{}; + std::uint8_t y{}; + std::uint8_t p{}; + bool c{}; + bool z{}; + bool i{}; + bool d{}; + bool b{}; + bool v{}; + bool n{}; + } registers{}; + }; + + inline auto operator==(snapshot const& a, snapshot const& b) -> bool + { + if (a.cpu_cycle != b.cpu_cycle) { return false; } + if (a.ppu_cycle != b.ppu_cycle) { return false; } + if (a.ram != b.ram) { return false; } + if (a.sram != b.sram) { return false; } + if (a.vram != b.vram) { return false; } + if (a.oam != b.oam) { return false; } + if (a.registers.pc != b.registers.pc) { return false; } + if (a.registers.sp != b.registers.sp) { return false; } + if (a.registers.a != b.registers.a) { return false; } + if (a.registers.x != b.registers.x) { return false; } + if (a.registers.y != b.registers.y) { return false; } + if (a.registers.p != b.registers.p) { return false; } + if (a.registers.c != b.registers.c) { return false; } + if (a.registers.z != b.registers.z) { return false; } + if (a.registers.i != b.registers.i) { return false; } + if (a.registers.d != b.registers.d) { return false; } + if (a.registers.b != b.registers.b) { return false; } + if (a.registers.v != b.registers.v) { return false; } + if (a.registers.n != b.registers.n) { return false; } + return true; + } + + inline auto operator!=(snapshot const& a, snapshot const& b) -> bool { return !(a == b); } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/sys/types/status.hh b/lib/nes/sys/types/status.hh new file mode 100644 index 0000000..64f5969 --- /dev/null +++ b/lib/nes/sys/types/status.hh @@ -0,0 +1,36 @@ +#pragma once + +namespace nes +{ + /// A status code. + enum class status + { + success, + error_uninitialized, + error_system_error, + error_invalid_ines_data, + error_unsupported_mapper, + error_unsupported_instruction, + }; + + constexpr auto to_string(status const status) -> char const* + { + switch (status) + { + case status::success: + return "Success"; + case status::error_uninitialized: + return "Uninitialized"; + case status::error_system_error: + return "System error"; + case status::error_invalid_ines_data: + return "Invalid iNES data"; + case status::error_unsupported_mapper: + return "Unsupported mapper"; + case status::error_unsupported_instruction: + return "Unsupported instruction"; + } + + return "(invalid)"; + } +} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/CMakeLists.txt b/lib/nes/util/CMakeLists.txt deleted file mode 100644 index 6f5c112..0000000 --- a/lib/nes/util/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -target_sources( - nes - PRIVATE - address.hh - button-mask.hh - cycle-count.hh - debug.hh - display.hh - rgb.hh - snapshot.hh - status.hh) \ No newline at end of file diff --git a/lib/nes/util/address.hh b/lib/nes/util/address.hh deleted file mode 100644 index 2cce2f0..0000000 --- a/lib/nes/util/address.hh +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include - -namespace nes -{ - /// An address on the NES. - class address - { - std::uint16_t absolute_{ 0 }; - - public: - /// Create a zero address. - constexpr explicit address() = default; - - /// Create an address from its absolute value. - constexpr explicit address(std::uint16_t const absolute) - : absolute_{ absolute } - { - } - - /// Create an address from its page and offset combination. - constexpr explicit address(std::uint8_t const page, std::uint8_t const offset) - : address{ static_cast((page << 8) | (offset << 0)) } - { - } - - /// Get the absolute address value. - constexpr auto get_absolute() const -> std::uint16_t - { - return absolute_; - } - - /// Get the address page (the first 8 bits). - constexpr auto get_page() const -> std::uint8_t - { - return static_cast((get_absolute() & 0xFF00) >> 8); - } - - /// Get the offset within the page (the last 8 bits). - constexpr auto get_offset() const -> std::uint8_t - { - return static_cast((get_absolute() & 0x00FF) >> 0); - } - }; - - constexpr auto operator==(address const a, address const b) -> bool { return a.get_absolute() == b.get_absolute(); } - constexpr auto operator!=(address const a, address const b) -> bool { return a.get_absolute() != b.get_absolute(); } - constexpr auto operator<(address const a, address const b) -> bool { return a.get_absolute() < b.get_absolute(); } - constexpr auto operator>(address const a, address const b) -> bool { return a.get_absolute() > b.get_absolute(); } - constexpr auto operator<=(address const a, address const b) -> bool { return a.get_absolute() <= b.get_absolute(); } - constexpr auto operator>=(address const a, address const b) -> bool { return a.get_absolute() >= b.get_absolute(); } - - constexpr auto operator+(address const a, unsigned const b) -> address - { - return address{ static_cast(a.get_absolute() + b) }; - } - - constexpr auto operator+(unsigned const a, address const b) -> address - { - return address{ static_cast(a + b.get_absolute()) }; - } - - constexpr auto operator-(address const a, unsigned const b) -> address - { - return address{ static_cast(a.get_absolute() - b) }; - } - - constexpr auto operator%(address const a, unsigned const b) -> address - { - return address{ static_cast(a.get_absolute() % b) }; - } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/button-mask.hh b/lib/nes/util/button-mask.hh deleted file mode 100644 index b010b9e..0000000 --- a/lib/nes/util/button-mask.hh +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -namespace nes -{ - class button_mask - { - std::uint8_t raw_value_{ 0 }; - - public: - constexpr explicit button_mask() = default; - - static constexpr auto from_raw_value(std::uint8_t const raw_value) -> button_mask - { - return button_mask{ raw_value }; - } - - constexpr auto add(button_mask const other) -> void { raw_value_ |= other.raw_value_; } - constexpr auto remove(button_mask const other) -> void { raw_value_ ^= other.raw_value_; } - constexpr auto contains(button_mask const other) const -> bool { return (raw_value_ & other.raw_value_) == other.raw_value_; } - constexpr auto is_empty() const -> bool { return raw_value_ == 0; } - constexpr auto get_raw_value() const -> std::uint8_t { return raw_value_; } - - private: - constexpr explicit button_mask(std::uint8_t const raw_value) - : raw_value_{ raw_value } - { - } - }; - - namespace buttons - { - static constexpr auto a = button_mask::from_raw_value(1 << 0); - static constexpr auto b = button_mask::from_raw_value(1 << 1); - static constexpr auto select = button_mask::from_raw_value(1 << 2); - static constexpr auto start = button_mask::from_raw_value(1 << 3); - static constexpr auto up = button_mask::from_raw_value(1 << 4); - static constexpr auto down = button_mask::from_raw_value(1 << 5); - static constexpr auto left = button_mask::from_raw_value(1 << 6); - static constexpr auto right = button_mask::from_raw_value(1 << 7); - } // namespace buttons -} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/cycle-count.hh b/lib/nes/util/cycle-count.hh deleted file mode 100644 index f1a4a7e..0000000 --- a/lib/nes/util/cycle-count.hh +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include - -namespace nes -{ - /// A cycle count that can be used for multiple components. - /// - /// Each component can have a different clock frequency and this structure offers conversions between different - /// cycle counts. - class cycle_count - { - friend constexpr auto operator+(cycle_count, cycle_count) -> cycle_count; - friend constexpr auto operator-(cycle_count, cycle_count) -> cycle_count; - friend constexpr auto operator*(std::uint64_t, cycle_count) -> cycle_count; - friend constexpr auto operator*(cycle_count, std::uint64_t) -> cycle_count; - friend constexpr auto operator/(cycle_count, cycle_count) -> std::uint64_t; - - std::uint64_t units_{ 0 }; // The most fine-granular cycle count (here: PPU cycles). - - static constexpr auto ppu_cycle() -> cycle_count { return cycle_count{ 1 }; } - static constexpr auto cpu_cycle() -> cycle_count { return cycle_count{ 3 }; } - static constexpr auto second() -> cycle_count { return 1789773 * cpu_cycle(); } - - public: - constexpr explicit cycle_count() = default; - - constexpr auto operator+=(cycle_count const other) -> cycle_count& { return *this = *this + other; } - constexpr auto operator*=(std::uint64_t const other) -> cycle_count& { return *this = *this * other; } - - static constexpr auto from_units(std::uint64_t const units) -> cycle_count - { - return cycle_count{ units }; - } - - static constexpr auto from_duration(std::chrono::microseconds const duration) -> cycle_count - { - return from_units(static_cast(duration.count()) * second().get_units() / (1000 * 1000)); - } - - static constexpr auto from_cpu(std::uint64_t const cpu_cycles) -> cycle_count - { - return cpu_cycles * cpu_cycle(); - } - - static constexpr auto from_ppu(std::uint64_t const ppu_cycles) -> cycle_count - { - return ppu_cycles * ppu_cycle(); - } - - /// Value in implementation-defined units. - constexpr auto get_units() const -> std::uint64_t { return units_; } - - constexpr auto to_duration() const -> std::chrono::microseconds - { - return std::chrono::microseconds{ get_units() * 1000 * 1000 / second().get_units() }; - } - - constexpr auto to_cpu() const -> std::uint64_t - { - return *this / cpu_cycle(); - } - - constexpr auto to_ppu() const -> std::uint64_t - { - return *this / ppu_cycle(); - } - - private: - constexpr explicit cycle_count(std::uint64_t const units) - : units_{ units } - { - } - }; - - constexpr auto operator==(cycle_count const a, cycle_count const b) -> bool { return a.get_units() == b.get_units(); } - constexpr auto operator!=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() != b.get_units(); } - constexpr auto operator<(cycle_count const a, cycle_count const b) -> bool { return a.get_units() < b.get_units(); } - constexpr auto operator>(cycle_count const a, cycle_count const b) -> bool { return a.get_units() > b.get_units(); } - constexpr auto operator<=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() <= b.get_units(); } - constexpr auto operator>=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() >= b.get_units(); } - constexpr auto operator+(cycle_count const a, cycle_count const b) -> cycle_count { return cycle_count{ a.get_units() + b.get_units() }; } - constexpr auto operator-(cycle_count const a, cycle_count const b) -> cycle_count { return cycle_count{ a.get_units() - b.get_units() }; } - constexpr auto operator*(std::uint64_t const a, cycle_count const b) -> cycle_count { return cycle_count{ a * b.get_units() }; } - constexpr auto operator*(cycle_count const a, std::uint64_t const b) -> cycle_count { return cycle_count{ a.get_units() * b }; } - constexpr auto operator/(cycle_count const a, cycle_count const b) -> std::uint64_t { return a.get_units() / b.get_units(); } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/debug.hh b/lib/nes/util/debug.hh deleted file mode 100644 index 409d55a..0000000 --- a/lib/nes/util/debug.hh +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#ifdef NES_ENABLE_DEBUG_OUTPUT - -#include - -#define NES_DEBUG_LOG(name, ...) \ - spdlog::info("[" #name "] " __VA_ARGS__) - -#else - -#define NES_DEBUG_LOG(name, fmt, ...) \ - (void)0 - -#endif \ No newline at end of file diff --git a/lib/nes/util/display.hh b/lib/nes/util/display.hh deleted file mode 100644 index 8b6e0e5..0000000 --- a/lib/nes/util/display.hh +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "nes/util/rgb.hh" - -namespace nes -{ - class rgb; - - /// Abstraction of the display logic. - class display - { - public: - static constexpr auto width = unsigned{ 256 }; - static constexpr auto height = unsigned{ 240 }; - - virtual ~display() = default; - - display(display const&) = delete; - display(display&&) = delete; - auto operator=(display const&) -> display& = delete; - auto operator=(display&&) -> display& = delete; - - /// Switch between front and back buffers. - virtual auto switch_buffers() -> void = 0; - /// Update the pixel at the given position in the back buffer. - virtual auto set(unsigned x, unsigned y, rgb value) -> void = 0; - /// Load the pixel value at the given position in the back buffer. - virtual auto get(unsigned x, unsigned y) const -> rgb = 0; - /// Clear the entire back buffer with a color. - virtual auto clear(rgb const color) -> void - { - for (auto y = unsigned{ 0 }; y < height; ++y) - { - for (auto x = unsigned{ 0 }; x < width; ++x) - { - set(x, y, color); - } - } - } - - protected: - explicit display() = default; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/rgb.hh b/lib/nes/util/rgb.hh deleted file mode 100644 index d62c70c..0000000 --- a/lib/nes/util/rgb.hh +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -namespace nes -{ - /// An RGB value. - class rgb - { - public: - explicit rgb() = default; - - explicit rgb(std::uint8_t const r, std::uint8_t const g, std::uint8_t const b) - : r{ r } - , g{ g } - , b{ b } - { - } - - static auto from_hex(std::uint32_t const hex) -> rgb - { - auto res = rgb{}; - res.r = (hex & 0x00FF0000) >> 16; - res.g = (hex & 0x0000FF00) >> 8; - res.b = (hex & 0x000000FF) >> 0; - return res; - } - - std::uint8_t r{ 0 }; - std::uint8_t g{ 0 }; - std::uint8_t b{ 0 }; - }; -} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/snapshot.hh b/lib/nes/util/snapshot.hh deleted file mode 100644 index de23a46..0000000 --- a/lib/nes/util/snapshot.hh +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "nes/util/cycle-count.hh" -#include -#include - -namespace nes -{ - /// System snapshot used for comparison with a reference implementation. - struct snapshot - { - explicit snapshot() = default; - - cycle_count cpu_cycle; - cycle_count ppu_cycle; - std::vector ram; - std::vector sram; - std::vector vram; - std::vector oam; - struct - { - std::uint16_t pc{}; - std::uint8_t sp{}; - std::uint8_t a{}; - std::uint8_t x{}; - std::uint8_t y{}; - std::uint8_t p{}; - bool c{}; - bool z{}; - bool i{}; - bool d{}; - bool b{}; - bool v{}; - bool n{}; - } registers{}; - }; - - inline auto operator==(snapshot const& a, snapshot const& b) -> bool - { - if (a.cpu_cycle != b.cpu_cycle) { return false; } - if (a.ppu_cycle != b.ppu_cycle) { return false; } - if (a.ram != b.ram) { return false; } - if (a.sram != b.sram) { return false; } - if (a.vram != b.vram) { return false; } - if (a.oam != b.oam) { return false; } - if (a.registers.pc != b.registers.pc) { return false; } - if (a.registers.sp != b.registers.sp) { return false; } - if (a.registers.a != b.registers.a) { return false; } - if (a.registers.x != b.registers.x) { return false; } - if (a.registers.y != b.registers.y) { return false; } - if (a.registers.p != b.registers.p) { return false; } - if (a.registers.c != b.registers.c) { return false; } - if (a.registers.z != b.registers.z) { return false; } - if (a.registers.i != b.registers.i) { return false; } - if (a.registers.d != b.registers.d) { return false; } - if (a.registers.b != b.registers.b) { return false; } - if (a.registers.v != b.registers.v) { return false; } - if (a.registers.n != b.registers.n) { return false; } - return true; - } - - inline auto operator!=(snapshot const& a, snapshot const& b) -> bool { return !(a == b); } -} // namespace nes \ No newline at end of file diff --git a/lib/nes/util/status.hh b/lib/nes/util/status.hh deleted file mode 100644 index 64f5969..0000000 --- a/lib/nes/util/status.hh +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -namespace nes -{ - /// A status code. - enum class status - { - success, - error_uninitialized, - error_system_error, - error_invalid_ines_data, - error_unsupported_mapper, - error_unsupported_instruction, - }; - - constexpr auto to_string(status const status) -> char const* - { - switch (status) - { - case status::success: - return "Success"; - case status::error_uninitialized: - return "Uninitialized"; - case status::error_system_error: - return "System error"; - case status::error_invalid_ines_data: - return "Invalid iNES data"; - case status::error_unsupported_mapper: - return "Unsupported mapper"; - case status::error_unsupported_instruction: - return "Unsupported instruction"; - } - - return "(invalid)"; - } -} // namespace nes \ No newline at end of file -- cgit v1.3.1