diff options
| -rw-r--r-- | CMakeLists.txt | 50 | ||||
| -rw-r--r-- | app/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | app/mac/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | app/mac/main.mm (renamed from src/main.mm) | 0 | ||||
| -rw-r--r-- | app/mac/scene.hh (renamed from src/scene.hh) | 0 | ||||
| -rw-r--r-- | app/mac/scene.mm (renamed from src/scene.mm) | 0 | ||||
| -rw-r--r-- | app/test/CMakeLists.txt | 14 | ||||
| -rw-r--r-- | app/test/main.cc | 76 | ||||
| -rw-r--r-- | lib/CMakeLists.txt | 21 | ||||
| -rw-r--r-- | lib/nes/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | lib/nes/cartridge.cc (renamed from src/nes/cartridge.cc) | 0 | ||||
| -rw-r--r-- | lib/nes/cartridge.hh (renamed from src/nes/cartridge.hh) | 0 | ||||
| -rw-r--r-- | lib/nes/controller.cc (renamed from src/nes/controller.cc) | 2 | ||||
| -rw-r--r-- | lib/nes/controller.hh (renamed from src/nes/controller.hh) | 0 | ||||
| -rw-r--r-- | lib/nes/cpu.cc (renamed from src/nes/cpu.cc) | 19 | ||||
| -rw-r--r-- | lib/nes/cpu.hh (renamed from src/nes/cpu.hh) | 13 | ||||
| -rw-r--r-- | lib/nes/display.cc (renamed from src/nes/display.cc) | 0 | ||||
| -rw-r--r-- | lib/nes/display.hh (renamed from src/nes/display.hh) | 0 | ||||
| -rw-r--r-- | lib/nes/mapper.cc (renamed from src/nes/mapper.cc) | 5 | ||||
| -rw-r--r-- | lib/nes/mapper.hh (renamed from src/nes/mapper.hh) | 3 | ||||
| -rw-r--r-- | lib/nes/nes.cc (renamed from src/nes/nes.cc) | 10 | ||||
| -rw-r--r-- | lib/nes/nes.hh (renamed from src/nes/nes.hh) | 7 | ||||
| -rw-r--r-- | lib/nes/ppu.cc (renamed from src/nes/ppu.cc) | 64 | ||||
| -rw-r--r-- | lib/nes/ppu.hh (renamed from src/nes/ppu.hh) | 9 | ||||
| -rw-r--r-- | lib/nes/ref/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | lib/nes/ref/test.cc | 3 | ||||
| -rw-r--r-- | lib/nes/util/CMakeLists.txt (renamed from src/nes/util/CMakeLists.txt) | 6 | ||||
| -rw-r--r-- | lib/nes/util/address.hh (renamed from src/nes/util/address.hh) | 0 | ||||
| -rw-r--r-- | lib/nes/util/button-mask.hh (renamed from src/nes/util/button-mask.hh) | 0 | ||||
| -rw-r--r-- | lib/nes/util/cycle-count.hh (renamed from src/nes/util/cycle_count.hh) | 2 | ||||
| -rw-r--r-- | lib/nes/util/debug.hh | 15 | ||||
| -rw-r--r-- | lib/nes/util/rgb.hh (renamed from src/nes/util/rgb.hh) | 0 | ||||
| -rw-r--r-- | lib/nes/util/snapshot.hh | 63 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 13 | ||||
| -rw-r--r-- | src/nes/CMakeLists.txt | 19 | ||||
| -rw-r--r-- | src/status.hh | 97 | ||||
| -rw-r--r-- | src/test/CMakeLists.txt | 0 | ||||
| -rw-r--r-- | src/test/main.cc | 3 |
38 files changed, 335 insertions, 228 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bfbf5d..abd4f29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,49 @@ project( LANGUAGES CXX OBJCXX OBJC VERSION 1.0) -set(CMAKE_CXX_STANDARD 17) +option(NES_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer." OFF) +option(NES_ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer." OFF) +option(NES_ENABLE_DEBUG_OUTPUT "Enable debug output." OFF) +option(NES_ENABLE_PPU "Enable Picture Processing Unit." ON) -add_executable(nes) -add_subdirectory(src) +add_library(nes_options INTERFACE) +add_library(nes::options ALIAS nes_options) +target_compile_features(nes_options INTERFACE cxx_std_17) +target_compile_options(nes_options + INTERFACE + -Wall + -Wextra + -Wnon-virtual-dtor + -Wcast-align + -Wunused + -Woverloaded-virtual + -Wpedantic + -Wconversion + -Wsign-conversion + -Wdouble-promotion + -Wformat=2 + -Wswitch-enum + -Wno-gnu-anonymous-struct) +if(APPLE) + # Use automatic reference counting for Objective-C code. + target_compile_options(nes_options INTERFACE "-fobjc-arc") +endif() +if(NES_ENABLE_SANITIZER_ADDRESS AND NES_ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) + message(FATAL_ERROR "Address sanitizer and undefined behavior sanitizer cannot be enabled at the same time.") +elseif(NES_ENABLE_SANITIZER_ADDRESS) + target_compile_options(nes_options INTERFACE -fsanitize=address) +elseif(NES_ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) + target_compile_options(nes_options INTERFACE -fsanitize=undefined) +endif() -find_library(FRAMEWORK_COCOA Cocoa REQUIRED) -find_library(FRAMEWORK_SPRITEKIT SpriteKit REQUIRED) -target_link_libraries(nes PRIVATE ${FRAMEWORK_COCOA} ${FRAMEWORK_SPRITEKIT})
\ No newline at end of file +if(NES_ENABLE_DEBUG_OUTPUT) + include(FetchContent) + FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.0) + FetchContent_MakeAvailable(spdlog) +endif() + +add_subdirectory(lib) +add_subdirectory(app)
\ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..8ff9434 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,5 @@ +if(APPLE) + add_subdirectory(mac) +endif() + +add_subdirectory(test)
\ No newline at end of file diff --git a/app/mac/CMakeLists.txt b/app/mac/CMakeLists.txt new file mode 100644 index 0000000..a4f25c9 --- /dev/null +++ b/app/mac/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(nes_mac) + +find_library(FRAMEWORK_COCOA Cocoa REQUIRED) +find_library(FRAMEWORK_SPRITEKIT SpriteKit REQUIRED) + +target_include_directories(nes_mac PRIVATE .) +target_link_libraries( + nes_mac + PRIVATE + nes::options + nes::nes + ${FRAMEWORK_COCOA} + ${FRAMEWORK_SPRITEKIT}) + +target_sources( + nes_mac + PRIVATE + main.mm + scene.hh + scene.mm)
\ No newline at end of file diff --git a/src/main.mm b/app/mac/main.mm index 5ca7c5c..5ca7c5c 100644 --- a/src/main.mm +++ b/app/mac/main.mm diff --git a/src/scene.hh b/app/mac/scene.hh index 1660e66..1660e66 100644 --- a/src/scene.hh +++ b/app/mac/scene.hh diff --git a/src/scene.mm b/app/mac/scene.mm index f858185..f858185 100644 --- a/src/scene.mm +++ b/app/mac/scene.mm diff --git a/app/test/CMakeLists.txt b/app/test/CMakeLists.txt new file mode 100644 index 0000000..b88acc2 --- /dev/null +++ b/app/test/CMakeLists.txt @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..3bb785f --- /dev/null +++ b/app/test/main.cc @@ -0,0 +1,76 @@ +#include "nes/display.hh" +#include "nes/nes.hh" +#include "nes/util/rgb.hh" +#include "nes/util/snapshot.hh" +#include <iostream> + +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"; + + auto cartridge = nes::cartridge::from_file(path); + if (!cartridge.is_valid()) + { + std::cerr << "Could not load cartridge!" << std::endl; + std::abort(); + } + + // Initialize the system to be tested. + auto display_a = std::make_unique<dummy_display>(); + auto sys_a = nes::nes{ cartridge, *display_a }; + + // Initialize the (assumed to be working) reference implementation. + auto display_b = std::make_unique<dummy_display>(); + auto sys_b = nes::nes{ cartridge, *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 new file mode 100644 index 0000000..e98002c --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(nes STATIC) +add_library(nes::nes ALIAS nes) + +target_include_directories(nes PUBLIC .) +target_link_libraries(nes PRIVATE nes::options) + +if(NES_ENABLE_PPU) + target_compile_definitions(nes PUBLIC NES_ENABLE_PPU) +endif() +if(NES_ENABLE_DEBUG_OUTPUT) + target_compile_definitions(nes PUBLIC 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 new file mode 100644 index 0000000..78b7d04 --- /dev/null +++ b/lib/nes/CMakeLists.txt @@ -0,0 +1,20 @@ +target_sources( + nes + PRIVATE + nes.hh + nes.cc + cartridge.hh + cartridge.cc + display.hh + display.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 diff --git a/src/nes/cartridge.cc b/lib/nes/cartridge.cc index 861dc13..861dc13 100644 --- a/src/nes/cartridge.cc +++ b/lib/nes/cartridge.cc diff --git a/src/nes/cartridge.hh b/lib/nes/cartridge.hh index 3ce2a16..3ce2a16 100644 --- a/src/nes/cartridge.hh +++ b/lib/nes/cartridge.hh diff --git a/src/nes/controller.cc b/lib/nes/controller.cc index 87842e2..d9d6946 100644 --- a/src/nes/controller.cc +++ b/lib/nes/controller.cc @@ -7,7 +7,7 @@ namespace nes auto const res = index_ < 8 ? (pressed_.get_raw_value() >> index_) & 1 : 0; index_ += 1; if (strobing_) { index_ = 0; } - return res; + return static_cast<std::uint8_t>(res); } auto controller::write(std::uint8_t const value) -> void diff --git a/src/nes/controller.hh b/lib/nes/controller.hh index fa16859..fa16859 100644 --- a/src/nes/controller.hh +++ b/lib/nes/controller.hh diff --git a/src/nes/cpu.cc b/lib/nes/cpu.cc index 9060e72..51507b0 100644 --- a/src/nes/cpu.cc +++ b/lib/nes/cpu.cc @@ -2,8 +2,7 @@ #include "nes/ppu.hh" #include "nes/controller.hh" #include "nes/mapper.hh" - -#define ENABLE_PPU +#include "nes/util/snapshot.hh" namespace nes { @@ -16,7 +15,7 @@ namespace nes registers_.pc = read16(address{ 0xFFFC }); } - auto cpu::snapshot(test::status& snapshot) -> void + auto cpu::build_snapshot(snapshot& snapshot) -> void { snapshot.cpu_cycle = current_cycles_; snapshot.ram = std::vector(std::begin(ram_), std::end(ram_)); @@ -49,13 +48,13 @@ namespace nes { // See https://www.nesdev.org/wiki/CPU_unofficial_opcodes +#ifdef NES_ENABLE_PPU if (nmi_pending_) { -#ifdef ENABLE_PPU execute_interrupt(address{ 0xFFFA }); -#endif nmi_pending_ = false; } +#endif auto const opcode = advance_pc8(); switch (opcode) @@ -1261,7 +1260,7 @@ namespace nes auto cpu::fetch_operand<cpu::addressing_mode::immediate>(force_page_crossing) -> operand<addressing_mode::immediate> { - return operand<addressing_mode::immediate>{ *this, advance_pc8() }; + return operand<addressing_mode::immediate>{ advance_pc8() }; } // ----------------------------------------------------------------------------------------------------------------- @@ -1277,7 +1276,7 @@ namespace nes if (addr <= address{ 0x1FFF }) { return ram_[addr.get_absolute() % ram_size]; } if (addr <= address{ 0x3FFF }) { -#ifdef ENABLE_PPU +#ifdef NES_ENABLE_PPU switch (addr.get_absolute() % 8) { case 0: return ppu_.read_latch(); @@ -1295,7 +1294,7 @@ namespace nes #endif } if (addr <= address{ 0x4013 }) { return 0x0; } // TODO: APU registers -#ifdef ENABLE_PPU +#ifdef NES_ENABLE_PPU if (addr == address{ 0x4014 }) { return ppu_.read_latch(); } #else if (addr == address{ 0x4014 }) { return 0x0; } @@ -1319,7 +1318,7 @@ namespace nes if (addr <= address{ 0x1FFF }) { ram_[addr.get_absolute() % ram_size] = value; return; } if (addr <= address{ 0x3FFF }) { -#ifdef ENABLE_PPU +#ifdef NES_ENABLE_PPU switch (addr.get_absolute() % 8) { case 0: ppu_.write_ppuctrl(value); return; @@ -1337,7 +1336,7 @@ namespace nes #endif } if (addr <= address{ 0x4013 }) { return; } // TODO: APU registers -#ifdef ENABLE_PPU +#ifdef NES_ENABLE_PPU if (addr == address{ 0x4014 }) { ppu_.write_oamdma(value); return; } #else if (addr == address{ 0x4014 }) { return; } diff --git a/src/nes/cpu.hh b/lib/nes/cpu.hh index fc3ef5b..9511c09 100644 --- a/src/nes/cpu.hh +++ b/lib/nes/cpu.hh @@ -1,14 +1,14 @@ #pragma once -#include "nes/util/cycle_count.hh" +#include "nes/util/cycle-count.hh" #include "nes/util/address.hh" -#include "status.hh" namespace nes { class ppu; class controller; class mapper; + struct snapshot; class cpu { @@ -110,13 +110,11 @@ namespace nes template<> class operand<addressing_mode::immediate> { - cpu& cpu_; std::uint8_t value_{ 0 }; public: - explicit operand(cpu& cpu, std::uint8_t const value) - : cpu_{ cpu } - , value_{ value } + explicit operand(std::uint8_t const value) + : value_{ value } { } @@ -132,9 +130,8 @@ namespace nes auto operator=(cpu const&) -> cpu& = delete; auto operator=(cpu&&) -> cpu& = delete; - auto snapshot(test::status&) -> void; - 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; diff --git a/src/nes/display.cc b/lib/nes/display.cc index 4ff1d5f..4ff1d5f 100644 --- a/src/nes/display.cc +++ b/lib/nes/display.cc diff --git a/src/nes/display.hh b/lib/nes/display.hh index 14ccff1..14ccff1 100644 --- a/src/nes/display.hh +++ b/lib/nes/display.hh diff --git a/src/nes/mapper.cc b/lib/nes/mapper.cc index 5392d87..9346be7 100644 --- a/src/nes/mapper.cc +++ b/lib/nes/mapper.cc @@ -16,11 +16,6 @@ namespace nes { } - auto mapper::snapshot(test::status& snapshot) const -> void - { - snapshot.chrrom = std::vector(cartridge_.get_chr_rom(), cartridge_.get_chr_rom() + cartridge_.get_chr_rom_length()); - } - auto mapper::select(cartridge& cartridge) -> mapper { switch (cartridge.get_mapper_number()) diff --git a/src/nes/mapper.hh b/lib/nes/mapper.hh index 12ad804..f2873a6 100644 --- a/src/nes/mapper.hh +++ b/lib/nes/mapper.hh @@ -1,7 +1,6 @@ #pragma once #include "nes/util/address.hh" -#include "status.hh" namespace nes { @@ -28,8 +27,6 @@ namespace nes auto operator=(mapper const&) -> mapper& = delete; auto operator=(mapper&&) -> mapper& = delete; - auto snapshot(test::status&) const -> void; - auto read_cpu(address const addr) -> std::uint8_t { return (this->*read_cpu_)(addr); diff --git a/src/nes/nes.cc b/lib/nes/nes.cc index 7ac1887..01e339d 100644 --- a/src/nes/nes.cc +++ b/lib/nes/nes.cc @@ -2,6 +2,7 @@ #include "nes/mapper.hh" #include "nes/display.hh" #include "nes/util/rgb.hh" +#include "nes/util/snapshot.hh" #include <iostream> namespace nes @@ -62,13 +63,12 @@ namespace nes } } - auto nes::snapshot() -> test::status + auto nes::get_snapshot() -> snapshot { - auto res = test::status{}; + auto res = snapshot{}; res.sram = std::vector(cartridge_.get_ram(), cartridge_.get_ram() + cartridge_.get_ram_length()); - cpu_.snapshot(res); - ppu_.snapshot(res); - mapper_.snapshot(res); + cpu_.build_snapshot(res); + ppu_.build_snapshot(res); return res; } } // namespace nes
\ No newline at end of file diff --git a/src/nes/nes.hh b/lib/nes/nes.hh index 76c29ab..fe57cd4 100644 --- a/src/nes/nes.hh +++ b/lib/nes/nes.hh @@ -1,16 +1,16 @@ #pragma once -#include "nes/util/cycle_count.hh" +#include "nes/util/cycle-count.hh" #include "nes/cartridge.hh" #include "nes/controller.hh" #include "nes/cpu.hh" #include "nes/ppu.hh" #include "nes/mapper.hh" -#include "status.hh" namespace nes { class display; + struct snapshot; /// The main console abstraction. class nes @@ -36,12 +36,11 @@ namespace nes 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 ref_operations() -> std::vector<test::memory_operation>& { return ppu_.memory_operations; } auto step(std::chrono::microseconds delta) -> void; auto step(cycle_count delta) -> void; auto step_to_nmi() -> void; // XXX: Debugging - auto snapshot() -> test::status; + auto get_snapshot() -> snapshot; }; } // namespace nes
\ No newline at end of file diff --git a/src/nes/ppu.cc b/lib/nes/ppu.cc index 87c7f8d..3170de9 100644 --- a/src/nes/ppu.cc +++ b/lib/nes/ppu.cc @@ -4,9 +4,8 @@ #include "nes/mapper.hh" #include "nes/util/address.hh" #include "nes/util/rgb.hh" -#include <iostream> - -// #define ENABLE_TRACES +#include "nes/util/snapshot.hh" +#include "nes/util/debug.hh" namespace nes { @@ -17,7 +16,7 @@ namespace nes { } - auto ppu::snapshot(test::status& snapshot) -> void + 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_)); @@ -137,7 +136,6 @@ namespace nes if (pre_line && scanline_cycle_ == 1) { status_.vblank = false; - nmi_change(); status_.sprite_zero_hit = false; status_.sprite_overflow = false; } @@ -461,13 +459,10 @@ namespace nes { auto const base = latch_ & ~status_mask; auto const res = base | (status_.value & status_mask); -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::read_ppustatus, res, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "PPUSTATUS -> {:#2x}", res); status_.vblank = false; internal_.w = false; - nmi_change(); write_latch(res); return res; @@ -480,9 +475,7 @@ namespace nes { res &= 0xE3; } -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::read_oamdata, res, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "OAMDATA -> {:#2x}", res); write_latch(res); return res; } @@ -503,9 +496,7 @@ namespace nes ppudata_read_buffer_ = read8(addr - 0x1000); } -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::read_ppudata, res, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "PPUDATA -> {:#2x}", res); increment_vram(); return res; } @@ -517,26 +508,22 @@ namespace nes auto ppu::write_ppuctrl(std::uint8_t const value) -> void { + write_latch(value); if (current_cycles_ > boot_up_cycles) { - write_latch(value); + NES_DEBUG_LOG(ppu, "PPUCTRL <- {:#2x}", value); control_.value = value; internal_.t = (internal_.t & 0xF3FF) | ((value & 0x03) << 10); -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_ppuctrl, value, current_cycles_); -#endif nmi_change(); } } auto ppu::write_ppuscroll(std::uint8_t const value) -> void { + write_latch(value); if (current_cycles_ > boot_up_cycles) { - write_latch(value); -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_ppuscroll, value, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "PPUSCROLL <- {:#2x}", value); if (!internal_.w) { // First write -> x value. @@ -556,24 +543,20 @@ namespace nes auto ppu::write_ppumask(std::uint8_t const value) -> void { + write_latch(value); if (current_cycles_ > boot_up_cycles) { - write_latch(value); -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_ppumask, value, current_cycles_); -#endif + 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) { - write_latch(value); -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_ppuaddr, value, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "PPUADDR <- {:#2x}", value); if (!internal_.w) { // First write. @@ -592,9 +575,7 @@ namespace nes auto ppu::write_ppudata(std::uint8_t const value) -> void { -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_ppudata, value, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "PPUDATA <- {:#2x} (address: {:#4x})", value, internal_.v); write_latch(value); write8(address{ internal_.v }, value); increment_vram(); @@ -602,18 +583,14 @@ namespace nes auto ppu::write_oamaddr(std::uint8_t const value) -> void { -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_oamaddr, value, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "OAMADDR <- {:#2x}", value); write_latch(value); oamaddr_ = value; } auto ppu::write_oamdata(std::uint8_t const value) -> void { -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_oamdata, value, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "OAMDATA <- {:#2x}", value); write_latch(value); oam_[oamaddr_] = value; oamaddr_ += 1; @@ -622,9 +599,7 @@ namespace nes auto ppu::write_oamdma(std::uint8_t const value) -> void { // TODO: Do this in CPU::step instead in case there's an NMI? -#ifdef ENABLE_TRACES - memory_operations.emplace_back(test::memory_operation::type::write_oamdma, value, current_cycles_); -#endif + NES_DEBUG_LOG(ppu, "OAMDMA <- {:#2x}", value); auto addr = address{ value, 0x00 }; for (auto i = unsigned{ 0 }; i < 256; ++i) { @@ -643,8 +618,7 @@ namespace nes auto ppu::nmi_change() -> void { - auto const should_trigger = control_.vblank_nmi && status_.vblank; - if (should_trigger) + if (control_.vblank_nmi && status_.vblank) { cpu_.trigger_nmi(); } diff --git a/src/nes/ppu.hh b/lib/nes/ppu.hh index fff51de..d7d9baa 100644 --- a/src/nes/ppu.hh +++ b/lib/nes/ppu.hh @@ -1,7 +1,6 @@ #pragma once -#include "nes/util/cycle_count.hh" -#include "status.hh" +#include "nes/util/cycle-count.hh" namespace nes { @@ -10,6 +9,7 @@ namespace nes class mapper; class display; class rgb; + struct snapshot; class ppu { @@ -148,7 +148,6 @@ namespace nes std::uint8_t high_tile_byte_{ 0 }; sprite sprites_[8]{}; // Evaluated sprites. unsigned sprite_count_{ 0 }; // Number of evaluated sprites in sprites_. - bool nmi_requested_{ false }; public: explicit ppu(cpu&, mapper&, display&); @@ -158,10 +157,8 @@ namespace nes auto operator=(ppu const&) -> ppu& = delete; auto operator=(ppu&&) -> ppu& = delete; - std::vector<test::memory_operation> memory_operations; - auto get_cycles() const -> cycle_count { return current_cycles_; } - auto snapshot(test::status&) -> void; + auto build_snapshot(snapshot&) -> void; auto step() -> void; // Memory access diff --git a/lib/nes/ref/CMakeLists.txt b/lib/nes/ref/CMakeLists.txt new file mode 100644 index 0000000..ab8e74d --- /dev/null +++ b/lib/nes/ref/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources( + nes_ref + PRIVATE + test.cc)
\ No newline at end of file diff --git a/lib/nes/ref/test.cc b/lib/nes/ref/test.cc new file mode 100644 index 0000000..210ece0 --- /dev/null +++ b/lib/nes/ref/test.cc @@ -0,0 +1,3 @@ +// +// Created by Hannes Schulze on 23.01.25. +// diff --git a/src/nes/util/CMakeLists.txt b/lib/nes/util/CMakeLists.txt index adaa80c..0fdb0ba 100644 --- a/src/nes/util/CMakeLists.txt +++ b/lib/nes/util/CMakeLists.txt @@ -3,5 +3,7 @@ target_sources( PRIVATE address.hh button-mask.hh - cycle_count.hh - rgb.hh)
\ No newline at end of file + cycle-count.hh + debug.hh + rgb.hh + snapshot.hh)
\ No newline at end of file diff --git a/src/nes/util/address.hh b/lib/nes/util/address.hh index 7987d07..7987d07 100644 --- a/src/nes/util/address.hh +++ b/lib/nes/util/address.hh diff --git a/src/nes/util/button-mask.hh b/lib/nes/util/button-mask.hh index b010b9e..b010b9e 100644 --- a/src/nes/util/button-mask.hh +++ b/lib/nes/util/button-mask.hh diff --git a/src/nes/util/cycle_count.hh b/lib/nes/util/cycle-count.hh index 8afd40a..f1a4a7e 100644 --- a/src/nes/util/cycle_count.hh +++ b/lib/nes/util/cycle-count.hh @@ -35,7 +35,7 @@ namespace nes static constexpr auto from_duration(std::chrono::microseconds const duration) -> cycle_count { - return from_units(duration.count() * second().get_units() / (1000 * 1000)); + return from_units(static_cast<std::uint64_t>(duration.count()) * second().get_units() / (1000 * 1000)); } static constexpr auto from_cpu(std::uint64_t const cpu_cycles) -> cycle_count diff --git a/lib/nes/util/debug.hh b/lib/nes/util/debug.hh new file mode 100644 index 0000000..409d55a --- /dev/null +++ b/lib/nes/util/debug.hh @@ -0,0 +1,15 @@ +#pragma once + +#ifdef NES_ENABLE_DEBUG_OUTPUT + +#include <spdlog/spdlog.h> + +#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/src/nes/util/rgb.hh b/lib/nes/util/rgb.hh index d62c70c..d62c70c 100644 --- a/src/nes/util/rgb.hh +++ b/lib/nes/util/rgb.hh diff --git a/lib/nes/util/snapshot.hh b/lib/nes/util/snapshot.hh new file mode 100644 index 0000000..de23a46 --- /dev/null +++ b/lib/nes/util/snapshot.hh @@ -0,0 +1,63 @@ +#pragma once + +#include "nes/util/cycle-count.hh" +#include <vector> +#include <cstdint> + +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<std::uint8_t> ram; + std::vector<std::uint8_t> sram; + std::vector<std::uint8_t> vram; + std::vector<std::uint8_t> 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/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index b997a1d..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_subdirectory(nes) -#add_subdirectory(ref) -#add_subdirectory(test) - -target_sources( - nes - PRIVATE - main.mm - status.hh - scene.hh - scene.mm) - -target_include_directories(nes PRIVATE .)
\ No newline at end of file diff --git a/src/nes/CMakeLists.txt b/src/nes/CMakeLists.txt deleted file mode 100644 index fcdcb07..0000000 --- a/src/nes/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -add_subdirectory(util) - -target_sources( - nes - PRIVATE - nes.hh - nes.cc - cartridge.hh - cartridge.cc - display.hh - display.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/src/status.hh b/src/status.hh deleted file mode 100644 index b56be58..0000000 --- a/src/status.hh +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include "nes/util/cycle_count.hh" -#include <vector> -#include <cstdint> - -namespace test -{ - /// System status used for comparison. - struct status - { - explicit status() = default; - - nes::cycle_count cpu_cycle; - std::vector<std::uint8_t> ram; - std::vector<std::uint8_t> sram; - std::vector<std::uint8_t> vram; - std::vector<std::uint8_t> oam; - std::vector<std::uint8_t> chrrom; - 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{}; - - static auto equal(status const& a, status const& b) -> bool - { - if (a.cpu_cycle != b.cpu_cycle) { return false; } - if (a.ram != b.ram) { return false; } - if (a.sram != b.sram) { 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; - } - }; - - struct memory_operation - { - enum class type - { - read_ppustatus, - read_oamdata, - read_ppudata, - write_ppuctrl, - write_ppuscroll, - write_ppumask, - write_ppuaddr, - write_ppudata, - write_oamaddr, - write_oamdata, - write_oamdma, - }; - - explicit constexpr memory_operation(type const t, std::uint8_t const v, nes::cycle_count cycles) - : t{ t } - , v{ v } - , cycles{ cycles } - { - } - - type t{}; - std::uint8_t v{}; - nes::cycle_count cycles; - }; - - constexpr auto operator==(memory_operation const a, memory_operation const b) -> bool - { - return a.t == b.t && a.v == b.v; - } - - constexpr auto operator!=(memory_operation const a, memory_operation const b) -> bool - { - return a.t != b.t || a.v != b.v; - } -} // namespace test
\ No newline at end of file diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt deleted file mode 100644 index e69de29..0000000 --- a/src/test/CMakeLists.txt +++ /dev/null diff --git a/src/test/main.cc b/src/test/main.cc deleted file mode 100644 index 58e618d..0000000 --- a/src/test/main.cc +++ /dev/null @@ -1,3 +0,0 @@ -// -// Created by Hannes Schulze on 17.01.25. -// |
