diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2025-01-18 19:35:42 +0100 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2025-01-18 19:35:42 +0100 |
| commit | 67672d06776e38b06f31f6599678767c60eed58a (patch) | |
| tree | 4846c762815b887036b10a7aadd849dd8fdd4420 /src/nes | |
| parent | fe7243fdd14f6ef57033f9e70141aba970dba7ea (diff) | |
implement ppu io registers
Diffstat (limited to 'src/nes')
| -rw-r--r-- | src/nes/cartridge.cc | 5 | ||||
| -rw-r--r-- | src/nes/cartridge.hh | 8 | ||||
| -rw-r--r-- | src/nes/cpu.cc | 49 | ||||
| -rw-r--r-- | src/nes/cpu.hh | 15 | ||||
| -rw-r--r-- | src/nes/mapper.cc | 78 | ||||
| -rw-r--r-- | src/nes/mapper.hh | 42 | ||||
| -rw-r--r-- | src/nes/ppu.cc | 301 | ||||
| -rw-r--r-- | src/nes/ppu.hh | 143 | ||||
| -rw-r--r-- | src/nes/util/address.hh | 10 |
9 files changed, 620 insertions, 31 deletions
diff --git a/src/nes/cartridge.cc b/src/nes/cartridge.cc index d2d11b6..861dc13 100644 --- a/src/nes/cartridge.cc +++ b/src/nes/cartridge.cc @@ -12,6 +12,8 @@ namespace nes 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<std::uint8_t const*>(data); auto res = cartridge{}; res.is_valid_ = true; @@ -29,6 +31,9 @@ namespace nes res.mapper_number_ = (control_1 & 0b11110000) >> 4 | (control_2 & 0b11110000) >> 0; + res.name_table_arrangement_ = (control_1 & 0b00000001) != 0 + ? name_table_arrangement::vertical + : name_table_arrangement::horizontal; // Load program data. auto offset = std::size_t{ 16 }; diff --git a/src/nes/cartridge.hh b/src/nes/cartridge.hh index a334125..3ce2a16 100644 --- a/src/nes/cartridge.hh +++ b/src/nes/cartridge.hh @@ -6,6 +6,12 @@ namespace nes { + enum class name_table_arrangement + { + horizontal, + vertical, + }; + class cartridge { bool is_valid_{ false }; @@ -13,6 +19,7 @@ namespace nes std::vector<std::uint8_t> chr_rom_; std::vector<std::uint8_t> ram_; std::uint8_t mapper_number_{ 0 }; + name_table_arrangement name_table_arrangement_{}; public: explicit cartridge() = default; @@ -29,5 +36,6 @@ namespace nes auto get_ram_mut() -> std::uint8_t* { return ram_.data(); } auto get_ram_length() const -> std::size_t { return ram_.size(); } auto get_mapper_number() const -> std::uint8_t { return mapper_number_; } + auto get_name_table_arrangement() const -> name_table_arrangement { return name_table_arrangement_; } }; } // namespace nes
\ No newline at end of file diff --git a/src/nes/cpu.cc b/src/nes/cpu.cc index 3e3c6b8..9b5ed66 100644 --- a/src/nes/cpu.cc +++ b/src/nes/cpu.cc @@ -1,4 +1,5 @@ #include "nes/cpu.hh" +#include "nes/ppu.hh" #include "nes/controller.hh" #include "nes/mapper.hh" @@ -32,6 +33,11 @@ namespace nes snapshot.registers.n = registers_.n; } + auto cpu::stall_cycles(cycle_count const count) -> void + { + current_cycles_ += count; + } + auto cpu::step_to(cycle_count const target) -> void { while (current_cycles_ < target) @@ -1122,6 +1128,8 @@ namespace nes switch (Mode) { + case addressing_mode::immediate: static_assert(Mode != addressing_mode::immediate); + case addressing_mode::accumulator: static_assert(Mode != addressing_mode::accumulator); case addressing_mode::zero_page: { addr = address{ advance_pc8() }; @@ -1241,14 +1249,28 @@ namespace nes 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{ 0x3FFF }) + { + switch (addr.get_absolute() % 8) + { + case 0: return ppu_.read_latch(); + case 1: return ppu_.read_latch(); + case 2: return ppu_.read_ppustatus(); + case 3: return ppu_.read_latch(); + case 4: return ppu_.read_oamdata(); + case 5: return ppu_.read_latch(); + case 6: return ppu_.read_latch(); + case 7: return ppu_.read_ppudata(); + default: return 0x0; + } + } if (addr <= address{ 0x4013 }) { return 0x0; } // TODO: APU registers - if (addr == address{ 0x4014 }) { return 0x0; } // TODO: PPU registers + if (addr == address{ 0x4014 }) { return ppu_.read_latch(); } 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); + return mapper_.read_cpu(addr); } auto cpu::read16(address const addr) -> std::uint16_t @@ -1261,14 +1283,28 @@ namespace nes 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{ 0x3FFF }) + { + switch (addr.get_absolute() % 8) + { + case 0: ppu_.write_ppuctrl(value); return; + case 1: ppu_.write_ppumask(value); return; + case 2: ppu_.write_latch(value); return; + case 3: ppu_.write_oamaddr(value); return; + case 4: ppu_.write_oamdata(value); return; + case 5: ppu_.write_ppuscroll(value); return; + case 6: ppu_.write_ppuaddr(value); return; + case 7: ppu_.write_ppudata(value); return; + default: return; + } + } if (addr <= address{ 0x4013 }) { return; } // TODO: APU registers - if (addr == address{ 0x4014 }) { return; } // TODO: PPU registers + if (addr == address{ 0x4014 }) { ppu_.write_oamdma(value); return; } 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); + mapper_.write_cpu(addr, value); } auto cpu::write16(address const addr, std::uint16_t const value) -> void @@ -1278,5 +1314,4 @@ namespace nes 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 28ac0d6..75b1943 100644 --- a/src/nes/cpu.hh +++ b/src/nes/cpu.hh @@ -133,8 +133,16 @@ namespace nes auto snapshot(test::status&) -> void; + auto stall_cycles(cycle_count) -> void; auto step_to(cycle_count) -> void; + // Memory access + + auto read8(address) -> std::uint8_t; + auto read16(address) -> std::uint16_t; + auto write8(address, std::uint8_t) -> void; + auto write16(address, std::uint16_t) -> void; + private: auto step() -> void; @@ -281,12 +289,5 @@ namespace nes 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 index a451e3a..237ab47 100644 --- a/src/nes/mapper.cc +++ b/src/nes/mapper.cc @@ -4,10 +4,15 @@ namespace nes { - mapper::mapper(cartridge& cartridge, read_func const read, write_func const write) + 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_{ read } - , write_{ write } + , read_cpu_{ read_cpu } + , write_cpu_{ write_cpu } + , read_ppu_{ read_ppu } + , write_ppu_{ write_ppu } { } @@ -16,16 +21,21 @@ namespace nes switch (cartridge.get_mapper_number()) { case 0x00: - return mapper{ cartridge, &mapper::nrom_read, &mapper::nrom_write }; + return mapper{ + cartridge, + &mapper::nrom_read_cpu, &mapper::nrom_write_cpu, + &mapper::nrom_read_ppu, &mapper::nrom_write_ppu }; default: std::cerr << "Mapper not implemented: " << std::hex << cartridge.get_mapper_number() << std::endl; std::abort(); } } + // ----------------------------------------------------------------------------------------------------------------- // NROM + // ----------------------------------------------------------------------------------------------------------------- - auto mapper::nrom_read(address const addr) -> std::uint8_t + auto mapper::nrom_read_cpu(address const addr) -> std::uint8_t { if (addr <= address{ 0x5FFF }) { @@ -54,7 +64,7 @@ namespace nes return 0x0; } - auto mapper::nrom_write(address const addr, std::uint8_t const value) -> void + auto mapper::nrom_write_cpu(address const addr, std::uint8_t const value) -> void { if (addr <= address{ 0x5FFF }) { @@ -63,7 +73,63 @@ namespace nes if (addr <= address{ 0x7FFF }) { cartridge_.get_ram_mut()[addr.get_absolute() - 0x6000] = value; + return; + } + } + + auto mapper::nrom_read_ppu(address const addr, std::uint8_t const* vram) -> std::uint8_t + { + if (addr <= address{ 0x1FFF }) + { + auto const rel = addr.get_absolute(); + if (rel >= cartridge_.get_chr_rom_length()) { return 0x0; } + + return cartridge_.get_chr_rom()[rel]; + } + if (addr <= address{ 0x3EFF }) + { + return vram[nrom_mirrored_vram_address(addr)]; + } + + return 0x0; + } + + auto mapper::nrom_write_ppu(address const addr, std::uint8_t* vram, std::uint8_t const value) -> void + { + if (addr <= address{ 0x1FFF }) + { + return; + } + if (addr <= address{ 0x3EFF }) + { + vram[nrom_mirrored_vram_address(addr)] = value; + return; } } + auto mapper::nrom_mirrored_vram_address(address const addr) const -> unsigned + { + auto rel = addr.get_absolute(); + if (rel >= 0x3000) { rel -= 0x1000; } + + switch (cartridge_.get_name_table_arrangement()) + { + case name_table_arrangement::horizontal: + // $2000 = $2400, $2800 = $2c00 + if (rel <= 0x23FF) { return 0x000 + (rel % 0x400); } + if (rel <= 0x27FF) { return 0x000 + (rel % 0x400); } + if (rel <= 0x2BFF) { return 0x400 + (rel % 0x400); } + if (rel <= 0x2FFF) { return 0x400 + (rel % 0x400); } + break; + case name_table_arrangement::vertical: + // $2000 = $2800, $2400 = $2c00 + if (rel <= 0x23FF) { return 0x000 + (rel % 0x400); } + if (rel <= 0x27FF) { return 0x400 + (rel % 0x400); } + if (rel <= 0x2BFF) { return 0x000 + (rel % 0x400); } + if (rel <= 0x2FFF) { return 0x400 + (rel % 0x400); } + break; + } + + return 0; + } } // namespace nes
\ No newline at end of file diff --git a/src/nes/mapper.hh b/src/nes/mapper.hh index 0aec2ac..f2873a6 100644 --- a/src/nes/mapper.hh +++ b/src/nes/mapper.hh @@ -8,12 +8,16 @@ namespace nes class mapper { - using read_func = std::uint8_t(mapper::*)(address); - using write_func = void(mapper::*)(address, std::uint8_t); + 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_func read_; - write_func write_; + 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; @@ -23,22 +27,38 @@ namespace nes auto operator=(mapper const&) -> mapper& = delete; auto operator=(mapper&&) -> mapper& = delete; - auto read(address const addr) -> std::uint8_t + auto read_cpu(address const addr) -> std::uint8_t { - return (this->*read_)(addr); + return (this->*read_cpu_)(addr); } - auto write(address const addr, std::uint8_t const value) -> void + auto write_cpu(address const addr, std::uint8_t const value) -> void { - (this->*write_)(addr, value); + (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_func read, write_func write); + explicit mapper( + cartridge& cartridge, + read_cpu_func read_cpu, write_cpu_func write_cpu, + read_ppu_func read_ppu, write_ppu_func write_ppu); // Implementations - auto nrom_read(address) -> std::uint8_t; - auto nrom_write(address, std::uint8_t) -> void; + auto nrom_read_cpu(address) -> std::uint8_t; + auto nrom_write_cpu(address, std::uint8_t) -> void; + auto nrom_read_ppu(address, std::uint8_t const* vram) -> std::uint8_t; + auto nrom_write_ppu(address, std::uint8_t* vram, std::uint8_t) -> void; + auto nrom_mirrored_vram_address(address) const -> unsigned; }; } // namespace nes
\ No newline at end of file diff --git a/src/nes/ppu.cc b/src/nes/ppu.cc index 302b395..e57754d 100644 --- a/src/nes/ppu.cc +++ b/src/nes/ppu.cc @@ -1,4 +1,8 @@ #include "nes/ppu.hh" +#include "nes/cpu.hh" +#include "nes/mapper.hh" +#include "nes/util/address.hh" +#include "nes/util/rgb.hh" namespace nes { @@ -13,6 +17,303 @@ namespace nes while (current_cycles_ < cycle) { current_cycles_ += cycle_count::from_ppu(1); + step(); + } + } + + auto ppu::step() -> void + { + // TODO + } + + // ----------------------------------------------------------------------------------------------------------------- + // Memory Access + // ----------------------------------------------------------------------------------------------------------------- + + // + // See: https://www.nesdev.org/wiki/PPU_memory_map + // + + auto ppu::read8(address addr) -> std::uint8_t + { + addr = addr % 0x4000; // PPU only has 16 KiB addresses. + if (addr <= address{ 0x3EFF }) { return mapper_.read_ppu(addr, vram_); } + if (addr <= address{ 0x3FFF }) + { + auto const index = color_index{ addr.get_absolute() }; + auto const color = get_color(index); + return static_cast<std::uint8_t>(color); + } + + return 0x0; + } + + auto ppu::write8(address addr, std::uint8_t const value) -> void + { + addr = addr % 0x4000; // PPU only has 16 KiB addresses. + if (addr <= address{ 0x3EFF }) + { + mapper_.write_ppu(addr, vram_, value); + return; + } + if (addr <= address{ 0x3FFF }) + { + auto const index = color_index{ addr.get_absolute() }; + get_color(index) = color{ value }; + return; + } + } + + // ----------------------------------------------------------------------------------------------------------------- + // IO Registers + // ----------------------------------------------------------------------------------------------------------------- + + // + // See: https://www.nesdev.org/wiki/PPU_registers + // + + auto ppu::read_latch() -> std::uint8_t + { + return latch_; + } + + auto ppu::read_ppustatus() -> std::uint8_t + { + auto const base = latch_ & ~status_mask; + auto const res = base | (status_.value & status_mask); + + status_.vblank = false; + internal_.w = false; + + write_latch(res); + return res; + } + + auto ppu::read_oamdata() -> std::uint8_t + { + auto const res = oam_[oamaddr_]; + write_latch(res); + return res; + } + + auto ppu::read_ppudata() -> std::uint8_t + { + auto const addr = address{ internal_.v }; + auto res = read8(addr); + + if (addr <= address{ 0x3F00 }) + { + std::swap(res, ppudata_read_buffer_); + } + else + { + // For palette buffers: buffer the mirrored nametable instead. + ppudata_read_buffer_ = read8(addr - 0x1000); + } + + write_latch(res); + increment_vram(); + return res; + } + + auto ppu::write_latch(std::uint8_t const value) -> void + { + latch_ = value; + } + + auto ppu::write_ppuctrl(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + control_.value = value; + } + } + + auto ppu::write_ppuscroll(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + if (!internal_.w) + { + // First write -> x value. + internal_.t = (internal_.t & 0b1111111111100000) | (value >> 3); + internal_.x = value & 0b00000111; + } + else + { + // Second write -> y value. + internal_.t = (internal_.t & 0b1000111111111111) | ((value & 0b00000111) << 12); + internal_.t = (internal_.t & 0b1111110000011111) | ((value & 0b11111000) << 2); + } + + internal_.w = !internal_.w; + } + } + + auto ppu::write_ppumask(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + mask_.value = value; + } + } + + auto ppu::write_ppuaddr(std::uint8_t const value) -> void + { + write_latch(value); + if (current_cycles_ > boot_up_cycles) + { + if (!internal_.w) + { + // First write. + internal_.t = (internal_.t & 0b0000000011111111) | ((value & 0b00111111) << 8); + } + else + { + // Second write. + internal_.t = (internal_.t & 0b1111111100000000) | ((value & 0b11111111) << 0); + internal_.v = internal_.t; + } + + internal_.w = !internal_.w; + } + } + + auto ppu::write_ppudata(std::uint8_t const value) -> void + { + write_latch(value); + write8(address{ internal_.v }, value); + increment_vram(); + } + + auto ppu::write_oamaddr(std::uint8_t const value) -> void + { + write_latch(value); + oamaddr_ = value; + } + + auto ppu::write_oamdata(std::uint8_t const value) -> void + { + write_latch(value); + oam_[oamaddr_] = value; + oamaddr_ += 1; + } + + auto ppu::write_oamdma(std::uint8_t const value) -> void + { + // TODO: Do this in CPU::step instead in case there's an NMI? + for (auto i = std::uint8_t{ 0x00 }; i < 0xff; ++i) + { + oam_[oamaddr_] = cpu_.read8(address{ value, i }); + oamaddr_ += 1; + } + + auto const stalled_cycles = cycle_count::from_cpu((current_cycles_.to_ppu() % 2 == 0) ? 513 : 514); + cpu_.stall_cycles(stalled_cycles); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------------------------------------------------- + + auto ppu::increment_vram() -> void + { + switch (control_.vram_increment) + { + case vram_increment::add_1_across: + internal_.v += 1; + break; + case vram_increment::add_32_down: + internal_.v += 32; + break; + } + } + + auto ppu::get_color(color_index index) -> color& + { + // See https://www.nesdev.org/wiki/PPU_palettes + index.value = index.value % 0x20; + if (index.color == 0) + { + // The first color is mirrored between background and foreground palettes. + index.role = role::background; + } + return palette_buffer_[index.value]; + } + + auto ppu::resolve_color(color color) -> rgb + { + auto const index = static_cast<std::uint8_t>(color); + switch (index & 0x3F) + { + case 0x00: return rgb::from_hex(0x666666); + case 0x01: return rgb::from_hex(0x882A00); + case 0x02: return rgb::from_hex(0xA71214); + case 0x03: return rgb::from_hex(0xA4003B); + case 0x04: return rgb::from_hex(0x7E005C); + case 0x05: return rgb::from_hex(0x40006E); + case 0x06: return rgb::from_hex(0x00066C); + case 0x07: return rgb::from_hex(0x001D56); + case 0x08: return rgb::from_hex(0x003533); + case 0x09: return rgb::from_hex(0x00480B); + case 0x0A: return rgb::from_hex(0x005200); + case 0x0B: return rgb::from_hex(0x084F00); + case 0x0C: return rgb::from_hex(0x4D4000); + case 0x0D: return rgb::from_hex(0x000000); + case 0x0E: return rgb::from_hex(0x000000); + case 0x0F: return rgb::from_hex(0x000000); + case 0x10: return rgb::from_hex(0xADADAD); + case 0x11: return rgb::from_hex(0xD95F15); + case 0x12: return rgb::from_hex(0xFF4042); + case 0x13: return rgb::from_hex(0xFE2775); + case 0x14: return rgb::from_hex(0xCC1AA0); + case 0x15: return rgb::from_hex(0x7B1EB7); + case 0x16: return rgb::from_hex(0x2031B5); + case 0x17: return rgb::from_hex(0x004E99); + case 0x18: return rgb::from_hex(0x006D6B); + case 0x19: return rgb::from_hex(0x008738); + case 0x1A: return rgb::from_hex(0x00930C); + case 0x1B: return rgb::from_hex(0x328F00); + case 0x1C: return rgb::from_hex(0x8D7C00); + case 0x1D: return rgb::from_hex(0x000000); + case 0x1E: return rgb::from_hex(0x000000); + case 0x1F: return rgb::from_hex(0x000000); + case 0x20: return rgb::from_hex(0xFFFEFF); + case 0x21: return rgb::from_hex(0xFFB064); + case 0x22: return rgb::from_hex(0xFF9092); + case 0x23: return rgb::from_hex(0xFF76C6); + case 0x24: return rgb::from_hex(0xFF6AF3); + case 0x25: return rgb::from_hex(0xCC6EFE); + case 0x26: return rgb::from_hex(0x7081FE); + case 0x27: return rgb::from_hex(0x229EEA); + case 0x28: return rgb::from_hex(0x00BEBC); + case 0x29: return rgb::from_hex(0x00D888); + case 0x2A: return rgb::from_hex(0x30E45C); + case 0x2B: return rgb::from_hex(0x82E045); + case 0x2C: return rgb::from_hex(0xDECD48); + case 0x2D: return rgb::from_hex(0x4F4F4F); + case 0x2E: return rgb::from_hex(0x000000); + case 0x2F: return rgb::from_hex(0x000000); + case 0x30: return rgb::from_hex(0xFFFEFF); + case 0x31: return rgb::from_hex(0xFFDFC0); + case 0x32: return rgb::from_hex(0xFFD2D3); + case 0x33: return rgb::from_hex(0xFFC8E8); + case 0x34: return rgb::from_hex(0xFFC2FB); + case 0x35: return rgb::from_hex(0xEAC4FE); + case 0x36: return rgb::from_hex(0xC5CCFE); + case 0x37: return rgb::from_hex(0xA5D8F7); + case 0x38: return rgb::from_hex(0x94E5E4); + case 0x39: return rgb::from_hex(0x96EFCF); + case 0x3A: return rgb::from_hex(0xABF4BD); + case 0x3B: return rgb::from_hex(0xCCF3B3); + case 0x3C: return rgb::from_hex(0xF2EBB5); + case 0x3D: return rgb::from_hex(0xB8B8B8); + case 0x3E: return rgb::from_hex(0x000000); + case 0x3F: return rgb::from_hex(0x000000); + default: return rgb::from_hex(0x000000); } } } // namespace nes
\ No newline at end of file diff --git a/src/nes/ppu.hh b/src/nes/ppu.hh index 8f116c7..1d08614 100644 --- a/src/nes/ppu.hh +++ b/src/nes/ppu.hh @@ -4,17 +4,129 @@ namespace nes { + class address; class cpu; class mapper; + class rgb; class ppu { static constexpr auto vram_size = std::size_t{ 0x800 }; + static constexpr auto oam_size = std::size_t{ 0x100 }; + static constexpr auto palette_buffer_size = std::size_t{ 0x20 }; + static constexpr auto status_mask = std::uint8_t{ 0b11100000 }; + // Writes to some registers are ignored until this clock cycle. + static constexpr auto boot_up_cycles = cycle_count::from_ppu(29658); + + enum class vram_increment + { + add_1_across = 0, + add_32_down = 1, + }; + + enum class sprite_size + { + _8x8 = 0, + _8x16 = 1, + }; + + enum class role + { + background = 0, + sprite = 1, + }; + + enum class color : std::uint8_t + { + }; + + union color_index + { + struct + { + unsigned color : 2; + unsigned palette : 2; + role role : 1; + }; + unsigned value; + + explicit color_index(unsigned const value) + : value{ value } + { + } + }; cycle_count current_cycles_; cpu& cpu_; mapper& mapper_; std::uint8_t vram_[vram_size]{}; + std::uint8_t oam_[oam_size]{}; + color palette_buffer_[palette_buffer_size] + { + color{ 0x09 }, color{ 0x01 }, color{ 0x00 }, color{ 0x01 }, + color{ 0x00 }, color{ 0x02 }, color{ 0x02 }, color{ 0x0D }, + color{ 0x08 }, color{ 0x10 }, color{ 0x08 }, color{ 0x24 }, + color{ 0x00 }, color{ 0x00 }, color{ 0x04 }, color{ 0x2C }, + color{ 0x09 }, color{ 0x01 }, color{ 0x34 }, color{ 0x03 }, + color{ 0x00 }, color{ 0x04 }, color{ 0x00 }, color{ 0x14 }, + color{ 0x08 }, color{ 0x3A }, color{ 0x00 }, color{ 0x02 }, + color{ 0x00 }, color{ 0x20 }, color{ 0x2C }, color{ 0x08 }, + }; + std::uint8_t latch_{}; // The last read/written IO register value, returned when reading write-only registers. + union + { + struct + { + bool : 1; + bool : 1; + bool : 1; + bool : 1; + bool : 1; + bool sprite_overflow : 1; + bool sprite_0_hit : 1; + bool vblank : 1; + }; + std::uint8_t value{ 0b00000000 }; + } status_{}; + union + { + struct + { + std::uint8_t base_name_table : 2; // Base nametable address. + vram_increment vram_increment : 1; // VRAM increment per CPU read/write of PPUDATA. + std::uint8_t sprite_pattern_table : 1; // Sprite pattern table address for 8x8 sprites. + std::uint8_t background_pattern_table : 1; // Background pattern table address. + sprite_size sprite_size : 1; // Sprite size. + bool enable_ext_pin : 1; // PPU master/slave select. + bool vblank_nmi : 1; // Enable/disable Vblank NMI + }; + std::uint8_t value{ 0b00000000 }; + } control_{}; + union + { + struct + { + bool grayscale : 1; // Enable/disable grayscale. + bool show_background_start : 1; // Show background in leftmost 8 pixels of screen. + bool show_sprites_start : 1; // Show sprites in leftmost 8 pixels of screen. + bool enable_background : 1; // Enable background rendering. + bool enable_sprites : 1; // Enable sprite rendering. + bool emphasize_red : 1; // Emphasize red. + bool emphasize_green : 1; // Emphasize green. + bool emphasize_blue : 1; // Emphasize blue. + }; + std::uint8_t value{ 0b00000000 }; + } mask_{}; + struct + { + // See https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers + std::uint16_t v{}; // Current VRAM address (15 bits) + std::uint16_t t{}; // Temporary VRAM address (15 bits) + std::uint8_t x{}; // Fine X scroll (3 bits) + bool w{}; // Second write toggle (1 bit) + } internal_{}; + std::uint8_t oamaddr_{}; // Address accessed by IO registers + std::uint8_t ppudata_read_buffer_{}; // Delays PPUDATA reads by one. public: explicit ppu(cpu& cpu, mapper& mapper); @@ -25,5 +137,36 @@ namespace nes auto operator=(ppu&&) -> ppu& = delete; auto step_to(cycle_count) -> void; + + // Memory access + + auto read8(address) -> std::uint8_t; + auto write8(address, std::uint8_t) -> void; + + // IO registers + + auto read_latch() -> std::uint8_t; + auto read_ppustatus() -> std::uint8_t; + auto read_oamdata() -> std::uint8_t; + auto read_ppudata() -> std::uint8_t; + + auto write_latch(std::uint8_t) -> void; + auto write_ppuctrl(std::uint8_t) -> void; + auto write_ppuscroll(std::uint8_t) -> void; + auto write_ppumask(std::uint8_t) -> void; + auto write_ppuaddr(std::uint8_t) -> void; + auto write_ppudata(std::uint8_t) -> void; + auto write_oamaddr(std::uint8_t) -> void; + auto write_oamdata(std::uint8_t) -> void; + auto write_oamdma(std::uint8_t) -> void; + + private: + auto step() -> void; + + // Helpers + + auto increment_vram() -> void; + auto get_color(color_index) -> color&; + auto resolve_color(color) -> rgb; }; } // namespace nes
\ No newline at end of file diff --git a/src/nes/util/address.hh b/src/nes/util/address.hh index 77ee76a..85a00ab 100644 --- a/src/nes/util/address.hh +++ b/src/nes/util/address.hh @@ -60,4 +60,14 @@ namespace nes { return address{ static_cast<std::uint16_t>(a + 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%(address const a, std::uint16_t b) -> address + { + return address{ static_cast<std::uint16_t>(a.get_absolute() % b) }; + } } // namespace nes
\ No newline at end of file |
