srctree

Rickard Hallerbäck parent f6f6220d f7bf1544
Adding mapper 003

core/CMakeLists.txt added: 284, removed: 10, total 274
@@ -48,6 +48,8 @@ add_library(${PROJECT_NAME}
src/rom/nrom.h
src/rom/mapper_2.cpp
src/rom/mapper_2.h
src/rom/mapper_3.cpp
src/rom/mapper_3.h
src/rom_factory.cpp
)
add_library(n_e_s::core ALIAS ${PROJECT_NAME})
 
core/src/membank_factory.cpp added: 284, removed: 10, total 274
@@ -80,6 +80,12 @@ MemBankList MemBankFactory::create_nes_mem_banks(IPpu *ppu,
// Io dev
mem_banks.push_back(std::make_unique<MemBank<0x4018, 0x401F, 0x8>>());
 
// Empty SRAM
mem_banks.push_back(std::make_unique<MemBank<0x6000, 0x7FFF, 0x8>>());
 
// Expansion ROM
mem_banks.push_back(std::make_unique<MemBank<0x4020, 0x5FFF, 0x8>>());
 
return mem_banks;
}
 
 
filename was Deleted added: 284, removed: 10, total 274
@@ -0,0 +1,126 @@
#include "rom/mapper_3.h"
 
#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include "nes/core/ines_header.h"
#include "nes/core/invalid_address.h"
 
namespace n_e_s::core {
 
Mapper3::Mapper3(const INesHeader &h,
std::vector<uint8_t> prg_rom,
std::vector<uint8_t> chr_mem)
: IRom(h), prg_rom_(std::move(prg_rom)), chr_mem_(std::move(chr_mem)) {
prg_rom_size_ = h.prg_rom_size;
 
if (prg_rom_.size() !=
static_cast<std::size_t>(16u * 1024u * h.prg_rom_size)) {
throw std::invalid_argument("Invalid prg_rom size");
}
 
if (chr_mem_.size() !=
static_cast<std::size_t>(8u * 1024u * h.chr_rom_size)) {
throw std::invalid_argument("Invalid chr_ram size");
}
}
 
bool Mapper3::is_cpu_address_in_range(uint16_t addr) const {
const bool in_prg = addr >= kPrgRomStart;
return in_prg;
}
 
uint8_t Mapper3::cpu_read_byte(uint16_t addr) const {
if (addr >= kPrgRomStart) {
uint16_t mapped_addr = addr - kPrgRomStart;
if (prg_rom_size_ == 1) {
// Mirroring
mapped_addr = mapped_addr & 0x3FFFu;
}
return prg_rom_[mapped_addr];
}
throw InvalidAddress(addr);
}
 
void Mapper3::cpu_write_byte(uint16_t addr, uint8_t byte) {
(void)addr;
n_chr_bank_select_ = byte & 0x03u;
}
 
bool Mapper3::is_ppu_address_in_range(uint16_t addr) const {
const bool in_chr = addr < kChrWindow;
const bool in_nametable = addr >= kNametableStart && addr <= kNametableEnd;
return in_chr || in_nametable;
}
 
uint8_t Mapper3::ppu_read_byte(uint16_t addr) const {
if (addr < kChrWindow) {
const uint32_t mapped_addr = n_chr_bank_select_ * kChrWindow + addr;
return chr_mem_.at(mapped_addr);
}
 
const auto [index, addr_mod] =
translate_nametable_addr(addr, header().mirroring());
return nametables_[index][addr_mod];
}
 
void Mapper3::ppu_write_byte(uint16_t addr, uint8_t byte) {
if (addr < kChrWindow) {
const uint32_t mapped_addr = n_chr_bank_select_ * kChrWindow + addr;
chr_mem_.at(mapped_addr) = byte;
} else {
const auto [index, addr_mod] =
translate_nametable_addr(addr, header().mirroring());
nametables_[index][addr_mod] = byte;
}
}
 
std::pair<int, uint16_t> Mapper3::translate_nametable_addr(uint16_t addr,
Mirroring m) const {
// TODO(johnor): This logic is identical to mapper 0 (Nrom).
// Refactor and move outside the mapper.
 
// Nametables
// Range Size Desc
// $2000-$23FF $0400 Nametable 0
// $2400-$27FF $0400 Nametable 1
// $2800-$2BFF $0400 Nametable 2
// $2C00-$2FFF $0400 Nametable 3
// $3000-$3EFF $0F00 Mirrors of $2000-$2EFF
 
// Ignore top 4 bits to handle mirroring
addr &= 0x0FFFu;
if (m == Mirroring::Horizontal) {
// Nametable 0 and 1 should be the same
if (addr <= 0x03FF) {
return {0, addr % 0x0400};
}
if (addr >= 0x0400 && addr <= 0x07FF) {
return {0, addr % 0x0400};
}
if (addr >= 0x0800 && addr <= 0x0BFF) {
return {1, addr % 0x0400};
}
if (addr >= 0x0C00 && addr <= 0x0FFF) {
return {1, addr % 0x0400};
}
} else if (header().mirroring() == Mirroring::Vertical) {
// Nametable 0 and 3 should be the same
if (addr <= 0x03FF) {
return {0, addr % 0x0400};
}
if (addr >= 0x0400 && addr <= 0x07FF) {
return {1, addr % 0x0400};
}
if (addr >= 0x0800 && addr <= 0x0BFF) {
return {0, addr % 0x0400};
}
if (addr >= 0x0C00 && addr <= 0x0FFF) {
return {1, addr % 0x0400};
}
}
throw std::runtime_error("Unknown address: " + std::to_string(addr));
}
 
} // namespace n_e_s::core
 
filename was Deleted added: 284, removed: 10, total 274
@@ -0,0 +1,46 @@
#pragma once
 
#include "nes/core/irom.h"
 
#include <array>
#include <cstdint>
#include <utility>
#include <vector>
 
namespace n_e_s::core {
 
class Mapper3 : public IRom {
public:
Mapper3(const INesHeader &h,
std::vector<uint8_t> prg_rom,
std::vector<uint8_t> chr_mem);
 
[[nodiscard]] bool is_cpu_address_in_range(uint16_t addr) const override;
uint8_t cpu_read_byte(uint16_t addr) const override;
void cpu_write_byte(uint16_t addr, uint8_t byte) override;
 
[[nodiscard]] bool is_ppu_address_in_range(uint16_t addr) const override;
uint8_t ppu_read_byte(uint16_t addr) const override;
void ppu_write_byte(uint16_t addr, uint8_t byte) override;
 
private:
std::pair<int, uint16_t> translate_nametable_addr(uint16_t addr,
Mirroring m) const;
 
uint8_t n_chr_bank_select_ = {0u};
uint8_t prg_rom_size_;
std::vector<uint8_t> prg_rom_; // const?
std::vector<uint8_t> chr_mem_;
 
std::array<std::array<uint8_t, 0x0400>, 2> nametables_;
 
constexpr static uint16_t kChrStart{0x0000};
constexpr static uint16_t kChrWindow{0x2000};
constexpr static uint16_t kNametableStart{0x2000};
constexpr static uint16_t kNametableEnd{0x3EFF};
 
constexpr static uint16_t kPrgRomStart{0x8000};
constexpr static uint16_t kPrgRomEnd{0xFFFF};
};
 
} // namespace n_e_s::core
 
core/src/rom_factory.cpp added: 284, removed: 10, total 274
@@ -1,6 +1,7 @@
#include "nes/core/rom_factory.h"
 
#include "rom/mapper_2.h"
#include "rom/mapper_3.h"
#include "rom/nrom.h"
 
#include <fmt/format.h>
@@ -92,6 +93,9 @@ std::unique_ptr<IRom> RomFactory::from_bytes(std::istream &bytestream) {
if (mapper == 2) {
return std::make_unique<Mapper2>(h, prg_rom, chr_memory);
}
if (mapper == 3) {
return std::make_unique<Mapper3>(h, prg_rom, chr_memory);
}
 
throw std::logic_error(fmt::format("Unsupported mapper: {}", mapper));
}
 
core/test/src/test_rom.cpp added: 284, removed: 10, total 274
@@ -12,10 +12,7 @@ using namespace n_e_s::core;
 
namespace {
 
enum class Mapper {
Nrom = 0,
Mapper2 = 2,
};
enum class Mapper { Nrom = 0, Mapper2 = 2, Mapper3 = 3 };
 
std::string ines_header_bytes(const uint8_t mapper_id,
const uint8_t prg_rom_size,
@@ -330,4 +327,97 @@ TEST(Mapper2, write_and_read_byte_cpu_bus) {
EXPECT_EQ(0xAB, rom->cpu_read_byte(0xC001));
}
 
////////////////////////////////////////////////////////////////
// Mapper 3 tests
TEST(Mapper3, is_cpu_address_in_range) {
std::string bytes{nrom_bytes(1, 1, Mapper::Mapper3)};
std::stringstream ss(bytes);
std::unique_ptr<IRom> rom = RomFactory::from_bytes(ss);
 
EXPECT_TRUE(rom->is_cpu_address_in_range(0x8000u));
EXPECT_TRUE(rom->is_cpu_address_in_range(0xBFFFu));
EXPECT_TRUE(rom->is_cpu_address_in_range(0xC000u));
EXPECT_TRUE(rom->is_cpu_address_in_range(0xFFFFu));
 
EXPECT_FALSE(rom->is_cpu_address_in_range(0x0000u));
EXPECT_FALSE(rom->is_cpu_address_in_range(0x7FFFu));
}
 
TEST(Mapper3, is_ppu_address_in_range) {
std::string bytes{nrom_bytes(1, 1, Mapper::Mapper3)};
std::stringstream ss(bytes);
std::unique_ptr<IRom> rom = RomFactory::from_bytes(ss);
 
EXPECT_TRUE(rom->is_ppu_address_in_range(0x0000u));
EXPECT_TRUE(rom->is_ppu_address_in_range(0x2000u));
EXPECT_TRUE(rom->is_ppu_address_in_range(0x2C00u));
EXPECT_TRUE(rom->is_ppu_address_in_range(0x3EFFu));
 
// Palette memory is created in membank factory.
EXPECT_FALSE(rom->is_ppu_address_in_range(0x3F00u));
EXPECT_FALSE(rom->is_ppu_address_in_range(0x3F20u));
EXPECT_FALSE(rom->is_ppu_address_in_range(0x5000u));
}
 
TEST(Mapper3, write_and_read_byte_ppu_bus) {
std::string bytes{nrom_bytes(1, 1, Mapper::Mapper3)};
std::stringstream ss(bytes);
std::unique_ptr<IRom> rom = RomFactory::from_bytes(ss);
 
rom->ppu_write_byte(0x0100, 0x89);
EXPECT_EQ(0x89, rom->ppu_read_byte(0x0100));
}
 
TEST(Mapper3, write_should_modify_ppu_bank_select) {
std::string bytes{nrom_bytes(1, 4, Mapper::Mapper3)};
std::stringstream ss(bytes);
std::unique_ptr<IRom> rom = RomFactory::from_bytes(ss);
 
rom->cpu_write_byte(0x9000, 0x01);
rom->ppu_write_byte(0x0100, 0x11);
rom->cpu_write_byte(0x9000, 0x02);
rom->ppu_write_byte(0x0100, 0x22);
rom->cpu_write_byte(0x9000, 0x00);
rom->ppu_write_byte(0x0100, 0x01);
rom->cpu_write_byte(0x9000, 0x03);
rom->ppu_write_byte(0x0100, 0x33);
 
EXPECT_EQ(0x33, rom->ppu_read_byte(0x0100));
rom->cpu_write_byte(0x9000, 0x02);
EXPECT_EQ(0x22, rom->ppu_read_byte(0x0100));
rom->cpu_write_byte(0x9000, 0x01);
EXPECT_EQ(0x11, rom->ppu_read_byte(0x0100));
rom->cpu_write_byte(0x9000, 0x00);
EXPECT_EQ(0x01, rom->ppu_read_byte(0x0100));
}
 
TEST(Mapper3, prg_rom_should_not_be_writable) {
constexpr int kPrgRomBanks = 2;
std::string bytes{nrom_bytes(kPrgRomBanks, 1, Mapper::Mapper3)};
std::stringstream ss(bytes);
std::unique_ptr<IRom> rom = RomFactory::from_bytes(ss);
 
rom->cpu_write_byte(0x8000, 0x11);
EXPECT_EQ(0x00, rom->cpu_read_byte(0x8000));
rom->cpu_write_byte(0xFFFF, 0x12);
EXPECT_EQ(0x00, rom->cpu_read_byte(0xFFFF));
}
 
TEST(Mapper3, write_and_read_ppu_nametables) {
constexpr int kChrRomBanks = 8;
std::string bytes{nrom_bytes(1, kChrRomBanks, Mapper::Mapper3)};
std::stringstream ss(bytes);
std::unique_ptr<IRom> rom = RomFactory::from_bytes(ss);
 
rom->ppu_write_byte(0x2000, 0x01);
rom->ppu_write_byte(0x2400, 0x02);
rom->ppu_write_byte(0x2800, 0x03);
rom->ppu_write_byte(0x2C00, 0x04);
 
EXPECT_EQ(0x02, rom->ppu_read_byte(0x2000));
EXPECT_EQ(0x02, rom->ppu_read_byte(0x2400));
EXPECT_EQ(0x04, rom->ppu_read_byte(0x2800));
EXPECT_EQ(0x04, rom->ppu_read_byte(0x2C00));
}
 
} // namespace