From f114104e8e64e6f089cc1921c76ab58f33b77816 Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Wed, 6 Aug 2025 13:23:17 +0300 Subject: [PATCH] MMC1 mapper --- CMakeLists.txt | 3 +- examples/sdl/main.cpp | 4 +- src/Bus.cpp | 27 ++++++++----- src/Cartridge.cpp | 32 ++++++++++++--- src/Cartridge.h | 14 ++++++- src/Mapper/Mapper.h | 1 + src/Mapper/Mapper0.cpp | 4 ++ src/Mapper/Mapper0.h | 1 + src/Mapper/Mapper1.cpp | 89 ++++++++++++++++++++++++++++++++++++++++-- src/Mapper/Mapper1.h | 39 +++++++++++++++++- src/Ppu.cpp | 3 ++ src/Utils.h | 44 +++++++++++++++++++++ 12 files changed, 237 insertions(+), 24 deletions(-) create mode 100644 src/Utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ded792..cdf6412 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,8 @@ add_executable(nes src/Mapper/Mapper1.cpp src/Mapper/Mapper1.h src/Bus.cpp - src/Bus.h) + src/Bus.h + src/Utils.h) find_package(SDL2 CONFIG REQUIRED) find_package(fmt REQUIRED) diff --git a/examples/sdl/main.cpp b/examples/sdl/main.cpp index 2ccabf9..a945791 100644 --- a/examples/sdl/main.cpp +++ b/examples/sdl/main.cpp @@ -11,7 +11,7 @@ int main() { nes::System device; nes::SdlWindow window(nes::Ppu::SCREEN_WIDTH, nes::Ppu::SCREEN_HEIGHT); - window.setSize(nes::Ppu::SCREEN_WIDTH, nes::Ppu::SCREEN_HEIGHT); + window.setSize(nes::Ppu::SCREEN_WIDTH*2, nes::Ppu::SCREEN_HEIGHT*2); SDL_Event e; bool frameRendered = false; @@ -22,7 +22,7 @@ int main() { }); device.connect(std::make_shared()); //device.insertCartridge("/home/selim/Downloads/dk.nes"); - device.insertCartridge("/Users/selim/Documents/smb.nes"); + device.insertCartridge("/Users/selim/Documents/drm.nes"); //device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes"); auto frameStart = std::chrono::steady_clock::now(); diff --git a/src/Bus.cpp b/src/Bus.cpp index 3f0c270..49c9810 100644 --- a/src/Bus.cpp +++ b/src/Bus.cpp @@ -5,6 +5,8 @@ #include "Bus.h" #include +#include "../../../../../../opt/homebrew/Cellar/fmt/11.2.0/include/fmt/format.h" + namespace nes { Bus::Bus(uint8_t *ram, @@ -53,12 +55,6 @@ namespace nes { else if(address >= 0x2000 && address < 0x4000) { return _ppu->read(address & 0x0007); } - else if (address >= 0x6000 && address < 0x8000) { - return _cartridge->readRam(address); - } - else if(address >= 0x8000) { - return _cartridge->readPrg(address); - } else if(address == 0x4016) { return _controller1->read(); } @@ -67,6 +63,12 @@ namespace nes { return _controller2->read(); } } + else if (address >= 0x6000 && address < 0x8000) { + return _cartridge->readRam(address); + } + else if(address >= 0x8000) { + return _cartridge->readPrg(address); + } return 0; } @@ -78,9 +80,6 @@ namespace nes { else if(address >= 0x2000 && address < 0x4000) { _ppu->write(address & 0x0007, value); } - else if(address >= 0x8000) { - std::cout << "Cartridge (PRG) write at address: " << address << std::endl; - } else if(address == 0x4014) { _dma->start(value); } @@ -92,6 +91,14 @@ namespace nes { _controller2->poll(); } } + else if (address >= 0x6000 && address < 0x8000) { + _cartridge->writeRam(address, value); + } + else if(address >= 0x8000) { + _cartridge->writePrg(address, value); + } else { + //fmt::println("unknown bus write at address: {:#06x}, value: {}", address, value); + } } -} \ No newline at end of file +} diff --git a/src/Cartridge.cpp b/src/Cartridge.cpp index 2132ce5..4d1857d 100644 --- a/src/Cartridge.cpp +++ b/src/Cartridge.cpp @@ -19,9 +19,6 @@ namespace nes { std::ifstream rom(path, std::ios::binary); rom.read(reinterpret_cast(_romData.get()), static_cast(romSize)); - // UB here - // Should be std::start_lifetime_as(_romData); - // when it become available _header = reinterpret_cast(_romData.get()); if (memcmp(_header->magic, ROM_MAGIC, sizeof(ROM_MAGIC)) != 0) { @@ -32,15 +29,25 @@ namespace nes { const size_t chrSize = _header->chrChunks*CHR_CHUNK_SIZE; _prgRom = std::span(_romData.get() + sizeof(RomHeader), prgSize); - _chrRom = std::span(_romData.get() + sizeof(RomHeader) + prgSize, chrSize); - switch (_header->flags.mapper) { + if (chrSize == 0) { + _chrRam = std::make_unique(CHR_CHUNK_SIZE); + _chrRom = std::span(_chrRam.get(), CHR_CHUNK_SIZE); + } else { + _chrRom = std::span(_romData.get() + sizeof(RomHeader) + prgSize, chrSize); + } + + uint8_t mapper = _header->flags.mapper | (_header->flags2.upperMapper << 4); + + switch (mapper) { case 0: _mapper = std::make_unique(_header->prgChunks, _header->chrChunks); + break; case 1: _mapper = std::make_unique(_header->prgChunks, _header->chrChunks); + break; default: - _mapper = std::make_unique(_header->prgChunks, _header->chrChunks); + throw std::runtime_error("unknown mapper"); } if (const auto ramSize = _mapper->ramSize(); ramSize > 0) { @@ -53,16 +60,29 @@ namespace nes { return _prgRom[mappedAddress]; } + void Cartridge::writePrg(uint16_t address, uint8_t value) { + _mapper->write(address, value); + } + uint8_t Cartridge::readChr(uint16_t address) { uint32_t mappedAddress = _mapper->mapChr(address); return _chrRom[mappedAddress]; } + void Cartridge::writeChr(uint16_t address, uint8_t value) { + _chrRom[address] = value; + } + uint8_t Cartridge::readRam(uint16_t address) { uint32_t mappedAddress = _mapper->mapRam(address); return _ram[mappedAddress]; } + void Cartridge::writeRam(uint16_t address, uint8_t value) { + uint32_t mappedAddress = _mapper->mapRam(address); + _ram[mappedAddress] = value; + } + Cartridge::Mirroring Cartridge::mirroring() const { return _header->flags.mirroring; } diff --git a/src/Cartridge.h b/src/Cartridge.h index 1b0f11c..65860d7 100644 --- a/src/Cartridge.h +++ b/src/Cartridge.h @@ -34,12 +34,20 @@ namespace nes { uint8_t mapper: 4; }; + struct Flags2 { + uint8_t reserved: 2; + uint8_t version: 2; + uint8_t upperMapper: 4; + }; + struct RomHeader { uint8_t magic[4]; uint8_t prgChunks; uint8_t chrChunks; Flags flags; - uint8_t reserved[9]; + Flags2 flags2; + uint8_t prgRamSize; + uint8_t reserved[7]; }; public: @@ -47,8 +55,11 @@ namespace nes { public: uint8_t readPrg(uint16_t address); + void writePrg(uint16_t address, uint8_t value); uint8_t readChr(uint16_t address); + void writeChr(uint16_t address, uint8_t value); uint8_t readRam(uint16_t address); + void writeRam(uint16_t address, uint8_t value); Mirroring mirroring() const; private: @@ -58,6 +69,7 @@ namespace nes { std::span _prgRom; std::span _chrRom; std::unique_ptr _ram; + std::unique_ptr _chrRam; }; } diff --git a/src/Mapper/Mapper.h b/src/Mapper/Mapper.h index 4471833..e38fe23 100644 --- a/src/Mapper/Mapper.h +++ b/src/Mapper/Mapper.h @@ -20,6 +20,7 @@ namespace nes { virtual uint32_t mapChr(uint16_t address) = 0; virtual uint32_t mapRam(uint16_t address) = 0; virtual size_t ramSize() = 0; + virtual void write(uint16_t address, uint8_t value) = 0; protected: uint8_t _prgBanks; diff --git a/src/Mapper/Mapper0.cpp b/src/Mapper/Mapper0.cpp index 88a6481..0c87aba 100644 --- a/src/Mapper/Mapper0.cpp +++ b/src/Mapper/Mapper0.cpp @@ -21,4 +21,8 @@ namespace nes { size_t Mapper0::ramSize() { return 0; } + + void Mapper0::write(uint16_t address, uint8_t value) { + // not used + } } diff --git a/src/Mapper/Mapper0.h b/src/Mapper/Mapper0.h index 2a40d45..44b8f43 100644 --- a/src/Mapper/Mapper0.h +++ b/src/Mapper/Mapper0.h @@ -19,6 +19,7 @@ namespace nes { uint32_t mapChr(uint16_t address) override; uint32_t mapRam(uint16_t address) override; size_t ramSize() override; + void write(uint16_t address, uint8_t value) override; }; } diff --git a/src/Mapper/Mapper1.cpp b/src/Mapper/Mapper1.cpp index 57c5e1c..1348995 100644 --- a/src/Mapper/Mapper1.cpp +++ b/src/Mapper/Mapper1.cpp @@ -2,23 +2,50 @@ // Created by Selim Mustafaev on 27.06.2025. // +#include "../Utils.h" #include "Mapper1.h" namespace nes { + Mapper1::Mapper1(uint8_t prgBanks, uint8_t chrBanks): Mapper(prgBanks, chrBanks) { + + _controlReg.prgBankMode = FixLast16; + _controlReg.chrBankMode = Double4; + _prgBank16Hi = _prgBanks - 1; + } uint32_t Mapper1::mapPrg(uint16_t address) { if(_controlReg.prgBankMode == FixFirst16 || _controlReg.prgBankMode == FixLast16) { - + if (address >= PRG_FIRST_BANK && address < PRG_SECOND_BANK) { + return _prgBank16Lo*PRG_BANK16_SIZE + clampBits<14>(address); + } else if (address >= PRG_SECOND_BANK && address < PRG_END) { + return _prgBank16Hi*PRG_BANK16_SIZE + clampBits<14>(address); + } } else { - + return _prgBank32*PRG_BANK32_SIZE + clampBits<15>(address); } return address; } uint32_t Mapper1::mapChr(uint16_t address) { - return address; + + if (_chrBanks == 0) { + return address; + } + + switch (_controlReg.chrBankMode) { + case Single8: + return _chrBank8*CHR_BANK8_SIZE + clampBits<13>(address); + case Double4: + if (address >= CHR_FIRST_BANK && address < CHR_SECOND_BANK) { + return _chrBank4Lo*CHR_BANK4_SIZE + clampBits<12>(address); + } else if (address < CHR_END) { + return _chrBank4Hi*CHR_BANK4_SIZE + clampBits<12>(address); + } + default: + return address; + } } uint32_t Mapper1::mapRam(uint16_t address) { @@ -28,4 +55,60 @@ namespace nes { size_t Mapper1::ramSize() { return 32*1024; } + + void Mapper1::write(uint16_t address, uint8_t value) { + + // Reset shift register + if (bitIsSet<7>(value)) { + _shiftReg = 0; + _controlReg.prgBankMode = FixLast16; + _prgBank16Hi = _prgBanks - 1; + _writes = 0; + return; + } + + _writes++; + _shiftReg = (_shiftReg >> 1) | ((value & 0x1) << 4); + + if (_writes == 5) { + auto command = static_cast(getBits<13,2>(address)); + switch (command) { + case SetControlRegister: + _controlReg.value = clampBits<5>(_shiftReg); + break; + case SetChrBankLo: + if (_controlReg.chrBankMode == Double4) { + _chrBank4Lo = clampBits<5>(_shiftReg); + } else { + _chrBank8 = maskBits<1,4>(_shiftReg); + } + break; + case SetChrBankHi: + if (_controlReg.chrBankMode == Double4) { + _chrBank4Hi = clampBits<5>(_shiftReg); + } + break; + case ConfigurePrgBanks: + switch (_controlReg.prgBankMode) { + case FixFirst32: + case FixLast32: + _prgBank32 = getBits<1,3>(_shiftReg); + break; + case FixFirst16: + _prgBank16Lo = 0; + _prgBank16Hi = clampBits<4>(_shiftReg); + break; + + case FixLast16: + _prgBank16Lo = clampBits<4>(_shiftReg); + _prgBank16Hi = _prgBanks - 1; + break; + } + break; + } + + _writes = 0; + _shiftReg = 0; + } + } } diff --git a/src/Mapper/Mapper1.h b/src/Mapper/Mapper1.h index cb4b2f9..266a4c0 100644 --- a/src/Mapper/Mapper1.h +++ b/src/Mapper/Mapper1.h @@ -11,6 +11,19 @@ namespace nes { class Mapper1 final : public Mapper { public: + const uint16_t PRG_FIRST_BANK = 0x8000; + const uint16_t PRG_SECOND_BANK = 0xBFFF; + const uint16_t PRG_END = 0xFFFF; + + const uint16_t CHR_FIRST_BANK = 0x0000; + const uint16_t CHR_SECOND_BANK = 0x0FFF; + const uint16_t CHR_END = 0x1FFF; + + const size_t PRG_BANK16_SIZE = 16*1024; + const size_t PRG_BANK32_SIZE = 32*1024; + const size_t CHR_BANK4_SIZE = 4*1024; + const size_t CHR_BANK8_SIZE = 8*1024; + enum PrgBankMode: uint8_t { FixFirst32 = 0, FixLast32 = 1, @@ -23,9 +36,23 @@ namespace nes { Double4 = 1 }; + enum Command: uint8_t { + SetControlRegister = 0, + SetChrBankLo = 1, + SetChrBankHi = 2, + ConfigurePrgBanks = 3 + }; + + enum Mirroring: uint8_t { + OneScreenLowerBank = 0, + OneScreenUpperBank = 1, + Vertical = 2, + Horizontal = 3 + }; + union ControlRegister { struct { - uint8_t reserved: 2; + Mirroring mirroring: 2; PrgBankMode prgBankMode: 2; ChrBankMode chrBankMode: 1; }; @@ -34,6 +61,7 @@ namespace nes { public: using Mapper::Mapper; + Mapper1(uint8_t prgBanks, uint8_t chrBanks); ~Mapper1() override = default; public: @@ -41,11 +69,20 @@ namespace nes { uint32_t mapChr(uint16_t address) override; uint32_t mapRam(uint16_t address) override; size_t ramSize() override; + void write(uint16_t address, uint8_t value) override; private: ControlRegister _controlReg = {}; uint8_t _shiftReg = 0; uint8_t _writes = 0; + + uint8_t _chrBank4Lo = 0; + uint8_t _chrBank4Hi = 0; + uint8_t _chrBank8 = 0; + + uint8_t _prgBank16Lo = 0; + uint8_t _prgBank16Hi = 0; + uint8_t _prgBank32 = 0; }; } diff --git a/src/Ppu.cpp b/src/Ppu.cpp index fbd47b4..db8a7fe 100644 --- a/src/Ppu.cpp +++ b/src/Ppu.cpp @@ -4,6 +4,8 @@ #include "Ppu.h" +#include "../../../../../../opt/homebrew/Cellar/fmt/11.2.0/include/fmt/base.h" + #ifdef NES_LOGGING #include #endif @@ -228,6 +230,7 @@ namespace nes { _oam->write(value); break; default: + fmt::println("unknown ppu write"); break; } } diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 0000000..48446c7 --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,44 @@ +// +// Created by Selim Mustafaev on 02.07.2025. +// + +#ifndef UTILS_H +#define UTILS_H + +#include + +namespace nes { + + template + consteval T createBitMask() { + T hi = (1 << (pos + len)) - 1; + T lo = (1 << pos) - 1; + return hi - lo; + } + + template + bool bitIsSet(T value) { + return value & (1 << N); + } + + template + T getBits(T value) { + static_assert(sizeof(T)*8 >= (pos + len - 1)); + return (value >> pos) & ((1 << len) - 1); + } + + template + T maskBits(T value) { + static_assert(sizeof(T)*8 >= (pos + len - 1)); + return value & createBitMask(); + } + + template + T clampBits(T value) { + static_assert(sizeof(T)*8 >= N); + return value & ((1 << N) - 1); + } + +} + +#endif //UTILS_H