summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nes/cartridge.cc5
-rw-r--r--src/nes/cartridge.hh8
-rw-r--r--src/nes/cpu.cc49
-rw-r--r--src/nes/cpu.hh15
-rw-r--r--src/nes/mapper.cc78
-rw-r--r--src/nes/mapper.hh42
-rw-r--r--src/nes/ppu.cc301
-rw-r--r--src/nes/ppu.hh143
-rw-r--r--src/nes/util/address.hh10
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