From 430a246fe81d7eab6823bf6ca4562fd1e99f2a5d Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Sun, 26 Jan 2025 23:19:56 +0100 Subject: refactor mappers --- lib/nes/cartridge.cc | 37 ++- lib/nes/cartridge.hh | 17 +- lib/nes/cpu.cc | 763 ++++++++++++++++++++++++-------------------- lib/nes/cpu.hh | 13 +- lib/nes/mapper.cc | 252 ++++++++------- lib/nes/mapper.hh | 56 +--- lib/nes/nes.cc | 20 +- lib/nes/nes.hh | 4 +- lib/nes/ppu.cc | 15 +- lib/nes/ppu.hh | 9 +- lib/nes/util/CMakeLists.txt | 3 +- lib/nes/util/status.hh | 36 +++ 12 files changed, 674 insertions(+), 551 deletions(-) create mode 100644 lib/nes/util/status.hh (limited to 'lib/nes') diff --git a/lib/nes/cartridge.cc b/lib/nes/cartridge.cc index d4ade23..6a60741 100644 --- a/lib/nes/cartridge.cc +++ b/lib/nes/cartridge.cc @@ -1,36 +1,38 @@ #include "nes/cartridge.hh" +#include "nes/util/status.hh" #include namespace nes { - namespace + cartridge::cartridge(status const status) + : status_{ status } { - 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; + auto res = cartridge{ status::success }; // Parse header. - if (length < 16) { return cartridge{}; } - if (bytes[0] != 'N' || bytes[1] != 'E' || bytes[2] != 'S' || bytes[3] != 0x1A) { return cartridge{}; } + if (length < 16) { return cartridge{ status::error_invalid_ines_data }; } + if (bytes[0] != 'N' || bytes[1] != 'E' || bytes[2] != 'S' || bytes[3] != 0x1A) + { + return cartridge{ status::error_invalid_ines_data }; + } 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] : 1u; auto const has_trainer = (control_1 & 0b00000100) != 0; - - res.mapper_number_ = + auto const mapper_number = static_cast( (control_1 & 0b11110000) >> 4 | - (control_2 & 0b11110000) >> 0; + (control_2 & 0b11110000) >> 0); + + res.mapper_ = &mapper::get(mapper_number); res.name_table_arrangement_ = (control_1 & 0b00000001) != 0 ? name_table_arrangement::vertical : name_table_arrangement::horizontal; @@ -40,24 +42,29 @@ namespace nes 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{}; } + 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 = chr_rom_banks * chr_rom_bank_size; - if (length < offset + chr_rom_size) { return cartridge{}; } + 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(ram_banks * 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{}; } + if (!stream.good()) { return cartridge{ status::error_system_error }; } stream.seekg(0, std::ios::end); auto const length = stream.tellg(); diff --git a/lib/nes/cartridge.hh b/lib/nes/cartridge.hh index 3ce2a16..13c3594 100644 --- a/lib/nes/cartridge.hh +++ b/lib/nes/cartridge.hh @@ -1,5 +1,7 @@ #pragma once +#include "nes/util/status.hh" +#include "nes/mapper.hh" #include #include #include @@ -14,12 +16,16 @@ namespace nes class cartridge { - bool is_valid_{ false }; + 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 }; + + status status_{ status::error_uninitialized }; std::vector prg_rom_; std::vector chr_rom_; std::vector ram_; - std::uint8_t mapper_number_{ 0 }; name_table_arrangement name_table_arrangement_{}; + mapper* mapper_{ &mapper::invalid() }; public: explicit cartridge() = default; @@ -27,7 +33,7 @@ namespace nes 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_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(); } @@ -35,7 +41,10 @@ namespace nes 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_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/cpu.cc b/lib/nes/cpu.cc index ee7416d..cf8943f 100644 --- a/lib/nes/cpu.cc +++ b/lib/nes/cpu.cc @@ -1,14 +1,14 @@ #include "nes/cpu.hh" #include "nes/ppu.hh" #include "nes/controller.hh" -#include "nes/mapper.hh" +#include "nes/cartridge.hh" #include "nes/util/snapshot.hh" namespace nes { - cpu::cpu(ppu& ppu, mapper& mapper, controller& controller_1, controller& controller_2) + cpu::cpu(ppu& ppu, cartridge& cartridge, controller& controller_1, controller& controller_2) : ppu_{ ppu } - , mapper_{ mapper } + , cartridge_{ cartridge } , controller_1_{ controller_1 } , controller_2_{ controller_2 } { @@ -44,7 +44,7 @@ namespace nes nmi_pending_ = true; } - auto cpu::step() -> void + auto cpu::step() -> status { // See https://www.nesdev.org/wiki/CPU_unofficial_opcodes @@ -59,263 +59,263 @@ namespace nes 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(); + 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; } } @@ -324,30 +324,31 @@ namespace nes // ----------------------------------------------------------------------------------------------------------------- template - auto cpu::run_brk() -> void + auto cpu::run_brk() -> status { // BRK: Break fetch_operand(); execute_interrupt(address{ 0xFFFE }); + return status::success; } template - auto cpu::run_ora() -> void + 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() -> void + auto cpu::run_stp() -> status { - // TODO: Proper STP handling - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_slo() -> void + auto cpu::run_slo() -> status { // SLO: ASL + ORA auto operand = fetch_operand(force_page_crossing::yes); @@ -355,64 +356,72 @@ namespace nes operand.write(tmp); eval_ora(tmp); current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; } template - auto cpu::run_nop() -> void + auto cpu::run_nop() -> status { // NOP: NOP auto operand = fetch_operand(); current_cycles_ += operand.get_cycles(); + return status::success; } - auto cpu::run_nop() -> void + auto cpu::run_nop() -> status { // NOP: NOP current_cycles_ += cycle_count::from_cpu(2); + return status::success; } - auto cpu::run_php() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_bpl() -> status { // BPL: Break If Positive branch(!registers_.p.get_n()); + return status::success; } - auto cpu::run_clc() -> void + 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() -> void + auto cpu::run_jsr() -> status { // JSR: Jump to Subroutine static_assert(Mode == addressing_mode::absolute, "JSR only supports absolute addressing."); @@ -422,19 +431,21 @@ namespace nes 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() -> void + 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() -> void + auto cpu::run_rla() -> status { // RLA: ROL + AND auto operand = fetch_operand(force_page_crossing::yes); @@ -442,10 +453,11 @@ namespace nes operand.write(tmp); eval_and(tmp); current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; } template - auto cpu::run_bit() -> void + auto cpu::run_bit() -> status { // BIT: Bit Test auto operand = fetch_operand(); @@ -455,57 +467,64 @@ namespace nes 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() -> void + 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() -> void + 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() -> void + auto cpu::run_bmi() -> status { branch(registers_.p.get_n()); + return status::success; } - auto cpu::run_sec() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_sre() -> status { // SRE: LSR + EOR auto operand = fetch_operand(force_page_crossing::yes); @@ -513,10 +532,11 @@ namespace nes operand.write(tmp); eval_eor(tmp); current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; } template - auto cpu::run_lsr() -> void + auto cpu::run_lsr() -> status { // LSR: Logical Shift Right auto operand = fetch_operand(); @@ -526,67 +546,75 @@ namespace nes 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() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_bvc() -> status { // BVC: Branch If Overflow Clear branch(!registers_.p.get_v()); + return status::success; } - auto cpu::run_cli() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_rra() -> status { // RRA: ROR + ADC auto operand = fetch_operand(force_page_crossing::yes); @@ -594,276 +622,302 @@ namespace nes operand.write(tmp); eval_adc(tmp); current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; } template - auto cpu::run_ror() -> void + 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() -> void + auto cpu::run_arr() -> status { // ARR: AND #i + ROR A but with different flags // TODO - std::abort(); + return status::error_unsupported_instruction; } - auto cpu::run_pla() -> void + 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() -> void + auto cpu::run_bvs() -> status { // BVS: Branch If Overflow Set branch(registers_.p.get_v()); + return status::success; } - auto cpu::run_sei() -> void + 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() -> void + 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() -> void + auto cpu::run_ahx() -> status { // AHX: ??? // TODO - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_sax() -> void + 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() -> void + 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() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_xaa() -> status { // XAA: Depends on analog behavior. run_stp(); + return status::success; } template - auto cpu::run_bcc() -> void + auto cpu::run_bcc() -> status { // BCC: Branch If Carry Clear branch(!registers_.p.get_c()); + return status::success; } - auto cpu::run_tya() -> void + 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() -> void + 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() -> void + auto cpu::run_tas() -> status { // TAS: ??? // TODO - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_shy() -> void + auto cpu::run_shy() -> status { // SHY: ??? // TODO - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_shx() -> void + auto cpu::run_shx() -> status { // SHY: ??? // TODO - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_ldy() -> void + 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() -> void + 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() -> void + 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() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_bcs() -> status { // BCS: Branch If Carry Set branch(registers_.p.get_c()); + return status::success; } - auto cpu::run_clv() -> void + 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() -> void + 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() -> void + auto cpu::run_las() -> status { // LAS: ??? // TODO - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_cpy() -> void + 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() -> void + auto cpu::run_axs() -> status { // AXS: Set X to ((A AND X) - #value without borrow), updates NZC // TODO - std::abort(); + return status::error_unsupported_instruction; } template - auto cpu::run_cmp() -> void + 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() -> void + auto cpu::run_dcp() -> status { // DCP: DEC + CMP auto operand = fetch_operand(force_page_crossing::yes); @@ -871,10 +925,11 @@ namespace nes 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() -> void + auto cpu::run_dec() -> status { // DEC: Decrement Memory auto operand = fetch_operand(); @@ -883,58 +938,65 @@ namespace nes write8(operand.get_address(), new_val); update_zn(new_val); current_cycles_ += inc_dec_cycle_count(); + return status::success; } - auto cpu::run_iny() -> void + 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() -> void + 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() -> void + auto cpu::run_bne() -> status { // BNE: Branch If Not Equal branch(!registers_.p.get_z()); + return status::success; } - auto cpu::run_cld() -> void + 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() -> void + 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() -> void + 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() -> void + auto cpu::run_isc() -> status { // ISC: INC + SBC auto operand = fetch_operand(force_page_crossing::yes); @@ -942,10 +1004,11 @@ namespace nes operand.write(tmp); eval_adc(~tmp); current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2); + return status::success; } template - auto cpu::run_inc() -> void + auto cpu::run_inc() -> status { // INC: Increment Memory auto operand = fetch_operand(); @@ -954,28 +1017,32 @@ namespace nes write8(operand.get_address(), new_val); update_zn(new_val); current_cycles_ += inc_dec_cycle_count(); + return status::success; } - auto cpu::run_inx() -> void + 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() -> void + auto cpu::run_beq() -> status { // BEQ: Branch If Equal branch(registers_.p.get_z()); + return status::success; } - auto cpu::run_sed() -> void + auto cpu::run_sed() -> status { // SED: Set Decimal Flag registers_.p.set_d(true); current_cycles_ += cycle_count::from_cpu(2); + return status::success; } // ----------------------------------------------------------------------------------------------------------------- @@ -1303,7 +1370,7 @@ namespace nes 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); + return cartridge_.get_mapper().read_cpu(addr, cartridge_); } auto cpu::read16(address const addr) -> std::uint16_t @@ -1345,7 +1412,7 @@ namespace nes 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); + cartridge_.get_mapper().write_cpu(addr, value, cartridge_); } auto cpu::write16(address const addr, std::uint16_t const value) -> void diff --git a/lib/nes/cpu.hh b/lib/nes/cpu.hh index 431cdf2..d5bc032 100644 --- a/lib/nes/cpu.hh +++ b/lib/nes/cpu.hh @@ -2,12 +2,13 @@ #include "nes/util/cycle-count.hh" #include "nes/util/address.hh" +#include "nes/util/status.hh" namespace nes { class ppu; class controller; - class mapper; + class cartridge; struct snapshot; class cpu @@ -17,7 +18,7 @@ namespace nes cycle_count current_cycles_; ppu& ppu_; - mapper& mapper_; + cartridge& cartridge_; controller& controller_1_; controller& controller_2_; std::uint8_t ram_[ram_size]{}; @@ -128,7 +129,7 @@ namespace nes }; public: - explicit cpu(ppu&, mapper&, controller& controller_1, controller& controller_2); + explicit cpu(ppu&, cartridge&, controller& controller_1, controller& controller_2); cpu(cpu const&) = delete; cpu(cpu&&) = delete; @@ -139,7 +140,7 @@ namespace nes auto build_snapshot(snapshot&) -> void; auto stall_cycles(cycle_count) -> void; auto trigger_nmi() -> void; - auto step() -> void; + auto step() -> status; auto is_nmi_pending() const -> bool { return nmi_pending_; } // XXX: Debugging // Memory access @@ -153,9 +154,9 @@ namespace nes // Instructions #define DEFINE_SIMPLE_INSTRUCTION(name) \ - auto run_##name() -> void; + auto run_##name() -> status; #define DEFINE_OPERAND_INSTRUCTION(name) \ - template auto run_##name() -> void; + template auto run_##name() -> status; DEFINE_OPERAND_INSTRUCTION(brk) DEFINE_OPERAND_INSTRUCTION(ora) diff --git a/lib/nes/mapper.cc b/lib/nes/mapper.cc index ad9ec40..40d11f8 100644 --- a/lib/nes/mapper.cc +++ b/lib/nes/mapper.cc @@ -1,134 +1,172 @@ #include "nes/mapper.hh" #include "nes/cartridge.hh" -#include namespace nes { - 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 } + namespace { - } + // ------------------------------------------------------------------------------------------------------------- + // NROM + // ------------------------------------------------------------------------------------------------------------- - auto mapper::select(cartridge& cartridge) -> mapper - { - switch (cartridge.get_mapper_number()) + class mapper_nrom final : public mapper { - 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(); - } - } + public: + explicit mapper_nrom() = default; - // ----------------------------------------------------------------------------------------------------------------- - // NROM - // ----------------------------------------------------------------------------------------------------------------- + auto validate(cartridge& cartridge) -> status override + { + if (cartridge.get_ram_length() != 0x2000) + { + return status::error_invalid_ines_data; + } - 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() - 0x8000u; - if (rel >= cartridge_.get_prg_rom_length()) { return 0x0; } + if (cartridge.get_prg_rom_length() != 0x4000 && cartridge.get_prg_rom_length() != 0x8000) + { + return status::error_invalid_ines_data; + } - 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; } - if (rel >= cartridge_.get_prg_rom_length()) { return 0x0; } + if (cartridge.get_chr_rom_length() != 0x2000) + { + return status::error_invalid_ines_data; + } - return cartridge_.get_prg_rom()[rel]; - } + return status::success; + } - return 0x0; - } + 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]; + } - 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; - } - } + return 0x0; + } - 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; } + 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; + } + } - return cartridge_.get_chr_rom()[rel]; - } - if (addr <= address{ 0x3EFF }) + 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 { - return vram[nrom_mirrored_vram_address(addr)]; - } + public: + explicit mapper_invalid() = default; - return 0x0; - } + 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 - auto mapper::nrom_write_ppu(address const addr, std::uint8_t* vram, std::uint8_t const value) -> void + // ----------------------------------------------------------------------------------------------------------------- + // Selection + // ----------------------------------------------------------------------------------------------------------------- + + auto mapper::invalid() -> mapper& { - if (addr <= address{ 0x1FFF }) - { - return; - } - if (addr <= address{ 0x3EFF }) - { - vram[nrom_mirrored_vram_address(addr)] = value; - return; - } + static auto instance = mapper_invalid{}; + return instance; } - auto mapper::nrom_mirrored_vram_address(address const addr) const -> unsigned + auto mapper::get(std::uint8_t const number) -> mapper& { - auto const rel = addr.get_absolute() % 0x1000u; - - switch (cartridge_.get_name_table_arrangement()) + switch (number) { - 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; + case 0x00: + { + static auto instance = mapper_nrom{}; + return instance; + } + default: + { + return invalid(); + } } - - return 0; } } // namespace nes \ No newline at end of file diff --git a/lib/nes/mapper.hh b/lib/nes/mapper.hh index f2873a6..7e41b23 100644 --- a/lib/nes/mapper.hh +++ b/lib/nes/mapper.hh @@ -1,6 +1,7 @@ #pragma once #include "nes/util/address.hh" +#include "nes/util/status.hh" namespace nes { @@ -8,57 +9,24 @@ namespace nes 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; + 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; - 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 + 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; - 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; + protected: + explicit mapper() = default; }; } // namespace nes \ No newline at end of file diff --git a/lib/nes/nes.cc b/lib/nes/nes.cc index 498ecd7..9a35bfa 100644 --- a/lib/nes/nes.cc +++ b/lib/nes/nes.cc @@ -1,25 +1,17 @@ #include "nes/nes.hh" -#include "nes/mapper.hh" #include "nes/util/display.hh" #include "nes/util/rgb.hh" #include "nes/util/snapshot.hh" -#include namespace nes { 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_ } + , ppu_{ cpu_, cartridge_, display_ } + , cpu_{ ppu_, cartridge_, controller_1_, controller_2_ } + , status_{ cartridge_.get_status() } { - if (!cartridge_.is_valid()) - { - std::cerr << "Cartridge not valid!" << std::endl; - std::abort(); - } - display_.clear(rgb{}); display_.switch_buffers(); display_.clear(rgb{}); @@ -32,7 +24,9 @@ namespace nes auto nes::step() -> void { - cpu_.step(); + if (get_status() != status::success) { return; } + + status_ = cpu_.step(); while (ppu_.get_cycles() < cpu_.get_cycles()) { ppu_.step(); @@ -42,7 +36,7 @@ namespace nes auto nes::step(cycle_count const delta) -> void { current_cycles_ += delta; - while (cpu_.get_cycles() < current_cycles_) + while (cpu_.get_cycles() < current_cycles_ && get_status() == status::success) { step(); } diff --git a/lib/nes/nes.hh b/lib/nes/nes.hh index 437075f..dab5ded 100644 --- a/lib/nes/nes.hh +++ b/lib/nes/nes.hh @@ -5,7 +5,6 @@ #include "nes/controller.hh" #include "nes/cpu.hh" #include "nes/ppu.hh" -#include "nes/mapper.hh" namespace nes { @@ -19,10 +18,10 @@ namespace nes display& display_; controller controller_1_; controller controller_2_; - mapper mapper_; ppu ppu_; cpu cpu_; cycle_count current_cycles_; + status status_{ status::success }; public: explicit nes(cartridge, display&); @@ -32,6 +31,7 @@ namespace nes 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_; } diff --git a/lib/nes/ppu.cc b/lib/nes/ppu.cc index 0772d80..0f2e0d6 100644 --- a/lib/nes/ppu.cc +++ b/lib/nes/ppu.cc @@ -1,6 +1,6 @@ #include "nes/ppu.hh" #include "nes/cpu.hh" -#include "nes/mapper.hh" +#include "nes/cartridge.hh" #include "nes/util/address.hh" #include "nes/util/display.hh" #include "nes/util/rgb.hh" @@ -9,9 +9,9 @@ namespace nes { - ppu::ppu(cpu& cpu, mapper& mapper, display& display) + ppu::ppu(cpu& cpu, cartridge& cartridge, display& display) : cpu_{ cpu } - , mapper_{ mapper } + , cartridge_{ cartridge } , display_{ display } { } @@ -110,7 +110,8 @@ namespace nes fetch_cycle_.palette, fetch_cycle_.bitplane_0, fetch_cycle_.bitplane_1); break; } - default: break; + default: + break; } } @@ -230,7 +231,7 @@ namespace nes { auto const height = get_sprite_height(); sprite_count_ = 0; - for (auto i = unsigned{ 0 }; i < 64; ++i) + for (auto i = unsigned{ 0 }; i < sprite_max_count; ++i) { auto const s = get_sprite(i); auto const row = static_cast(scanline_) - static_cast(s.y); @@ -298,7 +299,7 @@ namespace nes 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{ 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) }; @@ -314,7 +315,7 @@ namespace nes addr = addr % 0x4000; // PPU only has 16 KiB addresses. if (addr <= address{ 0x3EFF }) { - mapper_.write_ppu(addr, vram_, value); + cartridge_.get_mapper().write_ppu(addr, value, cartridge_, vram_); return; } if (addr <= address{ 0x3FFF }) diff --git a/lib/nes/ppu.hh b/lib/nes/ppu.hh index 8e521c5..0301ec4 100644 --- a/lib/nes/ppu.hh +++ b/lib/nes/ppu.hh @@ -6,15 +6,16 @@ namespace nes { class address; class cpu; - class mapper; + 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{ 0x100 }; + 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. @@ -95,7 +96,7 @@ namespace nes cycle_count current_cycles_; cpu& cpu_; - mapper& mapper_; + cartridge& cartridge_; display& display_; std::uint8_t vram_[vram_size]{}; std::uint8_t oam_[oam_size]{}; @@ -235,7 +236,7 @@ namespace nes unsigned sprite_count_{ 0 }; // Number of evaluated sprites in sprites_. public: - explicit ppu(cpu&, mapper&, display&); + explicit ppu(cpu&, cartridge&, display&); ppu(ppu const&) = delete; ppu(ppu&&) = delete; diff --git a/lib/nes/util/CMakeLists.txt b/lib/nes/util/CMakeLists.txt index fdbc939..6f5c112 100644 --- a/lib/nes/util/CMakeLists.txt +++ b/lib/nes/util/CMakeLists.txt @@ -7,4 +7,5 @@ target_sources( debug.hh display.hh rgb.hh - snapshot.hh) \ No newline at end of file + snapshot.hh + status.hh) \ No newline at end of file diff --git a/lib/nes/util/status.hh b/lib/nes/util/status.hh new file mode 100644 index 0000000..64f5969 --- /dev/null +++ b/lib/nes/util/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 -- cgit v1.3.1