summaryrefslogtreecommitdiff
path: root/src/nes
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-01-16 23:58:33 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-01-16 23:58:33 +0100
commit9d80b5b8a61bd8c2336f5032de8b84fea15d685f (patch)
tree2b18cd37f1adaeb91215de6b4848e71f990b5a11 /src/nes
parent5734a139d3f218972b5ebaff0e51167770352bcf (diff)
implement cpu
Diffstat (limited to 'src/nes')
-rw-r--r--src/nes/CMakeLists.txt4
-rw-r--r--src/nes/cartridge.cc16
-rw-r--r--src/nes/controller.cc9
-rw-r--r--src/nes/controller.hh5
-rw-r--r--src/nes/cpu.cc1247
-rw-r--r--src/nes/cpu.hh274
-rw-r--r--src/nes/mapper.cc61
-rw-r--r--src/nes/mapper.hh44
-rw-r--r--src/nes/nes.cc17
-rw-r--r--src/nes/nes.hh9
-rw-r--r--src/nes/ppu.cc4
-rw-r--r--src/nes/ppu.hh4
-rw-r--r--src/nes/util/CMakeLists.txt1
-rw-r--r--src/nes/util/address.hh33
-rw-r--r--src/nes/util/cycle_count.hh85
15 files changed, 1791 insertions, 22 deletions
diff --git a/src/nes/CMakeLists.txt b/src/nes/CMakeLists.txt
index 59f213d..fcdcb07 100644
--- a/src/nes/CMakeLists.txt
+++ b/src/nes/CMakeLists.txt
@@ -14,4 +14,6 @@ target_sources(
cpu.hh
cpu.cc
ppu.hh
- ppu.cc) \ No newline at end of file
+ ppu.cc
+ mapper.hh
+ mapper.cc) \ No newline at end of file
diff --git a/src/nes/cartridge.cc b/src/nes/cartridge.cc
index 5970d2a..d2d11b6 100644
--- a/src/nes/cartridge.cc
+++ b/src/nes/cartridge.cc
@@ -1,15 +1,15 @@
#include "nes/cartridge.hh"
#include <fstream>
-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
-
namespace nes
{
+ 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
{
auto const bytes = static_cast<std::uint8_t const*>(data);
@@ -31,7 +31,7 @@ namespace nes
(control_2 & 0b11110000) >> 0;
// Load program data.
- auto offset = 16;
+ auto offset = std::size_t{ 16 };
if (has_trainer) { offset += 512; }
auto const prg_rom_size = prg_rom_banks * prg_rom_bank_size;
diff --git a/src/nes/controller.cc b/src/nes/controller.cc
index f0d7728..e73f8e5 100644
--- a/src/nes/controller.cc
+++ b/src/nes/controller.cc
@@ -2,5 +2,14 @@
namespace nes
{
+ auto controller::read() -> std::uint8_t
+ {
+ // TODO
+ return 0x0;
+ }
+ auto controller::write(std::uint8_t) -> void
+ {
+ // TODO
+ }
} // namespace nes \ No newline at end of file
diff --git a/src/nes/controller.hh b/src/nes/controller.hh
index 0489b20..d0872a6 100644
--- a/src/nes/controller.hh
+++ b/src/nes/controller.hh
@@ -19,5 +19,10 @@ namespace nes
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 \ No newline at end of file
diff --git a/src/nes/cpu.cc b/src/nes/cpu.cc
index fb0f8d2..277c05d 100644
--- a/src/nes/cpu.cc
+++ b/src/nes/cpu.cc
@@ -1,6 +1,1253 @@
#include "nes/cpu.hh"
+#include "nes/controller.hh"
+#include "nes/mapper.hh"
+#include <iostream>
namespace nes
{
+ 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::step_to(cycle_count const target) -> void
+ {
+ while (current_cycles_ < target)
+ {
+ step();
+ }
+ }
+
+ auto cpu::step() -> void
+ {
+ // See https://www.nesdev.org/wiki/CPU_unofficial_opcodes
+
+ auto const opcode = advance_pc8();
+ switch (opcode)
+ {
+ case 0x00: run_brk(); break;
+ case 0x01: run_ora<addressing_mode::indexed_indirect>(); break;
+ case 0x02: run_stp(); break;
+ case 0x03: run_slo<addressing_mode::indexed_indirect>(); break;
+ case 0x04: run_nop<addressing_mode::zero_page>(); break;
+ case 0x05: run_ora<addressing_mode::zero_page>(); break;
+ case 0x06: run_asl<addressing_mode::zero_page>(); break;
+ case 0x07: run_slo<addressing_mode::zero_page>(); break;
+ case 0x08: run_php(); break;
+ case 0x09: run_ora<addressing_mode::immediate>(); break;
+ case 0x0A: run_asl<addressing_mode::accumulator>(); break;
+ case 0x0B: run_anc<addressing_mode::immediate>(); break;
+ case 0x0C: run_nop<addressing_mode::absolute>(); break;
+ case 0x0D: run_ora<addressing_mode::absolute>(); break;
+ case 0x0E: run_asl<addressing_mode::absolute>(); break;
+ case 0x0F: run_slo<addressing_mode::absolute>(); break;
+ case 0x10: run_bpl<addressing_mode::relative>(); break;
+ case 0x11: run_ora<addressing_mode::indirect_indexed>(); break;
+ case 0x12: run_stp(); break;
+ case 0x13: run_slo<addressing_mode::indirect_indexed>(); break;
+ case 0x14: run_nop<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x15: run_ora<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x16: run_asl<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x17: run_slo<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x18: run_clc(); break;
+ case 0x19: run_ora<addressing_mode::absolute_indexed_y>(); break;
+ case 0x1A: run_nop(); break;
+ case 0x1B: run_slo<addressing_mode::absolute_indexed_y>(); break;
+ case 0x1C: run_nop<addressing_mode::absolute_indexed_x>(); break;
+ case 0x1D: run_ora<addressing_mode::absolute_indexed_x>(); break;
+ case 0x1E: run_asl<addressing_mode::absolute_indexed_x>(); break;
+ case 0x1F: run_slo<addressing_mode::absolute_indexed_x>(); break;
+ case 0x20: run_jsr<addressing_mode::absolute>(); break;
+ case 0x21: run_and<addressing_mode::indexed_indirect>(); break;
+ case 0x22: run_stp(); break;
+ case 0x23: run_rla<addressing_mode::indexed_indirect>(); break;
+ case 0x24: run_bit<addressing_mode::zero_page>(); break;
+ case 0x25: run_and<addressing_mode::zero_page>(); break;
+ case 0x26: run_rol<addressing_mode::accumulator>(); break;
+ case 0x27: run_rla<addressing_mode::zero_page>(); break;
+ case 0x28: run_plp(); break;
+ case 0x29: run_and<addressing_mode::immediate>(); break;
+ case 0x2A: run_rol<addressing_mode::absolute>(); break;
+ case 0x2B: run_anc<addressing_mode::immediate>(); break;
+ case 0x2C: run_bit<addressing_mode::absolute>(); break;
+ case 0x2D: run_and<addressing_mode::absolute>(); break;
+ case 0x2E: run_rol<addressing_mode::absolute>(); break;
+ case 0x2F: run_rla<addressing_mode::absolute>(); break;
+ case 0x30: run_bmi<addressing_mode::relative>(); break;
+ case 0x31: run_and<addressing_mode::indirect_indexed>(); break;
+ case 0x32: run_stp(); break;
+ case 0x33: run_rla<addressing_mode::indirect_indexed>(); break;
+ case 0x34: run_nop<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x35: run_and<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x36: run_rol<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x37: run_rla<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x38: run_sec(); break;
+ case 0x39: run_and<addressing_mode::absolute_indexed_y>(); break;
+ case 0x3A: run_nop(); break;
+ case 0x3B: run_rla<addressing_mode::absolute_indexed_y>(); break;
+ case 0x3C: run_nop<addressing_mode::absolute_indexed_x>(); break;
+ case 0x3D: run_and<addressing_mode::absolute_indexed_x>(); break;
+ case 0x3E: run_rol<addressing_mode::absolute_indexed_x>(); break;
+ case 0x3F: run_rla<addressing_mode::absolute_indexed_x>(); break;
+ case 0x40: run_rti(); break;
+ case 0x41: run_eor<addressing_mode::indexed_indirect>(); break;
+ case 0x42: run_stp(); break;
+ case 0x43: run_sre<addressing_mode::indexed_indirect>(); break;
+ case 0x44: run_nop<addressing_mode::zero_page>(); break;
+ case 0x45: run_eor<addressing_mode::zero_page>(); break;
+ case 0x46: run_lsr<addressing_mode::zero_page>(); break;
+ case 0x47: run_sre<addressing_mode::zero_page>(); break;
+ case 0x48: run_pha(); break;
+ case 0x49: run_eor<addressing_mode::immediate>(); break;
+ case 0x4A: run_lsr<addressing_mode::accumulator>(); break;
+ case 0x4B: run_alr<addressing_mode::immediate>(); break;
+ case 0x4C: run_jmp<addressing_mode::absolute>(); break;
+ case 0x4D: run_eor<addressing_mode::absolute>(); break;
+ case 0x4E: run_lsr<addressing_mode::absolute>(); break;
+ case 0x4F: run_sre<addressing_mode::absolute>(); break;
+ case 0x50: run_bvc<addressing_mode::relative>(); break;
+ case 0x51: run_eor<addressing_mode::indirect_indexed>(); break;
+ case 0x52: run_stp(); break;
+ case 0x53: run_sre<addressing_mode::indirect_indexed>(); break;
+ case 0x54: run_nop<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x55: run_eor<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x56: run_lsr<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x57: run_sre<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x58: run_cli(); break;
+ case 0x59: run_eor<addressing_mode::absolute_indexed_y>(); break;
+ case 0x5A: run_nop(); break;
+ case 0x5B: run_sre<addressing_mode::absolute_indexed_y>(); break;
+ case 0x5C: run_nop<addressing_mode::absolute_indexed_x>(); break;
+ case 0x5D: run_eor<addressing_mode::absolute_indexed_x>(); break;
+ case 0x5E: run_lsr<addressing_mode::absolute_indexed_x>(); break;
+ case 0x5F: run_sre<addressing_mode::absolute_indexed_x>(); break;
+ case 0x60: run_rts(); break;
+ case 0x61: run_adc<addressing_mode::indexed_indirect>(); break;
+ case 0x62: run_stp(); break;
+ case 0x63: run_rra<addressing_mode::indexed_indirect>(); break;
+ case 0x64: run_nop<addressing_mode::zero_page>(); break;
+ case 0x65: run_adc<addressing_mode::zero_page>(); break;
+ case 0x66: run_ror<addressing_mode::zero_page>(); break;
+ case 0x67: run_rra<addressing_mode::zero_page>(); break;
+ case 0x68: run_pla(); break;
+ case 0x69: run_adc<addressing_mode::immediate>(); break;
+ case 0x6A: run_ror<addressing_mode::accumulator>(); break;
+ case 0x6B: run_arr<addressing_mode::immediate>(); break;
+ case 0x6C: run_jmp<addressing_mode::indirect>(); break;
+ case 0x6D: run_adc<addressing_mode::absolute>(); break;
+ case 0x6E: run_ror<addressing_mode::absolute>(); break;
+ case 0x6F: run_rra<addressing_mode::absolute>(); break;
+ case 0x70: run_bvs<addressing_mode::relative>(); break;
+ case 0x71: run_adc<addressing_mode::indirect_indexed>(); break;
+ case 0x72: run_stp(); break;
+ case 0x73: run_rra<addressing_mode::indirect_indexed>(); break;
+ case 0x74: run_nop<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x75: run_adc<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x76: run_ror<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x77: run_rra<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x78: run_sei(); break;
+ case 0x79: run_adc<addressing_mode::absolute_indexed_y>(); break;
+ case 0x7A: run_nop(); break;
+ case 0x7B: run_rra<addressing_mode::absolute_indexed_y>(); break;
+ case 0x7C: run_nop<addressing_mode::absolute_indexed_x>(); break;
+ case 0x7D: run_adc<addressing_mode::absolute_indexed_x>(); break;
+ case 0x7E: run_ror<addressing_mode::absolute_indexed_x>(); break;
+ case 0x7F: run_rra<addressing_mode::absolute_indexed_x>(); break;
+ case 0x80: run_nop<addressing_mode::immediate>(); break;
+ case 0x81: run_sta<addressing_mode::indexed_indirect>(); break;
+ case 0x82: run_nop<addressing_mode::immediate>(); break;
+ case 0x83: run_sax<addressing_mode::indexed_indirect>(); break;
+ case 0x84: run_sty<addressing_mode::zero_page>(); break;
+ case 0x85: run_sta<addressing_mode::zero_page>(); break;
+ case 0x86: run_stx<addressing_mode::zero_page>(); break;
+ case 0x87: run_sax<addressing_mode::zero_page>(); break;
+ case 0x88: run_dey(); break;
+ case 0x89: run_nop<addressing_mode::immediate>(); break;
+ case 0x8A: run_txa(); break;
+ case 0x8B: run_xaa<addressing_mode::immediate>(); break;
+ case 0x8C: run_sty<addressing_mode::absolute>(); break;
+ case 0x8D: run_sta<addressing_mode::absolute>(); break;
+ case 0x8E: run_stx<addressing_mode::absolute>(); break;
+ case 0x8F: run_sax<addressing_mode::absolute>(); break;
+ case 0x90: run_bcc<addressing_mode::relative>(); break;
+ case 0x91: run_sta<addressing_mode::indirect_indexed>(); break;
+ case 0x92: run_stp(); break;
+ case 0x93: run_ahx<addressing_mode::indirect_indexed>(); break;
+ case 0x94: run_sty<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x95: run_sta<addressing_mode::zero_page_indexed_x>(); break;
+ case 0x96: run_stx<addressing_mode::zero_page_indexed_y>(); break;
+ case 0x97: run_sax<addressing_mode::zero_page_indexed_y>(); break;
+ case 0x98: run_tya(); break;
+ case 0x99: run_sta<addressing_mode::absolute_indexed_y>(); break;
+ case 0x9A: run_txs(); break;
+ case 0x9B: run_tas<addressing_mode::absolute_indexed_y>(); break;
+ case 0x9C: run_shy<addressing_mode::absolute_indexed_x>(); break;
+ case 0x9D: run_sta<addressing_mode::absolute_indexed_x>(); break;
+ case 0x9E: run_shx<addressing_mode::absolute_indexed_y>(); break;
+ case 0x9F: run_ahx<addressing_mode::absolute_indexed_y>(); break;
+ case 0xA0: run_ldy<addressing_mode::immediate>(); break;
+ case 0xA1: run_lda<addressing_mode::indexed_indirect>(); break;
+ case 0xA2: run_ldx<addressing_mode::immediate>(); break;
+ case 0xA3: run_lax<addressing_mode::indexed_indirect>(); break;
+ case 0xA4: run_ldy<addressing_mode::zero_page>(); break;
+ case 0xA5: run_lda<addressing_mode::zero_page>(); break;
+ case 0xA6: run_ldx<addressing_mode::zero_page>(); break;
+ case 0xA7: run_lax<addressing_mode::zero_page>(); break;
+ case 0xA8: run_tay(); break;
+ case 0xA9: run_lda<addressing_mode::immediate>(); break;
+ case 0xAA: run_tax(); break;
+ case 0xAB: run_lax<addressing_mode::immediate>(); break;
+ case 0xAC: run_ldy<addressing_mode::absolute>(); break;
+ case 0xAD: run_lda<addressing_mode::absolute>(); break;
+ case 0xAE: run_ldx<addressing_mode::absolute>(); break;
+ case 0xAF: run_lax<addressing_mode::absolute>(); break;
+ case 0xB0: run_bcs<addressing_mode::relative>(); break;
+ case 0xB1: run_lda<addressing_mode::indirect_indexed>(); break;
+ case 0xB2: run_stp(); break;
+ case 0xB3: run_lax<addressing_mode::indirect_indexed>(); break;
+ case 0xB4: run_ldy<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xB5: run_lda<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xB6: run_ldx<addressing_mode::zero_page_indexed_y>(); break;
+ case 0xB7: run_lax<addressing_mode::zero_page_indexed_y>(); break;
+ case 0xB8: run_clv(); break;
+ case 0xB9: run_lda<addressing_mode::absolute_indexed_y>(); break;
+ case 0xBA: run_tsx(); break;
+ case 0xBB: run_las<addressing_mode::absolute_indexed_y>(); break;
+ case 0xBC: run_ldy<addressing_mode::absolute_indexed_x>(); break;
+ case 0xBD: run_lda<addressing_mode::absolute_indexed_x>(); break;
+ case 0xBE: run_ldx<addressing_mode::absolute_indexed_y>(); break;
+ case 0xBF: run_lax<addressing_mode::absolute_indexed_y>(); break;
+ case 0xC0: run_cpy<addressing_mode::immediate>(); break;
+ case 0xC1: run_cmp<addressing_mode::indexed_indirect>(); break;
+ case 0xC2: run_nop<addressing_mode::immediate>(); break;
+ case 0xC3: run_dcp<addressing_mode::indexed_indirect>(); break;
+ case 0xC4: run_cpy<addressing_mode::zero_page>(); break;
+ case 0xC5: run_cmp<addressing_mode::zero_page>(); break;
+ case 0xC6: run_dec<addressing_mode::zero_page>(); break;
+ case 0xC7: run_dcp<addressing_mode::zero_page>(); break;
+ case 0xC8: run_iny(); break;
+ case 0xC9: run_cmp<addressing_mode::immediate>(); break;
+ case 0xCA: run_dex(); break;
+ case 0xCB: run_axs<addressing_mode::immediate>(); break;
+ case 0xCC: run_cpy<addressing_mode::absolute>(); break;
+ case 0xCD: run_cmp<addressing_mode::absolute>(); break;
+ case 0xCE: run_dec<addressing_mode::absolute>(); break;
+ case 0xCF: run_dcp<addressing_mode::absolute>(); break;
+ case 0xD0: run_bne<addressing_mode::relative>(); break;
+ case 0xD1: run_cmp<addressing_mode::indirect_indexed>(); break;
+ case 0xD2: run_stp(); break;
+ case 0xD3: run_dcp<addressing_mode::indirect_indexed>(); break;
+ case 0xD4: run_nop<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xD5: run_cmp<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xD6: run_dec<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xD7: run_dcp<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xD8: run_cld(); break;
+ case 0xD9: run_cmp<addressing_mode::absolute_indexed_y>(); break;
+ case 0xDA: run_nop(); break;
+ case 0xDB: run_dcp<addressing_mode::absolute_indexed_y>(); break;
+ case 0xDC: run_nop<addressing_mode::absolute_indexed_x>(); break;
+ case 0xDD: run_cmp<addressing_mode::absolute_indexed_x>(); break;
+ case 0xDE: run_dec<addressing_mode::absolute_indexed_x>(); break;
+ case 0xDF: run_dcp<addressing_mode::absolute_indexed_x>(); break;
+ case 0xE0: run_cpx<addressing_mode::immediate>(); break;
+ case 0xE1: run_sbc<addressing_mode::indexed_indirect>(); break;
+ case 0xE2: run_nop<addressing_mode::immediate>(); break;
+ case 0xE3: run_isc<addressing_mode::indexed_indirect>(); break;
+ case 0xE4: run_cpx<addressing_mode::zero_page>(); break;
+ case 0xE5: run_sbc<addressing_mode::zero_page>(); break;
+ case 0xE6: run_inc<addressing_mode::zero_page>(); break;
+ case 0xE7: run_isc<addressing_mode::zero_page>(); break;
+ case 0xE8: run_inx(); break;
+ case 0xE9: run_sbc<addressing_mode::immediate>(); break;
+ case 0xEA: run_nop(); break;
+ case 0xEB: run_sbc<addressing_mode::immediate>(); break;
+ case 0xEC: run_cpx<addressing_mode::absolute>(); break;
+ case 0xED: run_sbc<addressing_mode::absolute>(); break;
+ case 0xEE: run_inc<addressing_mode::absolute>(); break;
+ case 0xEF: run_isc<addressing_mode::absolute>(); break;
+ case 0xF0: run_beq<addressing_mode::relative>(); break;
+ case 0xF1: run_sbc<addressing_mode::indirect_indexed>(); break;
+ case 0xF2: run_stp(); break;
+ case 0xF3: run_isc<addressing_mode::indirect_indexed>(); break;
+ case 0xF4: run_nop<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xF5: run_sbc<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xF6: run_inc<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xF7: run_isc<addressing_mode::zero_page_indexed_x>(); break;
+ case 0xF8: run_sed(); break;
+ case 0xF9: run_sbc<addressing_mode::absolute_indexed_y>(); break;
+ case 0xFA: run_nop(); break;
+ case 0xFB: run_isc<addressing_mode::absolute_indexed_y>(); break;
+ case 0xFC: run_nop<addressing_mode::absolute_indexed_x>(); break;
+ case 0xFD: run_sbc<addressing_mode::absolute_indexed_x>(); break;
+ case 0xFE: run_inc<addressing_mode::absolute_indexed_x>(); break;
+ case 0xFF: run_isc<addressing_mode::absolute_indexed_x>(); break;
+ default: std::abort();
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Instructions
+ // -----------------------------------------------------------------------------------------------------------------
+
+ auto cpu::run_brk() -> void
+ {
+ // TODO: Proper BRK handling
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_ora() -> void
+ {
+ // ORA: Logical Inclusive OR
+ auto operand = fetch_operand<Mode>();
+ eval_ora(operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ auto cpu::run_stp() -> void
+ {
+ // TODO: Proper STP handling
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_slo() -> void
+ {
+ // SLO: ASL + ORA
+ auto operand = fetch_operand<Mode>(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<cpu::addressing_mode Mode>
+ auto cpu::run_nop() -> void
+ {
+ // NOP: NOP
+ auto operand = fetch_operand<Mode>();
+ 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
+ // Always set bits 4 and 5.
+ push_stack8(registers_.p | 0b00110000);
+ current_cycles_ += cycle_count::from_cpu(3);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_asl() -> void
+ {
+ // ASL: Arithmetic Shift Left
+ auto operand = fetch_operand<Mode>();
+ eval_asl(operand.read());
+ current_cycles_ += shift_cycle_count<Mode>();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_anc() -> void
+ {
+ // ANC: AND #i + copy N to C
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bpl() -> void
+ {
+ // BPL: Break If Positive
+ branch<Mode>(!registers_.n);
+ }
+
+ auto cpu::run_clc() -> void
+ {
+ // CLC: Clear Carry Flag
+ registers_.c = false;
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ 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<Mode>();
+ registers_.pc = operand.get_address().get_absolute();
+ current_cycles_ += cycle_count::from_cpu(6);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_and() -> void
+ {
+ // AND: Logical AND
+ auto operand = fetch_operand<Mode>();
+ eval_and(operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_rla() -> void
+ {
+ // RLA: ROL + AND
+ auto operand = fetch_operand<Mode>(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<cpu::addressing_mode Mode>
+ auto cpu::run_bit() -> void
+ {
+ // BIT: Bit Test
+ auto operand = fetch_operand<Mode>();
+ auto const arg = operand.read();
+ auto const res = (registers_.a & arg) != 0;
+ registers_.z = !res;
+ registers_.v = (arg & 0x40) != 0;
+ registers_.n = (arg & 0x80) != 0;
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_rol() -> void
+ {
+ // ROR: Rotate Left
+ auto operand = fetch_operand<Mode>();
+ auto const result = eval_rol(operand.read());
+ operand.write(result);
+ current_cycles_ += shift_cycle_count<Mode>();
+ }
+
+ auto cpu::run_plp() -> void
+ {
+ // PLP: Pull Processor Status
+ eval_plp();
+ current_cycles_ += cycle_count::from_cpu(4);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bmi() -> void
+ {
+ branch<Mode>(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<cpu::addressing_mode Mode>
+ auto cpu::run_eor() -> void
+ {
+ // EOR: Exclusive OR
+ auto operand = fetch_operand<Mode>();
+ eval_eor(operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_sre() -> void
+ {
+ // SRE: LSR + EOR
+ auto operand = fetch_operand<Mode>(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<cpu::addressing_mode Mode>
+ auto cpu::run_lsr() -> void
+ {
+ // LSR: Logical Shift Right
+ auto operand = fetch_operand<Mode>();
+ auto const old_val = operand.read();
+ auto const new_val = static_cast<std::uint8_t>(old_val >> 1);
+ operand.write(new_val);
+ registers_.c = (old_val & 0x1) != 0;
+ update_alu_flags(new_val);
+ current_cycles_ += shift_cycle_count<Mode>();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_alr() -> void
+ {
+ // ALR: AND #i + LSR A
+ // TODO
+ std::abort();
+ }
+
+ auto cpu::run_pha() -> void
+ {
+ // PHA: Push Accumulator
+ push_stack8(registers_.a);
+ current_cycles_ += cycle_count::from_cpu(3);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_jmp() -> void
+ {
+ // JMP: Jump
+ auto operand = fetch_operand<Mode>();
+ registers_.pc = operand.get_address().get_absolute();
+ current_cycles_ += jump_cycle_count<Mode>();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bvc() -> void
+ {
+ // BVC: Branch If Overflow Clear
+ branch<Mode>(!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<cpu::addressing_mode Mode>
+ auto cpu::run_adc() -> void
+ {
+ // ADC: Add With Carry
+ auto operand = fetch_operand<Mode>();
+ eval_adc(operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_rra() -> void
+ {
+ // RRA: ROR + ADC
+ auto operand = fetch_operand<Mode>(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<cpu::addressing_mode Mode>
+ auto cpu::run_ror() -> void
+ {
+ // ROR: Rotate Right
+ auto operand = fetch_operand<Mode>();
+ auto const result = eval_ror(operand.read());
+ operand.write(result);
+ current_cycles_ += shift_cycle_count<Mode>();
+ }
+
+ template<cpu::addressing_mode Mode>
+ 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_alu_flags(registers_.a);
+ current_cycles_ += cycle_count::from_cpu(4);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bvs() -> void
+ {
+ // BVS: Branch If Overflow Set
+ branch<Mode>(registers_.v);
+ }
+
+ auto cpu::run_sei() -> void
+ {
+ // SEI: Set Interrupt Disable
+ registers_.i = true;
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_sta() -> void
+ {
+ // STA: Store Accumulator
+ auto operand = fetch_operand<Mode>(force_page_crossing::yes);
+ operand.write(registers_.a);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_ahx() -> void
+ {
+ // AHX: AND A X H
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_sax() -> void
+ {
+ // SAX: AND A X
+ auto operand = fetch_operand<Mode>();
+ auto const result = static_cast<std::uint8_t>(registers_.a & registers_.x);
+ operand.write(result);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_sty() -> void
+ {
+ // STX: Store Y
+ auto operand = fetch_operand<Mode>();
+ operand.write(registers_.y);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_stx() -> void
+ {
+ // STX: Store X
+ auto operand = fetch_operand<Mode>();
+ operand.write(registers_.x);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ auto cpu::run_dey() -> void
+ {
+ // DEY: Decrement Y Register
+ registers_.y -= 1;
+ update_alu_flags(registers_.y);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ auto cpu::run_txa() -> void
+ {
+ // TXA: Transfer X to Accumulator
+ registers_.a = registers_.x;
+ update_alu_flags(registers_.a);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_xaa() -> void
+ {
+ // XAA: Depends on analog behavior.
+ run_brk();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bcc() -> void
+ {
+ // BCC: Branch If Carry Clear
+ branch<Mode>(!registers_.c);
+ }
+
+ auto cpu::run_tya() -> void
+ {
+ // TYA: Transfer Y to Accumulator
+ registers_.a = registers_.y;
+ update_alu_flags(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<cpu::addressing_mode Mode>
+ auto cpu::run_tas() -> void
+ {
+ // TAS: S = A & X + AND S H
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_shy() -> void
+ {
+ // SHY: AND Y H
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_shx() -> void
+ {
+ // SHY: AND X H
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_ldy() -> void
+ {
+ // LDY: Load Y Register
+ auto operand = fetch_operand<Mode>();
+ registers_.y = operand.read();
+ update_alu_flags(registers_.y);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_lda() -> void
+ {
+ // LDA: Load Accumulator
+ auto operand = fetch_operand<Mode>();
+ registers_.a = operand.read();
+ update_alu_flags(registers_.a);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_ldx() -> void
+ {
+ // LDX: Load X Register
+ auto operand = fetch_operand<Mode>();
+ registers_.x = operand.read();
+ update_alu_flags(registers_.x);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_lax() -> void
+ {
+ // LAX: LDA + TAX
+ auto operand = fetch_operand<Mode>();
+ registers_.x = registers_.a = operand.read();
+ update_alu_flags(registers_.a);
+ current_cycles_ += operand.get_cycles();
+ }
+
+ auto cpu::run_tay() -> void
+ {
+ // TAY: Transfer Accumulator to Y
+ registers_.y = registers_.a;
+ update_alu_flags(registers_.y);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ auto cpu::run_tax() -> void
+ {
+ // TAX: Transfer Accumulator to X
+ registers_.x = registers_.a;
+ update_alu_flags(registers_.x);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bcs() -> void
+ {
+ // BCS: Branch If Carry Set
+ branch<Mode>(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_alu_flags(registers_.x);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_las() -> void
+ {
+ // LAS: AND S, store into A, X, S
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_cpy() -> void
+ {
+ // CPY: Compare Y Register
+ auto operand = fetch_operand<Mode>();
+ eval_cmp(registers_.y, operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_axs() -> void
+ {
+ // AXS: Set X to ((A AND X) - #value without borrow), updates NZC
+ // TODO
+ std::abort();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_cmp() -> void
+ {
+ // CMP: Compare
+ auto operand = fetch_operand<Mode>();
+ eval_cmp(registers_.a, operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_dcp() -> void
+ {
+ // DCP: DEC + CMP
+ auto operand = fetch_operand<Mode>(force_page_crossing::yes);
+ auto const tmp = static_cast<std::uint8_t>(operand.read() - 1);
+ operand.write(tmp);
+ eval_cmp(registers_.a, tmp);
+ current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_dec() -> void
+ {
+ // DEC: Decrement Memory
+ auto operand = fetch_operand<Mode>();
+ auto const old_val = read8(operand.get_address());
+ auto const new_val = static_cast<std::uint8_t>(old_val - 1);
+ write8(operand.get_address(), new_val);
+ update_alu_flags(new_val);
+ current_cycles_ += inc_dec_cycle_count<Mode>();
+ }
+
+ auto cpu::run_iny() -> void
+ {
+ // INY: Increment Y
+ registers_.y += 1;
+ update_alu_flags(registers_.y);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ auto cpu::run_dex() -> void
+ {
+ // DEX: Decrement X Register
+ registers_.x -= 1;
+ update_alu_flags(registers_.x);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_bne() -> void
+ {
+ // BNE: Branch If Not Equal
+ branch<Mode>(!registers_.z);
+ }
+
+ auto cpu::run_cld() -> void
+ {
+ // CLD: Clear Decimal Mode
+ registers_.d = false;
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_cpx() -> void
+ {
+ // CPX: Compare X Register
+ auto operand = fetch_operand<Mode>();
+ eval_cmp(registers_.x, operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_sbc() -> void
+ {
+ // SBC: Subtract With Carry
+ auto operand = fetch_operand<Mode>();
+ eval_adc(~operand.read());
+ current_cycles_ += operand.get_cycles();
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_isc() -> void
+ {
+ // ISC: INC + SBC
+ auto operand = fetch_operand<Mode>(force_page_crossing::yes);
+ auto const tmp = static_cast<std::uint8_t>(operand.read() + 1);
+ operand.write(tmp);
+ eval_adc(~tmp);
+ current_cycles_ += operand.get_cycles() + cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_inc() -> void
+ {
+ // INC: Increment Memory
+ auto operand = fetch_operand<Mode>();
+ auto const old_val = read8(operand.get_address());
+ auto const new_val = static_cast<std::uint8_t>(old_val + 1);
+ write8(operand.get_address(), new_val);
+ update_alu_flags(new_val);
+ current_cycles_ += inc_dec_cycle_count<Mode>();
+ }
+
+ auto cpu::run_inx() -> void
+ {
+ // INX: Increment X
+ registers_.x += 1;
+ update_alu_flags(registers_.x);
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::run_beq() -> void
+ {
+ // BEQ: Branch If Equal
+ branch<Mode>(registers_.z);
+ }
+
+ auto cpu::run_sed() -> void
+ {
+ // SED: Set Decimal Flag
+ registers_.d = true;
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Operands
+ // -----------------------------------------------------------------------------------------------------------------
+
+ //
+ // See: https://www.nesdev.org/wiki/CPU_addressing_modes
+ //
+
+ 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<std::uint8_t>(value >> 0 & 0xFF);
+ auto const high = static_cast<std::uint8_t>(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<std::uint16_t>((high << 8) | (low << 0));
+ }
+
+ auto cpu::update_alu_flags(std::uint8_t const value) -> void
+ {
+ registers_.z = value == 0;
+ registers_.n = (value & 0x80) != 0;
+ }
+
+ template<cpu::addressing_mode Mode>
+ 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<Mode>();
+ if (condition)
+ {
+ registers_.pc = operand.get_address().get_absolute();
+ current_cycles_ += operand.get_cycles();
+ }
+ else
+ {
+ current_cycles_ += cycle_count::from_cpu(2);
+ }
+ }
+
+ auto cpu::eval_ror(std::uint8_t const arg) -> std::uint8_t
+ {
+ auto const res = static_cast<std::uint8_t>((registers_.c << 7) | (arg >> 1));
+ registers_.c = (arg & 0x1) != 0;
+ update_alu_flags(res);
+ return res;
+ }
+
+ auto cpu::eval_rol(std::uint8_t const arg) -> std::uint8_t
+ {
+ auto const res = static_cast<std::uint8_t>((arg << 1) | (registers_.c ? 1 : 0));
+ registers_.c = (arg & 0x80) != 0;
+ update_alu_flags(res);
+ return res;
+ }
+
+ auto cpu::eval_asl(std::uint8_t const arg) -> std::uint8_t
+ {
+ auto const new_val = static_cast<std::uint8_t>(arg << 1);
+ registers_.c = (arg & 0x80) != 0;
+ update_alu_flags(new_val);
+ return new_val;
+ }
+
+ auto cpu::eval_lsr(std::uint8_t const arg) -> std::uint8_t
+ {
+ auto const new_val = static_cast<std::uint8_t>(arg >> 1);
+ registers_.c = (arg & 0x01) != 0;
+ update_alu_flags(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<std::uint8_t>(tmp);
+
+ registers_.a = new_val;
+ registers_.c = tmp > 0xFF;
+ registers_.v = ((old_val ^ arg) & 0x80) == 0 && ((old_val ^ new_val) & 0x80) == 0;
+ update_alu_flags(registers_.a);
+ }
+
+ auto cpu::eval_and(std::uint8_t const arg) -> void
+ {
+ registers_.a &= arg;
+ update_alu_flags(registers_.a);
+ }
+
+ auto cpu::eval_ora(std::uint8_t const arg) -> void
+ {
+ registers_.a |= arg;
+ update_alu_flags(registers_.a);
+ }
+
+ auto cpu::eval_eor(std::uint8_t const arg) -> void
+ {
+ registers_.a ^= arg;
+ update_alu_flags(registers_.a);
+ }
+
+ auto cpu::eval_cmp(std::uint8_t const a, std::uint8_t const b) -> void
+ {
+ update_alu_flags(a - b);
+ registers_.c = a >= b;
+ }
+
+ auto cpu::eval_plp() -> void
+ {
+ // Ignore bits 4 and 5
+ registers_.p =
+ (pop_stack8() & 0b11001111) |
+ (registers_.p & 0b00110000);
+ }
+
+ template<cpu::addressing_mode Mode>
+ auto cpu::fetch_operand(force_page_crossing const force_page_crossing) -> operand<Mode>
+ {
+ auto addr = address{};
+ auto cycles = cycle_count{};
+
+ switch (Mode)
+ {
+ 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<std::int8_t>(advance_pc8());
+ auto const base = address{ registers_.pc };
+ addr = address{ static_cast<std::uint16_t>(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<std::uint8_t>(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<std::uint8_t>(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<std::uint8_t>(arg + registers_.x + 1) });
+ auto const offset = read8(address{ 0x00, static_cast<std::uint8_t>(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<std::uint8_t>(arg + 1) });
+ auto const offset = read8(address{ 0x00, static_cast<std::uint8_t>(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<Mode>{ *this, addr, cycles };
+ }
+
+ template<>
+ auto cpu::fetch_operand<cpu::addressing_mode::accumulator>(force_page_crossing) -> operand<addressing_mode::accumulator>
+ {
+ return operand<addressing_mode::accumulator>{ *this };
+ }
+
+ template<>
+ auto cpu::fetch_operand<cpu::addressing_mode::immediate>(force_page_crossing) -> operand<addressing_mode::immediate>
+ {
+ return operand<addressing_mode::immediate>{ *this, 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 }) { return 0x0; } // TODO: PPU registers
+ if (addr <= address{ 0x4013 }) { return 0x0; } // TODO: APU registers
+ if (addr == address{ 0x4014 }) { return 0x0; } // TODO: PPU registers
+ 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(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<std::uint16_t>((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 }) { return; } // TODO: PPU registers
+ if (addr <= address{ 0x4013 }) { return; } // TODO: APU registers
+ if (addr == address{ 0x4014 }) { return; } // TODO: PPU registers
+ 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(addr, value);
+ }
+
+ auto cpu::write16(address const addr, std::uint16_t const value) -> void
+ {
+ auto const low = static_cast<std::uint8_t>(value >> 0 & 0xFF);
+ auto const high = static_cast<std::uint8_t>(value >> 8 & 0xFF);
+ write8(addr + 0, low);
+ write8(addr + 1, high);
+ }
} // namespace nes \ No newline at end of file
diff --git a/src/nes/cpu.hh b/src/nes/cpu.hh
index 972b307..59f2db4 100644
--- a/src/nes/cpu.hh
+++ b/src/nes/cpu.hh
@@ -1,15 +1,287 @@
#pragma once
+#include "nes/util/cycle_count.hh"
+#include "nes/util/address.hh"
+
namespace nes
{
+ class ppu;
+ class controller;
+ class mapper;
+
class cpu
{
+ static constexpr auto ram_size = std::size_t{ 0x800 };
+ static constexpr auto stack_offset = address{ 0x100 };
+
+ 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<addressing_mode>
+ 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<addressing_mode::accumulator>
+ {
+ 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<addressing_mode::immediate>
+ {
+ cpu& cpu_;
+ std::uint8_t value_{ 0 };
+
+ public:
+ explicit operand(cpu& cpu, std::uint8_t const value)
+ : cpu_{ cpu }
+ , value_{ value }
+ {
+ }
+
+ auto read() -> std::uint8_t { return value_; }
+ auto get_cycles() -> cycle_count { return cycle_count::from_cpu(2); }
+ };
+
+ cycle_count current_cycles_;
+ ppu& ppu_;
+ mapper& mapper_;
+ controller& controller_1_;
+ controller& controller_2_;
+ std::uint8_t ram_[ram_size]{};
+
+ // 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_{};
+
public:
- explicit cpu() = default;
+ explicit cpu(ppu& ppu, mapper& 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 step_to(cycle_count) -> void;
+
+ private:
+ auto step() -> void;
+
+ // Instructions
+
+#define DEFINE_SIMPLE_INSTRUCTION(name) \
+ auto run_##name() -> void;
+#define DEFINE_OPERAND_INSTRUCTION(name) \
+ template<addressing_mode Mode> auto run_##name() -> void;
+
+ DEFINE_SIMPLE_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
+
+ // Operands
+
+ 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_alu_flags(std::uint8_t value) -> void;
+ template<addressing_mode Mode>
+ auto branch(bool condition) -> 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;
+
+#define DEFINE_CYCLE_COUNT(name) \
+ template<addressing_mode Mode> static auto name##_cycle_count() -> cycle_count;
+#define DEFINE_CYCLE_COUNT_INSTANCE(name, mode, value) \
+ template<> auto name##_cycle_count<addressing_mode::mode>() -> cycle_count { return cycle_count::from_cpu(value); }
+
+ DEFINE_CYCLE_COUNT(jump)
+ DEFINE_CYCLE_COUNT_INSTANCE(jump, absolute, 2)
+ DEFINE_CYCLE_COUNT_INSTANCE(jump, indirect, 2)
+
+ 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<addressing_mode Mode>
+ auto fetch_operand(force_page_crossing = force_page_crossing::no) -> operand<Mode>;
+ template<>
+ auto fetch_operand<addressing_mode::accumulator>(force_page_crossing) -> operand<addressing_mode::accumulator>;
+ template<>
+ auto fetch_operand<addressing_mode::immediate>(force_page_crossing) -> operand<addressing_mode::immediate>;
+
+ // 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;
};
} // namespace nes \ No newline at end of file
diff --git a/src/nes/mapper.cc b/src/nes/mapper.cc
new file mode 100644
index 0000000..a540dbf
--- /dev/null
+++ b/src/nes/mapper.cc
@@ -0,0 +1,61 @@
+#include "nes/mapper.hh"
+#include "nes/cartridge.hh"
+#include <iostream>
+
+namespace nes
+{
+ mapper::mapper(cartridge& cartridge, read_func const read, write_func const write)
+ : cartridge_{ cartridge }
+ , read_{ read }
+ , write_{ write }
+ {
+ }
+
+ auto mapper::select(cartridge& cartridge) -> mapper
+ {
+ switch (cartridge.get_mapper_number())
+ {
+ case 0x00:
+ return mapper{ cartridge, &mapper::nrom_read, &mapper::nrom_write };
+ default:
+ std::cerr << "Mapper not implemented: " << std::hex << cartridge.get_mapper_number() << std::endl;
+ std::abort();
+ }
+ }
+
+ // NROM
+
+ auto mapper::nrom_read(address const addr) -> std::uint8_t
+ {
+ if (addr <= address{ 0x7FFF })
+ {
+ return cartridge_.get_ram()[addr.get_absolute() % 0x2000];
+ }
+ 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(address const addr, std::uint8_t const value) -> void
+ {
+ if (addr <= address{ 0x7FFF })
+ {
+ cartridge_.get_ram_mut()[addr.get_absolute() % 0x2000] = value;
+ }
+ }
+
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/mapper.hh b/src/nes/mapper.hh
new file mode 100644
index 0000000..0aec2ac
--- /dev/null
+++ b/src/nes/mapper.hh
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "nes/util/address.hh"
+
+namespace nes
+{
+ class cartridge;
+
+ class mapper
+ {
+ using read_func = std::uint8_t(mapper::*)(address);
+ using write_func = void(mapper::*)(address, std::uint8_t);
+
+ cartridge& cartridge_;
+ read_func read_;
+ write_func write_;
+
+ 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(address const addr) -> std::uint8_t
+ {
+ return (this->*read_)(addr);
+ }
+
+ auto write(address const addr, std::uint8_t const value) -> void
+ {
+ (this->*write_)(addr, value);
+ }
+
+ private:
+ explicit mapper(cartridge& cartridge, read_func read, write_func write);
+
+ // Implementations
+
+ auto nrom_read(address) -> std::uint8_t;
+ auto nrom_write(address, std::uint8_t) -> void;
+ };
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/nes.cc b/src/nes/nes.cc
index 2a44ced..0579735 100644
--- a/src/nes/nes.cc
+++ b/src/nes/nes.cc
@@ -1,4 +1,5 @@
#include "nes/nes.hh"
+#include "nes/mapper.hh"
#include "nes/display.hh"
#include "nes/util/rgb.hh"
#include <iostream>
@@ -8,6 +9,8 @@ namespace nes
nes::nes(cartridge cartridge, display& display)
: cartridge_{ std::move(cartridge) }
, display_{ display }
+ , mapper_{ mapper::select(cartridge_) }
+ , cpu_{ ppu_, mapper_, controller_1_, controller_2_ }
{
if (!cartridge_.is_valid())
{
@@ -15,12 +18,18 @@ namespace nes
std::abort();
}
- display.clear(rgb::from_hex(0xFF0000));
- display.switch_buffers();
+ display_.clear(rgb{ animation_progress_, animation_progress_, animation_progress_ });
+ display_.switch_buffers();
}
- auto nes::step(std::uint64_t delta_ms) -> void
+ auto nes::step(std::chrono::microseconds const delta) -> void
{
- std::cout << "Step: " << delta_ms << "ms\n";
+ current_cycles_ += cycle_count::from_duration(delta);
+ cpu_.step_to(current_cycles_);
+ ppu_.step_to(current_cycles_);
+
+ animation_progress_ -= 2;
+ display_.clear(rgb{ animation_progress_, animation_progress_, animation_progress_ });
+ display_.switch_buffers();
}
} // namespace nes \ No newline at end of file
diff --git a/src/nes/nes.hh b/src/nes/nes.hh
index d3baf2a..be50bbb 100644
--- a/src/nes/nes.hh
+++ b/src/nes/nes.hh
@@ -1,9 +1,11 @@
#pragma once
+#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"
namespace nes
{
@@ -16,8 +18,11 @@ namespace nes
display& display_;
controller controller_1_;
controller controller_2_;
- cpu cpu_;
+ mapper mapper_;
ppu ppu_;
+ cpu cpu_;
+ cycle_count current_cycles_;
+ std::uint8_t animation_progress_{ 255 };
public:
explicit nes(cartridge, display&);
@@ -32,6 +37,6 @@ namespace nes
auto get_controller_2() const -> controller const& { return controller_2_; }
auto get_controller_2() -> controller& { return controller_2_; }
- auto step(std::uint64_t delta_ms) -> void;
+ auto step(std::chrono::microseconds delta) -> void;
};
} // namespace nes \ No newline at end of file
diff --git a/src/nes/ppu.cc b/src/nes/ppu.cc
index 8ec6243..c2ec439 100644
--- a/src/nes/ppu.cc
+++ b/src/nes/ppu.cc
@@ -2,5 +2,7 @@
namespace nes
{
-
+ auto ppu::step_to(cycle_count const cycle) -> void
+ {
+ }
} // namespace nes \ No newline at end of file
diff --git a/src/nes/ppu.hh b/src/nes/ppu.hh
index 0f541ac..edb7aba 100644
--- a/src/nes/ppu.hh
+++ b/src/nes/ppu.hh
@@ -1,5 +1,7 @@
#pragma once
+#include "nes/util/cycle_count.hh"
+
namespace nes
{
class ppu
@@ -11,5 +13,7 @@ namespace nes
ppu(ppu&&) = delete;
auto operator=(ppu const&) -> ppu& = delete;
auto operator=(ppu&&) -> ppu& = delete;
+
+ auto step_to(cycle_count) -> void;
};
} // namespace nes \ No newline at end of file
diff --git a/src/nes/util/CMakeLists.txt b/src/nes/util/CMakeLists.txt
index f78a26f..adaa80c 100644
--- a/src/nes/util/CMakeLists.txt
+++ b/src/nes/util/CMakeLists.txt
@@ -3,4 +3,5 @@ target_sources(
PRIVATE
address.hh
button-mask.hh
+ cycle_count.hh
rgb.hh) \ No newline at end of file
diff --git a/src/nes/util/address.hh b/src/nes/util/address.hh
index f02b66b..0161998 100644
--- a/src/nes/util/address.hh
+++ b/src/nes/util/address.hh
@@ -11,30 +11,53 @@ namespace nes
public:
/// Create a zero address.
- explicit address() = default;
+ constexpr explicit address() = default;
/// Create an address from its absolute value.
- explicit address(std::uint16_t const absolute)
+ 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<std::uint16_t>((page << 8) | (offset < 0)) }
+ {
+ }
+
/// Get the absolute address value.
- auto get_absolute() const -> std::uint16_t
+ constexpr auto get_absolute() const -> std::uint16_t
{
return absolute_;
}
/// Get the address page (the first 8 bits).
- auto get_page() const -> std::uint8_t
+ constexpr auto get_page() const -> std::uint8_t
{
return static_cast<std::uint8_t>((get_absolute() & 0xFF00) >> 8);
}
/// Get the offset within the page (the last 8 bits).
- auto get_offset() const -> std::uint8_t
+ constexpr auto get_offset() const -> std::uint8_t
{
return static_cast<std::uint8_t>((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, std::uint16_t const b) -> address
+ {
+ return address{ static_cast<std::uint16_t>(a.get_absolute() + b) };
+ }
+
+ constexpr auto operator+(std::uint16_t const a, address const b) -> address
+ {
+ return address{ static_cast<std::uint16_t>(a + b.get_absolute()) };
+ }
} // namespace nes \ No newline at end of file
diff --git a/src/nes/util/cycle_count.hh b/src/nes/util/cycle_count.hh
new file mode 100644
index 0000000..f8039c2
--- /dev/null
+++ b/src/nes/util/cycle_count.hh
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <chrono>
+
+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*(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(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*(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