From 1db7fcad56591d22de36300b53da603a7c996125 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 24 Feb 2020 16:28:42 +0000 Subject: [PATCH] Added Parts #6 & #7 (Combined) --- Part #7 - Mappers & Basic Sounds/Bus.cpp | 278 +++ Part #7 - Mappers & Basic Sounds/Bus.h | 140 ++ .../Cartridge.cpp | 256 +++ Part #7 - Mappers & Basic Sounds/Cartridge.h | 113 ++ Part #7 - Mappers & Basic Sounds/Mapper.cpp | 93 + Part #7 - Mappers & Basic Sounds/Mapper.h | 102 ++ .../Mapper_000.cpp | 129 ++ Part #7 - Mappers & Basic Sounds/Mapper_000.h | 76 + .../Mapper_001.cpp | 301 ++++ Part #7 - Mappers & Basic Sounds/Mapper_001.h | 92 + .../Mapper_002.cpp | 125 ++ Part #7 - Mappers & Basic Sounds/Mapper_002.h | 75 + .../Mapper_003.cpp | 113 ++ Part #7 - Mappers & Basic Sounds/Mapper_003.h | 77 + .../Mapper_004.cpp | 344 ++++ Part #7 - Mappers & Basic Sounds/Mapper_004.h | 98 + .../Mapper_066.cpp | 111 ++ Part #7 - Mappers & Basic Sounds/Mapper_066.h | 75 + Part #7 - Mappers & Basic Sounds/olc2A03.cpp | 363 ++++ Part #7 - Mappers & Basic Sounds/olc2A03.h | 338 ++++ Part #7 - Mappers & Basic Sounds/olc2C02.cpp | 1406 +++++++++++++++ Part #7 - Mappers & Basic Sounds/olc2C02.h | 239 +++ Part #7 - Mappers & Basic Sounds/olc6502.cpp | 1589 +++++++++++++++++ Part #7 - Mappers & Basic Sounds/olc6502.h | 275 +++ .../olcNes_Sounds1.cpp | 420 +++++ 25 files changed, 7228 insertions(+) create mode 100644 Part #7 - Mappers & Basic Sounds/Bus.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Bus.h create mode 100644 Part #7 - Mappers & Basic Sounds/Cartridge.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Cartridge.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_000.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_000.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_001.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_001.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_002.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_002.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_003.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_003.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_004.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_004.h create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_066.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/Mapper_066.h create mode 100644 Part #7 - Mappers & Basic Sounds/olc2A03.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/olc2A03.h create mode 100644 Part #7 - Mappers & Basic Sounds/olc2C02.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/olc2C02.h create mode 100644 Part #7 - Mappers & Basic Sounds/olc6502.cpp create mode 100644 Part #7 - Mappers & Basic Sounds/olc6502.h create mode 100644 Part #7 - Mappers & Basic Sounds/olcNes_Sounds1.cpp diff --git a/Part #7 - Mappers & Basic Sounds/Bus.cpp b/Part #7 - Mappers & Basic Sounds/Bus.cpp new file mode 100644 index 0000000..4bb3f3e --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Bus.cpp @@ -0,0 +1,278 @@ +/* + olc::NES - System Bus + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Bus.h" + +Bus::Bus() +{ + // Connect CPU to communication bus + cpu.ConnectBus(this); + +} + + +Bus::~Bus() +{ +} + +void Bus::SetSampleFrequency(uint32_t sample_rate) +{ + dAudioTimePerSystemSample = 1.0 / (double)sample_rate; + dAudioTimePerNESClock = 1.0 / 5369318.0; // PPU Clock Frequency +} + +void Bus::cpuWrite(uint16_t addr, uint8_t data) +{ + if (cart->cpuWrite(addr, data)) + { + // The cartridge "sees all" and has the facility to veto + // the propagation of the bus transaction if it requires. + // This allows the cartridge to map any address to some + // other data, including the facility to divert transactions + // with other physical devices. The NES does not do this + // but I figured it might be quite a flexible way of adding + // "custom" hardware to the NES in the future! + } + else if (addr >= 0x0000 && addr <= 0x1FFF) + { + // System RAM Address Range. The range covers 8KB, though + // there is only 2KB available. That 2KB is "mirrored" + // through this address range. Using bitwise AND to mask + // the bottom 11 bits is the same as addr % 2048. + cpuRam[addr & 0x07FF] = data; + + } + else if (addr >= 0x2000 && addr <= 0x3FFF) + { + // PPU Address range. The PPU only has 8 primary registers + // and these are repeated throughout this range. We can + // use bitwise AND operation to mask the bottom 3 bits, + // which is the equivalent of addr % 8. + ppu.cpuWrite(addr & 0x0007, data); + } + else if ((addr >= 0x4000 && addr <= 0x4013) || addr == 0x4015 || addr == 0x4017) // NES APU + { + apu.cpuWrite(addr, data); + } + else if (addr == 0x4014) + { + // A write to this address initiates a DMA transfer + dma_page = data; + dma_addr = 0x00; + dma_transfer = true; + } + else if (addr >= 0x4016 && addr <= 0x4017) + { + // "Lock In" controller state at this time + controller_state[addr & 0x0001] = controller[addr & 0x0001]; + } + +} + +uint8_t Bus::cpuRead(uint16_t addr, bool bReadOnly) +{ + uint8_t data = 0x00; + if (cart->cpuRead(addr, data)) + { + // Cartridge Address Range + } + else if (addr >= 0x0000 && addr <= 0x1FFF) + { + // System RAM Address Range, mirrored every 2048 + data = cpuRam[addr & 0x07FF]; + } + else if (addr >= 0x2000 && addr <= 0x3FFF) + { + // PPU Address range, mirrored every 8 + data = ppu.cpuRead(addr & 0x0007, bReadOnly); + } + else if (addr == 0x4015) + { + // APU Read Status + data = apu.cpuRead(addr); + } + else if (addr >= 0x4016 && addr <= 0x4017) + { + // Read out the MSB of the controller status word + data = (controller_state[addr & 0x0001] & 0x80) > 0; + controller_state[addr & 0x0001] <<= 1; + } + + return data; +} + +void Bus::insertCartridge(const std::shared_ptr& cartridge) +{ + // Connects cartridge to both Main Bus and CPU Bus + this->cart = cartridge; + ppu.ConnectCartridge(cartridge); +} + +void Bus::reset() +{ + cart->reset(); + cpu.reset(); + ppu.reset(); + nSystemClockCounter = 0; + dma_page = 0x00; + dma_addr = 0x00; + dma_data = 0x00; + dma_dummy = true; + dma_transfer = false; +} + +bool Bus::clock() +{ + // Clocking. The heart and soul of an emulator. The running + // frequency is controlled by whatever calls this function. + // So here we "divide" the clock as necessary and call + // the peripheral devices clock() function at the correct + // times. + + // The fastest clock frequency the digital system cares + // about is equivalent to the PPU clock. So the PPU is clocked + // each time this function is called... + ppu.clock(); + + // ...also clock the APU + apu.clock(); + + // The CPU runs 3 times slower than the PPU so we only call its + // clock() function every 3 times this function is called. We + // have a global counter to keep track of this. + if (nSystemClockCounter % 3 == 0) + { + // Is the system performing a DMA transfer form CPU memory to + // OAM memory on PPU?... + if (dma_transfer) + { + // ...Yes! We need to wait until the next even CPU clock cycle + // before it starts... + if (dma_dummy) + { + // ...So hang around in here each clock until 1 or 2 cycles + // have elapsed... + if (nSystemClockCounter % 2 == 1) + { + // ...and finally allow DMA to start + dma_dummy = false; + } + } + else + { + // DMA can take place! + if (nSystemClockCounter % 2 == 0) + { + // On even clock cycles, read from CPU bus + dma_data = cpuRead(dma_page << 8 | dma_addr); + } + else + { + // On odd clock cycles, write to PPU OAM + ppu.pOAM[dma_addr] = dma_data; + // Increment the lo byte of the address + dma_addr++; + // If this wraps around, we know that 256 + // bytes have been written, so end the DMA + // transfer, and proceed as normal + if (dma_addr == 0x00) + { + dma_transfer = false; + dma_dummy = true; + } + } + } + } + else + { + // No DMA happening, the CPU is in control of its + // own destiny. Go forth my friend and calculate + // awesomeness for many generations to come... + cpu.clock(); + } + } + + // Synchronising with Audio + bool bAudioSampleReady = false; + dAudioTime += dAudioTimePerNESClock; + if (dAudioTime >= dAudioTimePerSystemSample) + { + dAudioTime -= dAudioTimePerSystemSample; + dAudioSample = apu.GetOutputSample(); + bAudioSampleReady = true; + } + + // The PPU is capable of emitting an interrupt to indicate the + // vertical blanking period has been entered. If it has, we need + // to send that irq to the CPU. + if (ppu.nmi) + { + ppu.nmi = false; + cpu.nmi(); + } + + + // Check if cartridge is requesting IRQ + if (cart->GetMapper()->irqState()) + { + cart->GetMapper()->irqClear(); + cpu.irq(); + } + + nSystemClockCounter++; + + return bAudioSampleReady; +} diff --git a/Part #7 - Mappers & Basic Sounds/Bus.h b/Part #7 - Mappers & Basic Sounds/Bus.h new file mode 100644 index 0000000..b051770 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Bus.h @@ -0,0 +1,140 @@ +/* + olc::NES - System Bus + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + + +#pragma once +#include +#include + +#include "olc6502.h" +#include "olc2C02.h" +#include "olc2A03.h" +#include "Cartridge.h" + +class Bus +{ +public: + Bus(); + ~Bus(); + +public: // Devices on Main Bus + + // The 6502 derived processor + olc6502 cpu; + // The 2C02 Picture Processing Unit + olc2C02 ppu; + // The "2A03" Audio Processing Unit + olc2A03 apu; + // The Cartridge or "GamePak" + std::shared_ptr cart; + // 2KB of RAM + uint8_t cpuRam[2048]; + // Controllers + uint8_t controller[2]; + + // Synchronisation with system Audio +public: + void SetSampleFrequency(uint32_t sample_rate); + double dAudioSample = 0.0; + +private: + double dAudioTime = 0.0; + double dAudioGlobalTime = 0.0; + double dAudioTimePerNESClock = 0.0; + double dAudioTimePerSystemSample = 0.0f; + + +public: // Main Bus Read & Write + void cpuWrite(uint16_t addr, uint8_t data); + uint8_t cpuRead(uint16_t addr, bool bReadOnly = false); + +private: + // A count of how many clocks have passed + uint32_t nSystemClockCounter = 0; + // Internal cache of controller state + uint8_t controller_state[2]; + +private: + // A simple form of Direct Memory Access is used to swiftly + // transfer data from CPU bus memory into the OAM memory. It would + // take too long to sensibly do this manually using a CPU loop, so + // the program prepares a page of memory with the sprite info required + // for the next frame and initiates a DMA transfer. This suspends the + // CPU momentarily while the PPU gets sent data at PPU clock speeds. + // Note here, that dma_page and dma_addr form a 16-bit address in + // the CPU bus address space + uint8_t dma_page = 0x00; + uint8_t dma_addr = 0x00; + uint8_t dma_data = 0x00; + + // DMA transfers need to be timed accurately. In principle it takes + // 512 cycles to read and write the 256 bytes of the OAM memory, a + // read followed by a write. However, the CPU needs to be on an "even" + // clock cycle, so a dummy cycle of idleness may be required + bool dma_dummy = true; + + // Finally a flag to indicate that a DMA transfer is happening + bool dma_transfer = false; + +public: // System Interface + // Connects a cartridge object to the internal buses + void insertCartridge(const std::shared_ptr& cartridge); + // Resets the system + void reset(); + // Clocks the system - a single whole system tick + bool clock(); +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Cartridge.cpp b/Part #7 - Mappers & Basic Sounds/Cartridge.cpp new file mode 100644 index 0000000..ed4def6 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Cartridge.cpp @@ -0,0 +1,256 @@ +/* + olc::NES - Cartridge + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/-THeUXqR3zY + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Cartridge.h" + +Cartridge::Cartridge(const std::string& sFileName) +{ + // iNES Format Header + struct sHeader + { + char name[4]; + uint8_t prg_rom_chunks; + uint8_t chr_rom_chunks; + uint8_t mapper1; + uint8_t mapper2; + uint8_t prg_ram_size; + uint8_t tv_system1; + uint8_t tv_system2; + char unused[5]; + } header; + + bImageValid = false; + + std::ifstream ifs; + ifs.open(sFileName, std::ifstream::binary); + if (ifs.is_open()) + { + // Read file header + ifs.read((char*)&header, sizeof(sHeader)); + + // If a "trainer" exists we just need to read past + // it before we get to the good stuff + if (header.mapper1 & 0x04) + ifs.seekg(512, std::ios_base::cur); + + // Determine Mapper ID + nMapperID = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4); + hw_mirror = (header.mapper1 & 0x01) ? VERTICAL : HORIZONTAL; + + // "Discover" File Format + uint8_t nFileType = 1; + if ((header.mapper2 & 0x0C) == 0x08) nFileType = 2; + + if (nFileType == 0) + { + + } + + if (nFileType == 1) + { + nPRGBanks = header.prg_rom_chunks; + vPRGMemory.resize(nPRGBanks * 16384); + ifs.read((char*)vPRGMemory.data(), vPRGMemory.size()); + + nCHRBanks = header.chr_rom_chunks; + if (nCHRBanks == 0) + { + // Create CHR RAM + vCHRMemory.resize(8192); + } + else + { + // Allocate for ROM + vCHRMemory.resize(nCHRBanks * 8192); + } + ifs.read((char*)vCHRMemory.data(), vCHRMemory.size()); + } + + if (nFileType == 2) + { + nPRGBanks = ((header.prg_ram_size & 0x07) << 8) | header.prg_rom_chunks; + vPRGMemory.resize(nPRGBanks * 16384); + ifs.read((char*)vPRGMemory.data(), vPRGMemory.size()); + + nCHRBanks = ((header.prg_ram_size & 0x38) << 8) | header.chr_rom_chunks; + vCHRMemory.resize(nCHRBanks * 8192); + ifs.read((char*)vCHRMemory.data(), vCHRMemory.size()); + } + + // Load appropriate mapper + switch (nMapperID) + { + case 0: pMapper = std::make_shared(nPRGBanks, nCHRBanks); break; + case 1: pMapper = std::make_shared(nPRGBanks, nCHRBanks); break; + case 2: pMapper = std::make_shared(nPRGBanks, nCHRBanks); break; + case 3: pMapper = std::make_shared(nPRGBanks, nCHRBanks); break; + case 4: pMapper = std::make_shared(nPRGBanks, nCHRBanks); break; + case 66: pMapper = std::make_shared(nPRGBanks, nCHRBanks); break; + + } + + bImageValid = true; + ifs.close(); + } + +} + + +Cartridge::~Cartridge() +{ +} + +bool Cartridge::ImageValid() +{ + return bImageValid; +} + +bool Cartridge::cpuRead(uint16_t addr, uint8_t &data) +{ + uint32_t mapped_addr = 0; + if (pMapper->cpuMapRead(addr, mapped_addr, data)) + { + if (mapped_addr == 0xFFFFFFFF) + { + // Mapper has actually set the data value, for example cartridge based RAM + return true; + } + else + { + // Mapper has produced an offset into cartridge bank memory + data = vPRGMemory[mapped_addr]; + } + return true; + } + else + return false; +} + +bool Cartridge::cpuWrite(uint16_t addr, uint8_t data) +{ + uint32_t mapped_addr = 0; + if (pMapper->cpuMapWrite(addr, mapped_addr, data)) + { + if (mapped_addr == 0xFFFFFFFF) + { + // Mapper has actually set the data value, for example cartridge based RAM + return true; + } + else + { + // Mapper has produced an offset into cartridge bank memory + vPRGMemory[mapped_addr] = data; + } + return true; + } + else + return false; +} + +bool Cartridge::ppuRead(uint16_t addr, uint8_t & data) +{ + uint32_t mapped_addr = 0; + if (pMapper->ppuMapRead(addr, mapped_addr)) + { + data = vCHRMemory[mapped_addr]; + return true; + } + else + return false; +} + +bool Cartridge::ppuWrite(uint16_t addr, uint8_t data) +{ + uint32_t mapped_addr = 0; + if (pMapper->ppuMapWrite(addr, mapped_addr)) + { + vCHRMemory[mapped_addr] = data; + return true; + } + else + return false; +} + + +void Cartridge::reset() +{ + // Note: This does not reset the ROM contents, + // but does reset the mapper. + if (pMapper != nullptr) + pMapper->reset(); +} + +MIRROR Cartridge::Mirror() +{ + MIRROR m = pMapper->mirror(); + if (m == MIRROR::HARDWARE) + { + // Mirror configuration was defined + // in hardware via soldering + return hw_mirror; + } + else + { + // Mirror configuration can be + // dynamically set via mapper + return m; + } +} + +std::shared_ptr Cartridge::GetMapper() +{ + return pMapper; +} diff --git a/Part #7 - Mappers & Basic Sounds/Cartridge.h b/Part #7 - Mappers & Basic Sounds/Cartridge.h new file mode 100644 index 0000000..ab1c858 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Cartridge.h @@ -0,0 +1,113 @@ +/* + olc::NES - Cartridge + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/-THeUXqR3zY + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once +#include +#include +#include +#include + + +#include "Mapper_000.h" +#include "Mapper_001.h" +#include "Mapper_002.h" +#include "Mapper_003.h" +#include "Mapper_004.h" +#include "Mapper_066.h" + +class Cartridge +{ +public: + Cartridge(const std::string& sFileName); + ~Cartridge(); + + +public: + bool ImageValid(); + + + +private: + bool bImageValid = false; + MIRROR hw_mirror = HORIZONTAL; + + uint8_t nMapperID = 0; + uint8_t nPRGBanks = 0; + uint8_t nCHRBanks = 0; + + std::vector vPRGMemory; + std::vector vCHRMemory; + + std::shared_ptr pMapper; + +public: + // Communication with Main Bus + bool cpuRead(uint16_t addr, uint8_t &data); + bool cpuWrite(uint16_t addr, uint8_t data); + + // Communication with PPU Bus + bool ppuRead(uint16_t addr, uint8_t &data); + bool ppuWrite(uint16_t addr, uint8_t data); + + // Permits system rest of mapper to know state + void reset(); + + // Get Mirror configuration + MIRROR Mirror(); + + std::shared_ptr GetMapper(); +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper.cpp b/Part #7 - Mappers & Basic Sounds/Mapper.cpp new file mode 100644 index 0000000..629bd33 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper.cpp @@ -0,0 +1,93 @@ +/* + olc::NES - Mapper Base Class (Abstract) + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Mapper.h" + +Mapper::Mapper(uint8_t prgBanks, uint8_t chrBanks) +{ + nPRGBanks = prgBanks; + nCHRBanks = chrBanks; + + reset(); +} + + +Mapper::~Mapper() +{ +} + +void Mapper::reset() +{ + +} + +MIRROR Mapper::mirror() +{ + return MIRROR::HARDWARE; +} + +bool Mapper::irqState() +{ + return false; +} + +void Mapper::irqClear() +{ +} + +void Mapper::scanline() +{ +} diff --git a/Part #7 - Mappers & Basic Sounds/Mapper.h b/Part #7 - Mappers & Basic Sounds/Mapper.h new file mode 100644 index 0000000..fd473ee --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper.h @@ -0,0 +1,102 @@ +/* + olc::NES - Mapper Base Class (Abstract) + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once +#include + +enum MIRROR +{ + HARDWARE, + HORIZONTAL, + VERTICAL, + ONESCREEN_LO, + ONESCREEN_HI, +}; + +class Mapper +{ +public: + Mapper(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper(); + +public: + // Transform CPU bus address into PRG ROM offset + virtual bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) = 0; + virtual bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) = 0; + + // Transform PPU bus address into CHR ROM offset + virtual bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) = 0; + virtual bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) = 0; + + // Reset mapper to known state + virtual void reset() = 0; + + // Get Mirror mode if mapper is in control + virtual MIRROR mirror(); + + // IRQ Interface + virtual bool irqState(); + virtual void irqClear(); + + // Scanline Counting + virtual void scanline(); + +protected: + // These are stored locally as many of the mappers require this information + uint8_t nPRGBanks = 0; + uint8_t nCHRBanks = 0; +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_000.cpp b/Part #7 - Mappers & Basic Sounds/Mapper_000.cpp new file mode 100644 index 0000000..fbb88e1 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_000.cpp @@ -0,0 +1,129 @@ +/* + olc::NES - Mapper 000 - NROM + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ +#include "Mapper_000.h" + +Mapper_000::Mapper_000(uint8_t prgBanks, uint8_t chrBanks) : Mapper(prgBanks, chrBanks) +{ +} + + +Mapper_000::~Mapper_000() +{ +} + +void Mapper_000::reset() +{ + +} + +bool Mapper_000::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) +{ + // if PRGROM is 16KB + // CPU Address Bus PRG ROM + // 0x8000 -> 0xBFFF: Map 0x0000 -> 0x3FFF + // 0xC000 -> 0xFFFF: Mirror 0x0000 -> 0x3FFF + // if PRGROM is 32KB + // CPU Address Bus PRG ROM + // 0x8000 -> 0xFFFF: Map 0x0000 -> 0x7FFF + if (addr >= 0x8000 && addr <= 0xFFFF) + { + mapped_addr = addr & (nPRGBanks > 1 ? 0x7FFF : 0x3FFF); + return true; + } + + return false; +} + +bool Mapper_000::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data) +{ + if (addr >= 0x8000 && addr <= 0xFFFF) + { + mapped_addr = addr & (nPRGBanks > 1 ? 0x7FFF : 0x3FFF); + return true; + } + + return false; +} + +bool Mapper_000::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) +{ + // There is no mapping required for PPU + // PPU Address Bus CHR ROM + // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF + if (addr >= 0x0000 && addr <= 0x1FFF) + { + mapped_addr = addr; + return true; + } + + return false; +} + +bool Mapper_000::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr >= 0x0000 && addr <= 0x1FFF) + { + if (nCHRBanks == 0) + { + // Treat as RAM + mapped_addr = addr; + return true; + } + } + + return false; +} + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_000.h b/Part #7 - Mappers & Basic Sounds/Mapper_000.h new file mode 100644 index 0000000..b022304 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_000.h @@ -0,0 +1,76 @@ +/* + olc::NES - Mapper 000 - NROM + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once +#include "Mapper.h" + + +class Mapper_000 : public Mapper +{ +public: + Mapper_000(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper_000(); + +public: + bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; + bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) override; + bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; + bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; + void reset() override; + + // No local equipment required +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_001.cpp b/Part #7 - Mappers & Basic Sounds/Mapper_001.cpp new file mode 100644 index 0000000..742fd2c --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_001.cpp @@ -0,0 +1,301 @@ +/* + olc::NES - Mapper 1 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Mapper_001.h" + + +Mapper_001::Mapper_001(uint8_t prgBanks, uint8_t chrBanks) : Mapper(prgBanks, chrBanks) +{ + vRAMStatic.resize(32 * 1024); +} + + +Mapper_001::~Mapper_001() +{ +} + +bool Mapper_001::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) +{ + if (addr >= 0x6000 && addr <= 0x7FFF) + { + // Read is from static ram on cartridge + mapped_addr = 0xFFFFFFFF; + + // Read data from RAM + data = vRAMStatic[addr & 0x1FFF]; + + // Signal mapper has handled request + return true; + } + + if (addr >= 0x8000) + { + if (nControlRegister & 0b01000) + { + // 16K Mode + if (addr >= 0x8000 && addr <= 0xBFFF) + { + mapped_addr = nPRGBankSelect16Lo * 0x4000 + (addr & 0x3FFF); + return true; + } + + if (addr >= 0xC000 && addr <= 0xFFFF) + { + mapped_addr = nPRGBankSelect16Hi * 0x4000 + (addr & 0x3FFF); + return true; + } + } + else + { + // 32K Mode + mapped_addr = nPRGBankSelect32 * 0x8000 + (addr & 0x7FFF); + return true; + } + } + + + + return false; +} + +bool Mapper_001::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data) +{ + if (addr >= 0x6000 && addr <= 0x7FFF) + { + // Write is to static ram on cartridge + mapped_addr = 0xFFFFFFFF; + + // Write data to RAM + vRAMStatic[addr & 0x1FFF] = data; + + // Signal mapper has handled request + return true; + } + + if (addr >= 0x8000) + { + if (data & 0x80) + { + // MSB is set, so reset serial loading + nLoadRegister = 0x00; + nLoadRegisterCount = 0; + nControlRegister = nControlRegister | 0x0C; + } + else + { + // Load data in serially into load register + // It arrives LSB first, so implant this at + // bit 5. After 5 writes, the register is ready + nLoadRegister >>= 1; + nLoadRegister |= (data & 0x01) << 4; + nLoadRegisterCount++; + + if (nLoadRegisterCount == 5) + { + // Get Mapper Target Register, by examining + // bits 13 & 14 of the address + uint8_t nTargetRegister = (addr >> 13) & 0x03; + + if (nTargetRegister == 0) // 0x8000 - 0x9FFF + { + // Set Control Register + nControlRegister = nLoadRegister & 0x1F; + + switch (nControlRegister & 0x03) + { + case 0: mirrormode = ONESCREEN_LO; break; + case 1: mirrormode = ONESCREEN_HI; break; + case 2: mirrormode = VERTICAL; break; + case 3: mirrormode = HORIZONTAL; break; + } + } + else if (nTargetRegister == 1) // 0xA000 - 0xBFFF + { + // Set CHR Bank Lo + if (nControlRegister & 0b10000) + { + // 4K CHR Bank at PPU 0x0000 + nCHRBankSelect4Lo = nLoadRegister & 0x1F; + } + else + { + // 8K CHR Bank at PPU 0x0000 + nCHRBankSelect8 = nLoadRegister & 0x1E; + } + } + else if (nTargetRegister == 2) // 0xC000 - 0xDFFF + { + // Set CHR Bank Hi + if (nControlRegister & 0b10000) + { + // 4K CHR Bank at PPU 0x1000 + nCHRBankSelect4Hi = nLoadRegister & 0x1F; + } + } + else if (nTargetRegister == 3) // 0xE000 - 0xFFFF + { + // Configure PRG Banks + uint8_t nPRGMode = (nControlRegister >> 2) & 0x03; + + if (nPRGMode == 0 || nPRGMode == 1) + { + // Set 32K PRG Bank at CPU 0x8000 + nPRGBankSelect32 = (nLoadRegister & 0x0E) >> 1; + } + else if (nPRGMode == 2) + { + // Fix 16KB PRG Bank at CPU 0x8000 to First Bank + nPRGBankSelect16Lo = 0; + // Set 16KB PRG Bank at CPU 0xC000 + nPRGBankSelect16Hi = nLoadRegister & 0x0F; + } + else if (nPRGMode == 3) + { + // Set 16KB PRG Bank at CPU 0x8000 + nPRGBankSelect16Lo = nLoadRegister & 0x0F; + // Fix 16KB PRG Bank at CPU 0xC000 to Last Bank + nPRGBankSelect16Hi = nPRGBanks - 1; + } + } + + // 5 bits were written, and decoded, so + // reset load register + nLoadRegister = 0x00; + nLoadRegisterCount = 0; + } + + } + + } + + // Mapper has handled write, but do not update ROMs + return false; +} + +bool Mapper_001::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr < 0x2000) + { + if (nCHRBanks == 0) + { + mapped_addr = addr; + return true; + } + else + { + if (nControlRegister & 0b10000) + { + // 4K CHR Bank Mode + if (addr >= 0x0000 && addr <= 0x0FFF) + { + mapped_addr = nCHRBankSelect4Lo * 0x1000 + (addr & 0x0FFF); + return true; + } + + if (addr >= 0x1000 && addr <= 0x1FFF) + { + mapped_addr = nCHRBankSelect4Hi * 0x1000 + (addr & 0x0FFF); + return true; + } + } + else + { + // 8K CHR Bank Mode + mapped_addr = nCHRBankSelect8 * 0x2000 + (addr & 0x1FFF); + return true; + } + } + } + + return false; +} + +bool Mapper_001::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr < 0x2000) + { + if (nCHRBanks == 0) + { + mapped_addr = addr; + return true; + } + + return true; + } + else + return false; +} + +void Mapper_001::reset() +{ + nControlRegister = 0x1C; + nLoadRegister = 0x00; + nLoadRegisterCount = 0x00; + + nCHRBankSelect4Lo = 0; + nCHRBankSelect4Hi = 0; + nCHRBankSelect8 = 0; + + nPRGBankSelect32 = 0; + nPRGBankSelect16Lo = 0; + nPRGBankSelect16Hi = nPRGBanks - 1; +} + +MIRROR Mapper_001::mirror() +{ + + return mirrormode; +} diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_001.h b/Part #7 - Mappers & Basic Sounds/Mapper_001.h new file mode 100644 index 0000000..1cc86c2 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_001.h @@ -0,0 +1,92 @@ + +/* + olc::NES - Mapper 1 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ +#pragma once + +#include "Mapper.h" +#include + +class Mapper_001 : public Mapper +{ +public: + Mapper_001(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper_001(); + + bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; + bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) override; + bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; + bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; + void reset() override; + MIRROR mirror(); + +private: + uint8_t nCHRBankSelect4Lo = 0x00; + uint8_t nCHRBankSelect4Hi = 0x00; + uint8_t nCHRBankSelect8 = 0x00; + + uint8_t nPRGBankSelect16Lo = 0x00; + uint8_t nPRGBankSelect16Hi = 0x00; + uint8_t nPRGBankSelect32 = 0x00; + + uint8_t nLoadRegister = 0x00; + uint8_t nLoadRegisterCount = 0x00; + uint8_t nControlRegister = 0x00; + + MIRROR mirrormode = MIRROR::HORIZONTAL; + + std::vector vRAMStatic; +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_002.cpp b/Part #7 - Mappers & Basic Sounds/Mapper_002.cpp new file mode 100644 index 0000000..88dcf9e --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_002.cpp @@ -0,0 +1,125 @@ +/* + olc::NES - Mapper 2 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Mapper_002.h" + + +Mapper_002::Mapper_002(uint8_t prgBanks, uint8_t chrBanks) : Mapper(prgBanks, chrBanks) +{ +} + + +Mapper_002::~Mapper_002() +{ +} + +bool Mapper_002::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) +{ + if (addr >= 0x8000 && addr <= 0xBFFF) + { + mapped_addr = nPRGBankSelectLo * 0x4000 + (addr & 0x3FFF); + return true; + } + + if (addr >= 0xC000 && addr <= 0xFFFF) + { + mapped_addr = nPRGBankSelectHi * 0x4000 + (addr & 0x3FFF); + return true; + } + + return false; +} + +bool Mapper_002::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data) +{ + if (addr >= 0x8000 && addr <= 0xFFFF) + { + nPRGBankSelectLo = data & 0x0F; + } + + // Mapper has handled write, but do not update ROMs + return false; +} + +bool Mapper_002::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr < 0x2000) + { + mapped_addr = addr; + return true; + } + else + return false; +} + +bool Mapper_002::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr < 0x2000) + { + if (nCHRBanks == 0) // Treating as RAM + { + mapped_addr = addr; + return true; + } + } + return false; +} + +void Mapper_002::reset() +{ + nPRGBankSelectLo = 0; + nPRGBankSelectHi = nPRGBanks - 1; +} diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_002.h b/Part #7 - Mappers & Basic Sounds/Mapper_002.h new file mode 100644 index 0000000..1e69820 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_002.h @@ -0,0 +1,75 @@ +/* + olc::NES - Mapper 2 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once +#include "Mapper.h" +class Mapper_002 : public Mapper +{ +public: + Mapper_002(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper_002(); + + bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; + bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) override; + bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; + bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; + void reset() override; + +private: + uint8_t nPRGBankSelectLo = 0x00; + uint8_t nPRGBankSelectHi = 0x00; +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_003.cpp b/Part #7 - Mappers & Basic Sounds/Mapper_003.cpp new file mode 100644 index 0000000..5295088 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_003.cpp @@ -0,0 +1,113 @@ +/* + olc::NES - Mapper 3 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Mapper_003.h" + +Mapper_003::Mapper_003(uint8_t prgBanks, uint8_t chrBanks) : Mapper(prgBanks, chrBanks) +{ +} + + +Mapper_003::~Mapper_003() +{ +} + +bool Mapper_003::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) +{ + if (addr >= 0x8000 && addr <= 0xFFFF) + { + if (nPRGBanks == 1) // 16K ROM + mapped_addr = addr & 0x3FFF; + if (nPRGBanks == 2) // 32K ROM + mapped_addr = addr & 0x7FFF; + return true; + } + else + return false; +} + +bool Mapper_003::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data) +{ + if (addr >= 0x8000 && addr <= 0xFFFF) + { + nCHRBankSelect = data & 0x03; + mapped_addr = addr; + } + + // Mapper has handled write, but do not update ROMs + return false; +} + +bool Mapper_003::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr < 0x2000) + { + mapped_addr = nCHRBankSelect * 0x2000 + addr; + return true; + } + else + return false; +} + +bool Mapper_003::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) +{ + return false; +} + +void Mapper_003::reset() +{ + nCHRBankSelect = 0; +} diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_003.h b/Part #7 - Mappers & Basic Sounds/Mapper_003.h new file mode 100644 index 0000000..a81a246 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_003.h @@ -0,0 +1,77 @@ +/* + olc::NES - Mapper 3 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ +#pragma once +#include "Mapper.h" + + +class Mapper_003 : public Mapper +{ +public: + Mapper_003(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper_003(); + + bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; + bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) override; + bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; + bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; + void reset() override; + +private: + uint8_t nCHRBankSelect = 0x00; + + +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_004.cpp b/Part #7 - Mappers & Basic Sounds/Mapper_004.cpp new file mode 100644 index 0000000..4c62aad --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_004.cpp @@ -0,0 +1,344 @@ +/* + olc::NES - Mapper 4 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Mapper_004.h" + +Mapper_004::Mapper_004(uint8_t prgBanks, uint8_t chrBanks) : Mapper(prgBanks, chrBanks) +{ + vRAMStatic.resize(32 * 1024); +} + + +Mapper_004::~Mapper_004() +{ +} + +bool Mapper_004::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) +{ + if (addr >= 0x6000 && addr <= 0x7FFF) + { + // Write is to static ram on cartridge + mapped_addr = 0xFFFFFFFF; + + // Write data to RAM + data = vRAMStatic[addr & 0x1FFF]; + + // Signal mapper has handled request + return true; + } + + + if (addr >= 0x8000 && addr <= 0x9FFF) + { + mapped_addr = pPRGBank[0] + (addr & 0x1FFF); + return true; + } + + if (addr >= 0xA000 && addr <= 0xBFFF) + { + mapped_addr = pPRGBank[1] + (addr & 0x1FFF); + return true; + } + + if (addr >= 0xC000 && addr <= 0xDFFF) + { + mapped_addr = pPRGBank[2] + (addr & 0x1FFF); + return true; + } + + if (addr >= 0xE000 && addr <= 0xFFFF) + { + mapped_addr = pPRGBank[3] + (addr & 0x1FFF); + return true; + } + + return false; +} + +bool Mapper_004::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data) +{ + if (addr >= 0x6000 && addr <= 0x7FFF) + { + // Write is to static ram on cartridge + mapped_addr = 0xFFFFFFFF; + + // Write data to RAM + vRAMStatic[addr & 0x1FFF] = data; + + // Signal mapper has handled request + return true; + } + + if (addr >= 0x8000 && addr <= 0x9FFF) + { + // Bank Select + if (!(addr & 0x0001)) + { + nTargetRegister = data & 0x07; + bPRGBankMode = (data & 0x40); + bCHRInversion = (data & 0x80); + } + else + { + // Update target register + pRegister[nTargetRegister] = data; + + // Update Pointer Table + if (bCHRInversion) + { + pCHRBank[0] = pRegister[2] * 0x0400; + pCHRBank[1] = pRegister[3] * 0x0400; + pCHRBank[2] = pRegister[4] * 0x0400; + pCHRBank[3] = pRegister[5] * 0x0400; + pCHRBank[4] = (pRegister[0] & 0xFE) * 0x0400; + pCHRBank[5] = pRegister[0] * 0x0400 + 0x0400; + pCHRBank[6] = (pRegister[1] & 0xFE) * 0x0400; + pCHRBank[7] = pRegister[1] * 0x0400 + 0x0400; + } + else + { + pCHRBank[0] = (pRegister[0] & 0xFE) * 0x0400; + pCHRBank[1] = pRegister[0] * 0x0400 + 0x0400; + pCHRBank[2] = (pRegister[1] & 0xFE) * 0x0400; + pCHRBank[3] = pRegister[1] * 0x0400 + 0x0400; + pCHRBank[4] = pRegister[2] * 0x0400; + pCHRBank[5] = pRegister[3] * 0x0400; + pCHRBank[6] = pRegister[4] * 0x0400; + pCHRBank[7] = pRegister[5] * 0x0400; + } + + if (bPRGBankMode) + { + pPRGBank[2] = (pRegister[6] & 0x3F) * 0x2000; + pPRGBank[0] = (nPRGBanks * 2 - 2) * 0x2000; + } + else + { + pPRGBank[0] = (pRegister[6] & 0x3F) * 0x2000; + pPRGBank[2] = (nPRGBanks * 2 - 2) * 0x2000; + } + + pPRGBank[1] = (pRegister[7] & 0x3F) * 0x2000; + pPRGBank[3] = (nPRGBanks * 2 - 1) * 0x2000; + + } + + return false; + } + + if (addr >= 0xA000 && addr <= 0xBFFF) + { + if (!(addr & 0x0001)) + { + // Mirroring + if (data & 0x01) + mirrormode = MIRROR::HORIZONTAL; + else + mirrormode = MIRROR::VERTICAL; + } + else + { + // PRG Ram Protect + // TODO: + } + return false; + } + + if (addr >= 0xC000 && addr <= 0xDFFF) + { + if (!(addr & 0x0001)) + { + nIRQReload = data; + } + else + { + nIRQCounter = 0x0000; + } + return false; + } + + if (addr >= 0xE000 && addr <= 0xFFFF) + { + if (!(addr & 0x0001)) + { + bIRQEnable = false; + bIRQActive = false; + } + else + { + bIRQEnable = true; + } + return false; + } + + + + return false; +} + +bool Mapper_004::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr >= 0x0000 && addr <= 0x03FF) + { + mapped_addr = pCHRBank[0] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x0400 && addr <= 0x07FF) + { + mapped_addr = pCHRBank[1] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x0800 && addr <= 0x0BFF) + { + mapped_addr = pCHRBank[2] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x0C00 && addr <= 0x0FFF) + { + mapped_addr = pCHRBank[3] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x1000 && addr <= 0x13FF) + { + mapped_addr = pCHRBank[4] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x1400 && addr <= 0x17FF) + { + mapped_addr = pCHRBank[5] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x1800 && addr <= 0x1BFF) + { + mapped_addr = pCHRBank[6] + (addr & 0x03FF); + return true; + } + + if (addr >= 0x1C00 && addr <= 0x1FFF) + { + mapped_addr = pCHRBank[7] + (addr & 0x03FF); + return true; + } + + return false; +} + +bool Mapper_004::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) +{ + + + return false; +} + +void Mapper_004::reset() +{ + nTargetRegister = 0x00; + bPRGBankMode = false; + bCHRInversion = false; + mirrormode = MIRROR::HORIZONTAL; + + bIRQActive = false; + bIRQEnable = false; + bIRQUpdate = false; + nIRQCounter = 0x0000; + nIRQReload = 0x0000; + + for (int i = 0; i < 4; i++) pPRGBank[i] = 0; + for (int i = 0; i < 8; i++) { pCHRBank[i] = 0; pRegister[i] = 0; } + + pPRGBank[0] = 0 * 0x2000; + pPRGBank[1] = 1 * 0x2000; + pPRGBank[2] = (nPRGBanks * 2 - 2) * 0x2000; + pPRGBank[3] = (nPRGBanks * 2 - 1) * 0x2000; +} + + +bool Mapper_004::irqState() +{ + return bIRQActive; +} + +void Mapper_004::irqClear() +{ + bIRQActive = false; +} + +void Mapper_004::scanline() +{ + if (nIRQCounter == 0) + { + nIRQCounter = nIRQReload; + } + else + nIRQCounter--; + + if (nIRQCounter == 0 && bIRQEnable) + { + bIRQActive = true; + } + +} + +MIRROR Mapper_004::mirror() +{ + return mirrormode; +} diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_004.h b/Part #7 - Mappers & Basic Sounds/Mapper_004.h new file mode 100644 index 0000000..fc5eca3 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_004.h @@ -0,0 +1,98 @@ +/* + olc::NES - Mapper 4 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once +#include "Mapper.h" +#include + +class Mapper_004 : public Mapper +{ +public: + Mapper_004(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper_004(); + + bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; + bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) override; + bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; + bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; + void reset() override; + + bool irqState() override; + void irqClear() override; + + void scanline() override; + MIRROR mirror() override; + +private: + // Control variables + uint8_t nTargetRegister = 0x00; + bool bPRGBankMode = false; + bool bCHRInversion = false; + MIRROR mirrormode = MIRROR::HORIZONTAL; + + uint32_t pRegister[8]; + uint32_t pCHRBank[8]; + uint32_t pPRGBank[4]; + + bool bIRQActive = false; + bool bIRQEnable = false; + bool bIRQUpdate = false; + uint16_t nIRQCounter = 0x0000; + uint16_t nIRQReload = 0x0000; + + std::vector vRAMStatic; +}; + diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_066.cpp b/Part #7 - Mappers & Basic Sounds/Mapper_066.cpp new file mode 100644 index 0000000..8e0a07b --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_066.cpp @@ -0,0 +1,111 @@ +/* + olc::NES - Mapper 66 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "Mapper_066.h" + +Mapper_066::Mapper_066(uint8_t prgBanks, uint8_t chrBanks) : Mapper(prgBanks, chrBanks) +{ +} + + +Mapper_066::~Mapper_066() +{ +} + +bool Mapper_066::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) +{ + if (addr >= 0x8000 && addr <= 0xFFFF) + { + mapped_addr = nPRGBankSelect * 0x8000 + (addr & 0x7FFF); + return true; + } + else + return false; +} + +bool Mapper_066::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data) +{ + if (addr >= 0x8000 && addr <= 0xFFFF) + { + nCHRBankSelect = data & 0x03; + nPRGBankSelect = (data & 0x30) >> 4; + } + + // Mapper has handled write, but do not update ROMs + return false; +} + +bool Mapper_066::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) +{ + if (addr < 0x2000) + { + mapped_addr = nCHRBankSelect * 0x2000 + addr; + return true; + } + else + return false; +} + +bool Mapper_066::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) +{ + return false; +} + +void Mapper_066::reset() +{ + nCHRBankSelect = 0; + nPRGBankSelect = 0; +} diff --git a/Part #7 - Mappers & Basic Sounds/Mapper_066.h b/Part #7 - Mappers & Basic Sounds/Mapper_066.h new file mode 100644 index 0000000..8e7f2a2 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/Mapper_066.h @@ -0,0 +1,75 @@ + +/* + olc::NES - Mapper 66 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ +#pragma once +#include "Mapper.h" +class Mapper_066 : public Mapper +{ +public: + Mapper_066(uint8_t prgBanks, uint8_t chrBanks); + ~Mapper_066(); + + bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; + bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, uint8_t data = 0) override; + bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; + bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; + void reset() override; + +private: + uint8_t nCHRBankSelect = 0x00; + uint8_t nPRGBankSelect = 0x00; +}; + diff --git a/Part #7 - Mappers & Basic Sounds/olc2A03.cpp b/Part #7 - Mappers & Basic Sounds/olc2A03.cpp new file mode 100644 index 0000000..9953fe5 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olc2A03.cpp @@ -0,0 +1,363 @@ +#include "olc2A03.h" + +/* + olc::NES - APU + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +uint8_t olc2A03::length_table[] = { 10, 254, 20, 2, 40, 4, 80, 6, + 160, 8, 60, 10, 14, 12, 26, 14, + 12, 16, 24, 18, 48, 20, 96, 22, + 192, 24, 72, 26, 16, 28, 32, 30 }; + +olc2A03::olc2A03() +{ + noise_seq.sequence = 0xDBDB; +} + + +olc2A03::~olc2A03() +{ +} + +void olc2A03::cpuWrite(uint16_t addr, uint8_t data) +{ + switch (addr) + { + case 0x4000: + switch ((data & 0xC0) >> 6) + { + case 0x00: pulse1_seq.new_sequence = 0b01000000; pulse1_osc.dutycycle = 0.125; break; + case 0x01: pulse1_seq.new_sequence = 0b01100000; pulse1_osc.dutycycle = 0.250; break; + case 0x02: pulse1_seq.new_sequence = 0b01111000; pulse1_osc.dutycycle = 0.500; break; + case 0x03: pulse1_seq.new_sequence = 0b10011111; pulse1_osc.dutycycle = 0.750; break; + } + pulse1_seq.sequence = pulse1_seq.new_sequence; + pulse1_halt = (data & 0x20); + pulse1_env.volume = (data & 0x0F); + pulse1_env.disable = (data & 0x10); + break; + + case 0x4001: + pulse1_sweep.enabled = data & 0x80; + pulse1_sweep.period = (data & 0x70) >> 4; + pulse1_sweep.down = data & 0x08; + pulse1_sweep.shift = data & 0x07; + pulse1_sweep.reload = true; + break; + + case 0x4002: + pulse1_seq.reload = (pulse1_seq.reload & 0xFF00) | data; + break; + + case 0x4003: + pulse1_seq.reload = (uint16_t)((data & 0x07)) << 8 | (pulse1_seq.reload & 0x00FF); + pulse1_seq.timer = pulse1_seq.reload; + pulse1_seq.sequence = pulse1_seq.new_sequence; + pulse1_lc.counter = length_table[(data & 0xF8) >> 3]; + pulse1_env.start = true; + break; + + case 0x4004: + switch ((data & 0xC0) >> 6) + { + case 0x00: pulse2_seq.new_sequence = 0b01000000; pulse2_osc.dutycycle = 0.125; break; + case 0x01: pulse2_seq.new_sequence = 0b01100000; pulse2_osc.dutycycle = 0.250; break; + case 0x02: pulse2_seq.new_sequence = 0b01111000; pulse2_osc.dutycycle = 0.500; break; + case 0x03: pulse2_seq.new_sequence = 0b10011111; pulse2_osc.dutycycle = 0.750; break; + } + pulse2_seq.sequence = pulse2_seq.new_sequence; + pulse2_halt = (data & 0x20); + pulse2_env.volume = (data & 0x0F); + pulse2_env.disable = (data & 0x10); + break; + + case 0x4005: + pulse2_sweep.enabled = data & 0x80; + pulse2_sweep.period = (data & 0x70) >> 4; + pulse2_sweep.down = data & 0x08; + pulse2_sweep.shift = data & 0x07; + pulse2_sweep.reload = true; + break; + + case 0x4006: + pulse2_seq.reload = (pulse2_seq.reload & 0xFF00) | data; + break; + + case 0x4007: + pulse2_seq.reload = (uint16_t)((data & 0x07)) << 8 | (pulse2_seq.reload & 0x00FF); + pulse2_seq.timer = pulse2_seq.reload; + pulse2_seq.sequence = pulse2_seq.new_sequence; + pulse2_lc.counter = length_table[(data & 0xF8) >> 3]; + pulse2_env.start = true; + + break; + + case 0x4008: + break; + + case 0x400C: + noise_env.volume = (data & 0x0F); + noise_env.disable = (data & 0x10); + noise_halt = (data & 0x20); + break; + + case 0x400E: + switch (data & 0x0F) + { + case 0x00: noise_seq.reload = 0; break; + case 0x01: noise_seq.reload = 4; break; + case 0x02: noise_seq.reload = 8; break; + case 0x03: noise_seq.reload = 16; break; + case 0x04: noise_seq.reload = 32; break; + case 0x05: noise_seq.reload = 64; break; + case 0x06: noise_seq.reload = 96; break; + case 0x07: noise_seq.reload = 128; break; + case 0x08: noise_seq.reload = 160; break; + case 0x09: noise_seq.reload = 202; break; + case 0x0A: noise_seq.reload = 254; break; + case 0x0B: noise_seq.reload = 380; break; + case 0x0C: noise_seq.reload = 508; break; + case 0x0D: noise_seq.reload = 1016; break; + case 0x0E: noise_seq.reload = 2034; break; + case 0x0F: noise_seq.reload = 4068; break; + } + break; + + case 0x4015: // APU STATUS + pulse1_enable = data & 0x01; + pulse2_enable = data & 0x02; + noise_enable = data & 0x04; + break; + + case 0x400F: + pulse1_env.start = true; + pulse2_env.start = true; + noise_env.start = true; + noise_lc.counter = length_table[(data & 0xF8) >> 3]; + break; + } +} + +uint8_t olc2A03::cpuRead(uint16_t addr) +{ + uint8_t data = 0x00; + + if (addr == 0x4015) + { + // data |= (pulse1_lc.counter > 0) ? 0x01 : 0x00; + // data |= (pulse2_lc.counter > 0) ? 0x02 : 0x00; + // data |= (noise_lc.counter > 0) ? 0x04 : 0x00; + } + + return data; +} + +void olc2A03::clock() +{ + // Depending on the frame count, we set a flag to tell + // us where we are in the sequence. Essentially, changes + // to notes only occur at these intervals, meaning, in a + // way, this is responsible for ensuring musical time is + // maintained. + bool bQuarterFrameClock = false; + bool bHalfFrameClock = false; + + dGlobalTime += (0.3333333333 / 1789773); + + + if (clock_counter % 6 == 0) + { + frame_clock_counter++; + + + // 4-Step Sequence Mode + if (frame_clock_counter == 3729) + { + bQuarterFrameClock = true; + } + + if (frame_clock_counter == 7457) + { + bQuarterFrameClock = true; + bHalfFrameClock = true; + } + + if (frame_clock_counter == 11186) + { + bQuarterFrameClock = true; + } + + if (frame_clock_counter == 14916) + { + bQuarterFrameClock = true; + bHalfFrameClock = true; + frame_clock_counter = 0; + } + + // Update functional units + + // Quater frame "beats" adjust the volume envelope + if (bQuarterFrameClock) + { + pulse1_env.clock(pulse1_halt); + pulse2_env.clock(pulse2_halt); + noise_env.clock(noise_halt); + } + + + // Half frame "beats" adjust the note length and + // frequency sweepers + if (bHalfFrameClock) + { + pulse1_lc.clock(pulse1_enable, pulse1_halt); + pulse2_lc.clock(pulse2_enable, pulse2_halt); + noise_lc.clock(noise_enable, noise_halt); + pulse1_sweep.clock(pulse1_seq.reload, 0); + pulse2_sweep.clock(pulse2_seq.reload, 1); + } + + // if (bUseRawMode) + { + // Update Pulse1 Channel ================================ + pulse1_seq.clock(pulse1_enable, [](uint32_t &s) + { + // Shift right by 1 bit, wrapping around + s = ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1); + }); + + // pulse1_sample = (double)pulse1_seq.output; + } + //else + { + pulse1_osc.frequency = 1789773.0 / (16.0 * (double)(pulse1_seq.reload + 1)); + pulse1_osc.amplitude = (double)(pulse1_env.output -1) / 16.0; + pulse1_sample = pulse1_osc.sample(dGlobalTime); + + if (pulse1_lc.counter > 0 && pulse1_seq.timer >= 8 && !pulse1_sweep.mute && pulse1_env.output > 2) + pulse1_output += (pulse1_sample - pulse1_output) * 0.5; + else + pulse1_output = 0; + } + + //if (bUseRawMode) + { + // Update Pulse1 Channel ================================ + pulse2_seq.clock(pulse2_enable, [](uint32_t &s) + { + // Shift right by 1 bit, wrapping around + s = ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1); + }); + + // pulse2_sample = (double)pulse2_seq.output; + + } + // else + { + pulse2_osc.frequency = 1789773.0 / (16.0 * (double)(pulse2_seq.reload + 1)); + pulse2_osc.amplitude = (double)(pulse2_env.output-1) / 16.0; + pulse2_sample = pulse2_osc.sample(dGlobalTime); + + if (pulse2_lc.counter > 0 && pulse2_seq.timer >= 8 && !pulse2_sweep.mute && pulse2_env.output > 2) + pulse2_output += (pulse2_sample - pulse2_output) * 0.5; + else + pulse2_output = 0; + } + + + noise_seq.clock(noise_enable, [](uint32_t &s) + { + s = (((s & 0x0001) ^ ((s & 0x0002) >> 1)) << 14) | ((s & 0x7FFF) >> 1); + }); + + if (noise_lc.counter > 0 && noise_seq.timer >= 8) + { + noise_output = (double)noise_seq.output * ((double)(noise_env.output-1) / 16.0); + } + + if (!pulse1_enable) pulse1_output = 0; + if (!pulse2_enable) pulse2_output = 0; + if (!noise_enable) noise_output = 0; + + } + + // Frequency sweepers change at high frequency + pulse1_sweep.track(pulse1_seq.reload); + pulse2_sweep.track(pulse2_seq.reload); + + pulse1_visual = (pulse1_enable && pulse1_env.output > 1 && !pulse1_sweep.mute) ? pulse1_seq.reload : 2047; + pulse2_visual = (pulse2_enable && pulse2_env.output > 1 && !pulse2_sweep.mute) ? pulse2_seq.reload : 2047; + noise_visual = (noise_enable && noise_env.output > 1) ? noise_seq.reload : 2047; + + clock_counter++; +} + +double olc2A03::GetOutputSample() +{ + if (bUseRawMode) + { + return (pulse1_sample - 0.5) * 0.5 + + (pulse2_sample - 0.5) * 0.5; + } + else + { + return ((1.0 * pulse1_output) - 0.8) * 0.1 + + ((1.0 * pulse2_output) - 0.8) * 0.1 + + ((2.0 * (noise_output - 0.5))) * 0.1; + } +} + +void olc2A03::reset() +{ +} diff --git a/Part #7 - Mappers & Basic Sounds/olc2A03.h b/Part #7 - Mappers & Basic Sounds/olc2A03.h new file mode 100644 index 0000000..63995f3 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olc2A03.h @@ -0,0 +1,338 @@ +/* + olc::NES - APU + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + + +/* + +IMPORTANT !!!! + +THIS CLASS IS VERY UNFINISHED + +*/ +#pragma once + +#include +#include + +class olc2A03 +{ +public: + olc2A03(); + ~olc2A03(); + +public: + void cpuWrite(uint16_t addr, uint8_t data); + uint8_t cpuRead(uint16_t addr); + void clock(); + void reset(); + + double GetOutputSample(); + +private: + uint32_t frame_clock_counter = 0; + uint32_t clock_counter = 0; + bool bUseRawMode = false; + +private: + static uint8_t length_table[]; + +private: + + // Sequencer Module + // ~~~~~~~~~~~~~~~~ + // The purpose of the sequencer is to output a '1' after a given + // interval. It does this by counting down from a start value, + // when that value is < 0, it gets reset, and an internal "rotary" + // buffer is shifted. The nature of ths shifted pattern is different + // depending upon the channel, or module that requires sequencing. + // For example, the square wave channels simply rotate the preset + // sequence, but the noise channel needs to generate pseudo-random + // outputs originating from the preset sequence. + // + // Consider a square wave channel. A preset sequence of 01010101 + // will output a 1 more freqently than 00010001, assuming we + // always output the LSB. The speed of this output is also + // governed by the timer counting down. The frequency is higher + // for small timer values, and lower for larger. Increasing + // the frequency of the output potentially increases the + // audible frequency. In fact, this is how the pulse channels + // fundamentally work. A "duty cycle" shape is loaded into the + // sequencer and the timer is used to vary the pitch, yielding + // notes. + + struct sequencer + { + uint32_t sequence = 0x00000000; + uint32_t new_sequence = 0x00000000; + uint16_t timer = 0x0000; + uint16_t reload = 0x0000; + uint8_t output = 0x00; + + // Pass in a lambda function to manipulate the sequence as required + // by the owner of this sequencer module + uint8_t clock(bool bEnable, std::function funcManip) + { + if (bEnable) + { + timer--; + if (timer == 0xFFFF) + { + timer = reload; + funcManip(sequence); + output = sequence & 0x00000001; + } + } + return output; + } + }; + + struct lengthcounter + { + uint8_t counter = 0x00; + uint8_t clock(bool bEnable, bool bHalt) + { + if (!bEnable) + counter = 0; + else + if (counter > 0 && !bHalt) + counter--; + return counter; + } + }; + + struct envelope + { + void clock(bool bLoop) + { + if (!start) + { + if (divider_count == 0) + { + divider_count = volume; + + if (decay_count == 0) + { + if (bLoop) + { + decay_count = 15; + } + + } + else + decay_count--; + } + else + divider_count--; + } + else + { + start = false; + decay_count = 15; + divider_count = volume; + } + + if (disable) + { + output = volume; + } + else + { + output = decay_count; + } + } + + bool start = false; + bool disable = false; + uint16_t divider_count = 0; + uint16_t volume = 0; + uint16_t output = 0; + uint16_t decay_count = 0; + }; + + + struct oscpulse + { + double frequency = 0; + double dutycycle = 0; + double amplitude = 1; + double pi = 3.14159; + double harmonics = 20; + + double sample(double t) + { + double a = 0; + double b = 0; + double p = dutycycle * 2.0 * pi; + + auto approxsin = [](float t) + { + float j = t * 0.15915; + j = j - (int)j; + return 20.785 * j * (j - 0.5) * (j - 1.0f); + }; + + for (double n = 1; n < harmonics; n++) + { + double c = n * frequency * 2.0 * pi * t; + a += -approxsin(c) / n; + b += -approxsin(c - p * n) / n; + + //a += -sin(c) / n; + //b += -sin(c - p * n) / n; + } + + return (2.0 * amplitude / pi) * (a - b); + } + }; + + struct sweeper + { + bool enabled = false; + bool down = false; + bool reload = false; + uint8_t shift = 0x00; + uint8_t timer = 0x00; + uint8_t period = 0x00; + uint16_t change = 0; + bool mute = false; + + void track(uint16_t &target) + { + if (enabled) + { + change = target >> shift; + mute = (target < 8) || (target > 0x7FF); + } + } + + bool clock(uint16_t &target, bool channel) + { + bool changed = false; + if (timer == 0 && enabled && shift > 0 && !mute) + { + if (target >= 8 && change < 0x07FF) + { + if (down) + { + target -= change - channel; + } + else + { + target += change; + } + changed = true; + } + } + + //if (enabled) + { + if (timer == 0 || reload) + { + timer = period; + reload = false; + } + else + timer--; + + mute = (target < 8) || (target > 0x7FF); + } + + return changed; + } + }; + + double dGlobalTime = 0.0; + + // Square Wave Pulse Channel 1 + bool pulse1_enable = false; + bool pulse1_halt = false; + double pulse1_sample = 0.0; + double pulse1_output = 0.0; + sequencer pulse1_seq; + oscpulse pulse1_osc; + envelope pulse1_env; + lengthcounter pulse1_lc; + sweeper pulse1_sweep; + + // Square Wave Pulse Channel 2 + bool pulse2_enable = false; + bool pulse2_halt = false; + double pulse2_sample = 0.0; + double pulse2_output = 0.0; + sequencer pulse2_seq; + oscpulse pulse2_osc; + envelope pulse2_env; + lengthcounter pulse2_lc; + sweeper pulse2_sweep; + + // Noise Channel + bool noise_enable = false; + bool noise_halt = false; + envelope noise_env; + lengthcounter noise_lc; + sequencer noise_seq; + double noise_sample = 0; + double noise_output = 0; + + +public: + uint16_t pulse1_visual = 0; + uint16_t pulse2_visual = 0; + uint16_t noise_visual = 0; + uint16_t triangle_visual = 0; + +}; + diff --git a/Part #7 - Mappers & Basic Sounds/olc2C02.cpp b/Part #7 - Mappers & Basic Sounds/olc2C02.cpp new file mode 100644 index 0000000..eb76b84 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olc2C02.cpp @@ -0,0 +1,1406 @@ +/* + olc::NES - Picture Processing Unit (PPU) 2C02 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/xdzOvpYPmGE + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "olc2C02.h" + +olc2C02::olc2C02() +{ + palScreen[0x00] = olc::Pixel(84, 84, 84); + palScreen[0x01] = olc::Pixel(0, 30, 116); + palScreen[0x02] = olc::Pixel(8, 16, 144); + palScreen[0x03] = olc::Pixel(48, 0, 136); + palScreen[0x04] = olc::Pixel(68, 0, 100); + palScreen[0x05] = olc::Pixel(92, 0, 48); + palScreen[0x06] = olc::Pixel(84, 4, 0); + palScreen[0x07] = olc::Pixel(60, 24, 0); + palScreen[0x08] = olc::Pixel(32, 42, 0); + palScreen[0x09] = olc::Pixel(8, 58, 0); + palScreen[0x0A] = olc::Pixel(0, 64, 0); + palScreen[0x0B] = olc::Pixel(0, 60, 0); + palScreen[0x0C] = olc::Pixel(0, 50, 60); + palScreen[0x0D] = olc::Pixel(0, 0, 0); + palScreen[0x0E] = olc::Pixel(0, 0, 0); + palScreen[0x0F] = olc::Pixel(0, 0, 0); + + palScreen[0x10] = olc::Pixel(152, 150, 152); + palScreen[0x11] = olc::Pixel(8, 76, 196); + palScreen[0x12] = olc::Pixel(48, 50, 236); + palScreen[0x13] = olc::Pixel(92, 30, 228); + palScreen[0x14] = olc::Pixel(136, 20, 176); + palScreen[0x15] = olc::Pixel(160, 20, 100); + palScreen[0x16] = olc::Pixel(152, 34, 32); + palScreen[0x17] = olc::Pixel(120, 60, 0); + palScreen[0x18] = olc::Pixel(84, 90, 0); + palScreen[0x19] = olc::Pixel(40, 114, 0); + palScreen[0x1A] = olc::Pixel(8, 124, 0); + palScreen[0x1B] = olc::Pixel(0, 118, 40); + palScreen[0x1C] = olc::Pixel(0, 102, 120); + palScreen[0x1D] = olc::Pixel(0, 0, 0); + palScreen[0x1E] = olc::Pixel(0, 0, 0); + palScreen[0x1F] = olc::Pixel(0, 0, 0); + + palScreen[0x20] = olc::Pixel(236, 238, 236); + palScreen[0x21] = olc::Pixel(76, 154, 236); + palScreen[0x22] = olc::Pixel(120, 124, 236); + palScreen[0x23] = olc::Pixel(176, 98, 236); + palScreen[0x24] = olc::Pixel(228, 84, 236); + palScreen[0x25] = olc::Pixel(236, 88, 180); + palScreen[0x26] = olc::Pixel(236, 106, 100); + palScreen[0x27] = olc::Pixel(212, 136, 32); + palScreen[0x28] = olc::Pixel(160, 170, 0); + palScreen[0x29] = olc::Pixel(116, 196, 0); + palScreen[0x2A] = olc::Pixel(76, 208, 32); + palScreen[0x2B] = olc::Pixel(56, 204, 108); + palScreen[0x2C] = olc::Pixel(56, 180, 204); + palScreen[0x2D] = olc::Pixel(60, 60, 60); + palScreen[0x2E] = olc::Pixel(0, 0, 0); + palScreen[0x2F] = olc::Pixel(0, 0, 0); + + palScreen[0x30] = olc::Pixel(236, 238, 236); + palScreen[0x31] = olc::Pixel(168, 204, 236); + palScreen[0x32] = olc::Pixel(188, 188, 236); + palScreen[0x33] = olc::Pixel(212, 178, 236); + palScreen[0x34] = olc::Pixel(236, 174, 236); + palScreen[0x35] = olc::Pixel(236, 174, 212); + palScreen[0x36] = olc::Pixel(236, 180, 176); + palScreen[0x37] = olc::Pixel(228, 196, 144); + palScreen[0x38] = olc::Pixel(204, 210, 120); + palScreen[0x39] = olc::Pixel(180, 222, 120); + palScreen[0x3A] = olc::Pixel(168, 226, 144); + palScreen[0x3B] = olc::Pixel(152, 226, 180); + palScreen[0x3C] = olc::Pixel(160, 214, 228); + palScreen[0x3D] = olc::Pixel(160, 162, 160); + palScreen[0x3E] = olc::Pixel(0, 0, 0); + palScreen[0x3F] = olc::Pixel(0, 0, 0); + +} + + +olc2C02::~olc2C02() +{ +} + +olc::Sprite& olc2C02::GetScreen() +{ + // Simply returns the current sprite holding the rendered screen + return sprScreen; +} + + + +olc::Sprite& olc2C02::GetPatternTable(uint8_t i, uint8_t palette) +{ + // This function draw the CHR ROM for a given pattern table into + // an olc::Sprite, using a specified palette. Pattern tables consist + // of 16x16 "tiles or characters". It is independent of the running + // emulation and using it does not change the systems state, though + // it gets all the data it needs from the live system. Consequently, + // if the game has not yet established palettes or mapped to relevant + // CHR ROM banks, the sprite may look empty. This approach permits a + // "live" extraction of the pattern table exactly how the NES, and + // ultimately the player would see it. + + // A tile consists of 8x8 pixels. On the NES, pixels are 2 bits, which + // gives an index into 4 different colours of a specific palette. There + // are 8 palettes to choose from. Colour "0" in each palette is effectively + // considered transparent, as those locations in memory "mirror" the global + // background colour being used. This mechanics of this are shown in + // detail in ppuRead() & ppuWrite() + + // Characters on NES + // ~~~~~~~~~~~~~~~~~ + // The NES stores characters using 2-bit pixels. These are not stored sequentially + // but in singular bit planes. For example: + // + // 2-Bit Pixels LSB Bit Plane MSB Bit Plane + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 + // 0 1 2 0 0 2 1 0 0 1 1 0 0 1 1 0 0 0 1 0 0 1 0 0 + // 0 0 0 0 0 0 0 0 = 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + // 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 + // 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 2 2 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // + // The planes are stored as 8 bytes of LSB, followed by 8 bytes of MSB + + // Loop through all 16x16 tiles + for (uint16_t nTileY = 0; nTileY < 16; nTileY++) + { + for (uint16_t nTileX = 0; nTileX < 16; nTileX++) + { + // Convert the 2D tile coordinate into a 1D offset into the pattern + // table memory. + uint16_t nOffset = nTileY * 256 + nTileX * 16; + + // Now loop through 8 rows of 8 pixels + for (uint16_t row = 0; row < 8; row++) + { + // For each row, we need to read both bit planes of the character + // in order to extract the least significant and most significant + // bits of the 2 bit pixel value. in the CHR ROM, each character + // is stored as 64 bits of lsb, followed by 64 bits of msb. This + // conveniently means that two corresponding rows are always 8 + // bytes apart in memory. + uint8_t tile_lsb = ppuRead(i * 0x1000 + nOffset + row + 0x0000); + uint8_t tile_msb = ppuRead(i * 0x1000 + nOffset + row + 0x0008); + + + // Now we have a single row of the two bit planes for the character + // we need to iterate through the 8-bit words, combining them to give + // us the final pixel index + for (uint16_t col = 0; col < 8; col++) + { + // We can get the index value by simply adding the bits together + // but we're only interested in the lsb of the row words because... + uint8_t pixel = (tile_msb & 0x01) << 1 | (tile_lsb & 0x01); + + // ...we will shift the row words 1 bit right for each column of + // the character. + tile_lsb >>= 1; tile_msb >>= 1; + + // Now we know the location and NES pixel value for a specific location + // in the pattern table, we can translate that to a screen colour, and an + // (x,y) location in the sprite + sprPatternTable[i].SetPixel + ( + nTileX * 8 + (7 - col), // Because we are using the lsb of the row word first + // we are effectively reading the row from right + // to left, so we need to draw the row "backwards" + nTileY * 8 + row, + GetColourFromPaletteRam(palette, pixel) + ); + } + } + } + } + + // Finally return the updated sprite representing the pattern table + return sprPatternTable[i]; +} + + +olc::Pixel& olc2C02::GetColourFromPaletteRam(uint8_t palette, uint8_t pixel) +{ + // This is a convenience function that takes a specified palette and pixel + // index and returns the appropriate screen colour. + // "0x3F00" - Offset into PPU addressable range where palettes are stored + // "palette << 2" - Each palette is 4 bytes in size + // "pixel" - Each pixel index is either 0, 1, 2 or 3 + // "& 0x3F" - Stops us reading beyond the bounds of the palScreen array + return palScreen[ppuRead(0x3F00 + (palette << 2) + pixel) & 0x3F]; + + // Note: We dont access tblPalette directly here, instead we know that ppuRead() + // will map the address onto the seperate small RAM attached to the PPU bus. +} + +olc::Sprite& olc2C02::GetNameTable(uint8_t i) +{ + // As of now unused, but a placeholder for nametable visualisation in teh future + return sprNameTable[i]; +} + + +uint8_t olc2C02::cpuRead(uint16_t addr, bool rdonly) +{ + uint8_t data = 0x00; + + if (rdonly) + { + // Reading from PPU registers can affect their contents + // so this read only option is used for examining the + // state of the PPU without changing its state. This is + // really only used in debug mode. + switch (addr) + { + case 0x0000: // Control + data = control.reg; + break; + case 0x0001: // Mask + data = mask.reg; + break; + case 0x0002: // Status + data = status.reg; + break; + case 0x0003: // OAM Address + break; + case 0x0004: // OAM Data + break; + case 0x0005: // Scroll + break; + case 0x0006: // PPU Address + break; + case 0x0007: // PPU Data + break; + } + } + else + { + // These are the live PPU registers that repsond + // to being read from in various ways. Note that not + // all the registers are capable of being read from + // so they just return 0x00 + switch (addr) + { + // Control - Not readable + case 0x0000: break; + + // Mask - Not Readable + case 0x0001: break; + + // Status + case 0x0002: + // Reading from the status register has the effect of resetting + // different parts of the circuit. Only the top three bits + // contain status information, however it is possible that + // some "noise" gets picked up on the bottom 5 bits which + // represent the last PPU bus transaction. Some games "may" + // use this noise as valid data (even though they probably + // shouldn't) + data = (status.reg & 0xE0) | (ppu_data_buffer & 0x1F); + + // Clear the vertical blanking flag + status.vertical_blank = 0; + + // Reset Loopy's Address latch flag + address_latch = 0; + break; + + // OAM Address - Not Readable + case 0x0003: break; + + // OAM Data + case 0x0004: + data = pOAM[oam_addr]; + break; + + // Scroll - Not Readable + case 0x0005: break; + + // PPU Address - Not Readable + case 0x0006: break; + + // PPU Data + case 0x0007: + // Reads from the NameTable ram get delayed one cycle, + // so output buffer which contains the data from the + // previous read request + data = ppu_data_buffer; + // then update the buffer for next time + ppu_data_buffer = ppuRead(vram_addr.reg); + // However, if the address was in the palette range, the + // data is not delayed, so it returns immediately + if (vram_addr.reg >= 0x3F00) data = ppu_data_buffer; + // All reads from PPU data automatically increment the nametable + // address depending upon the mode set in the control register. + // If set to vertical mode, the increment is 32, so it skips + // one whole nametable row; in horizontal mode it just increments + // by 1, moving to the next column + vram_addr.reg += (control.increment_mode ? 32 : 1); + break; + } + } + + return data; +} + +void olc2C02::cpuWrite(uint16_t addr, uint8_t data) +{ + switch (addr) + { + case 0x0000: // Control + control.reg = data; + tram_addr.nametable_x = control.nametable_x; + tram_addr.nametable_y = control.nametable_y; + break; + case 0x0001: // Mask + mask.reg = data; + break; + case 0x0002: // Status + break; + case 0x0003: // OAM Address + oam_addr = data; + break; + case 0x0004: // OAM Data + pOAM[oam_addr] = data; + break; + case 0x0005: // Scroll + if (address_latch == 0) + { + // First write to scroll register contains X offset in pixel space + // which we split into coarse and fine x values + fine_x = data & 0x07; + tram_addr.coarse_x = data >> 3; + address_latch = 1; + } + else + { + // First write to scroll register contains Y offset in pixel space + // which we split into coarse and fine Y values + tram_addr.fine_y = data & 0x07; + tram_addr.coarse_y = data >> 3; + address_latch = 0; + } + break; + case 0x0006: // PPU Address + if (address_latch == 0) + { + // PPU address bus can be accessed by CPU via the ADDR and DATA + // registers. The fisrt write to this register latches the high byte + // of the address, the second is the low byte. Note the writes + // are stored in the tram register... + tram_addr.reg = (uint16_t)((data & 0x3F) << 8) | (tram_addr.reg & 0x00FF); + address_latch = 1; + } + else + { + // ...when a whole address has been written, the internal vram address + // buffer is updated. Writing to the PPU is unwise during rendering + // as the PPU will maintam the vram address automatically whilst + // rendering the scanline position. + tram_addr.reg = (tram_addr.reg & 0xFF00) | data; + vram_addr = tram_addr; + address_latch = 0; + } + break; + case 0x0007: // PPU Data + ppuWrite(vram_addr.reg, data); + // All writes from PPU data automatically increment the nametable + // address depending upon the mode set in the control register. + // If set to vertical mode, the increment is 32, so it skips + // one whole nametable row; in horizontal mode it just increments + // by 1, moving to the next column + vram_addr.reg += (control.increment_mode ? 32 : 1); + break; + } +} + +uint8_t olc2C02::ppuRead(uint16_t addr, bool rdonly) +{ + uint8_t data = 0x00; + addr &= 0x3FFF; + + if (cart->ppuRead(addr, data)) + { + + } + else if (addr >= 0x0000 && addr <= 0x1FFF) + { + // If the cartridge cant map the address, have + // a physical location ready here + data = tblPattern[(addr & 0x1000) >> 12][addr & 0x0FFF]; + } + else if (addr >= 0x2000 && addr <= 0x3EFF) + { + addr &= 0x0FFF; + + if (cart->Mirror() == MIRROR::VERTICAL) + { + // Vertical + if (addr >= 0x0000 && addr <= 0x03FF) + data = tblName[0][addr & 0x03FF]; + if (addr >= 0x0400 && addr <= 0x07FF) + data = tblName[1][addr & 0x03FF]; + if (addr >= 0x0800 && addr <= 0x0BFF) + data = tblName[0][addr & 0x03FF]; + if (addr >= 0x0C00 && addr <= 0x0FFF) + data = tblName[1][addr & 0x03FF]; + } + else if (cart->Mirror() == MIRROR::HORIZONTAL) + { + // Horizontal + if (addr >= 0x0000 && addr <= 0x03FF) + data = tblName[0][addr & 0x03FF]; + if (addr >= 0x0400 && addr <= 0x07FF) + data = tblName[0][addr & 0x03FF]; + if (addr >= 0x0800 && addr <= 0x0BFF) + data = tblName[1][addr & 0x03FF]; + if (addr >= 0x0C00 && addr <= 0x0FFF) + data = tblName[1][addr & 0x03FF]; + } + } + else if (addr >= 0x3F00 && addr <= 0x3FFF) + { + addr &= 0x001F; + if (addr == 0x0010) addr = 0x0000; + if (addr == 0x0014) addr = 0x0004; + if (addr == 0x0018) addr = 0x0008; + if (addr == 0x001C) addr = 0x000C; + data = tblPalette[addr] & (mask.grayscale ? 0x30 : 0x3F); + } + + return data; +} + +void olc2C02::ppuWrite(uint16_t addr, uint8_t data) +{ + addr &= 0x3FFF; + + if (cart->ppuWrite(addr, data)) + { + + } + else if (addr >= 0x0000 && addr <= 0x1FFF) + { + tblPattern[(addr & 0x1000) >> 12][addr & 0x0FFF] = data; + } + else if (addr >= 0x2000 && addr <= 0x3EFF) + { + addr &= 0x0FFF; + if (cart->Mirror() == MIRROR::VERTICAL) + { + // Vertical + if (addr >= 0x0000 && addr <= 0x03FF) + tblName[0][addr & 0x03FF] = data; + if (addr >= 0x0400 && addr <= 0x07FF) + tblName[1][addr & 0x03FF] = data; + if (addr >= 0x0800 && addr <= 0x0BFF) + tblName[0][addr & 0x03FF] = data; + if (addr >= 0x0C00 && addr <= 0x0FFF) + tblName[1][addr & 0x03FF] = data; + } + else if (cart->Mirror() == MIRROR::HORIZONTAL) + { + // Horizontal + if (addr >= 0x0000 && addr <= 0x03FF) + tblName[0][addr & 0x03FF] = data; + if (addr >= 0x0400 && addr <= 0x07FF) + tblName[0][addr & 0x03FF] = data; + if (addr >= 0x0800 && addr <= 0x0BFF) + tblName[1][addr & 0x03FF] = data; + if (addr >= 0x0C00 && addr <= 0x0FFF) + tblName[1][addr & 0x03FF] = data; + } + } + else if (addr >= 0x3F00 && addr <= 0x3FFF) + { + addr &= 0x001F; + if (addr == 0x0010) addr = 0x0000; + if (addr == 0x0014) addr = 0x0004; + if (addr == 0x0018) addr = 0x0008; + if (addr == 0x001C) addr = 0x000C; + tblPalette[addr] = data; + } +} + +void olc2C02::ConnectCartridge(const std::shared_ptr& cartridge) +{ + this->cart = cartridge; +} + +void olc2C02::reset() +{ + fine_x = 0x00; + address_latch = 0x00; + ppu_data_buffer = 0x00; + scanline = 0; + cycle = 0; + bg_next_tile_id = 0x00; + bg_next_tile_attrib = 0x00; + bg_next_tile_lsb = 0x00; + bg_next_tile_msb = 0x00; + bg_shifter_pattern_lo = 0x0000; + bg_shifter_pattern_hi = 0x0000; + bg_shifter_attrib_lo = 0x0000; + bg_shifter_attrib_hi = 0x0000; + status.reg = 0x00; + mask.reg = 0x00; + control.reg = 0x00; + vram_addr.reg = 0x0000; + tram_addr.reg = 0x0000; + scanline_trigger = false; + odd_frame = false; +} + +void olc2C02::clock() +{ + // As we progress through scanlines and cycles, the PPU is effectively + // a state machine going through the motions of fetching background + // information and sprite information, compositing them into a pixel + // to be output. + + // The lambda functions (functions inside functions) contain the various + // actions to be performed depending upon the output of the state machine + // for a given scanline/cycle combination + + // ============================================================================== + // Increment the background tile "pointer" one tile/column horizontally + auto IncrementScrollX = [&]() + { + // Note: pixel perfect scrolling horizontally is handled by the + // data shifters. Here we are operating in the spatial domain of + // tiles, 8x8 pixel blocks. + + // Ony if rendering is enabled + if (mask.render_background || mask.render_sprites) + { + // A single name table is 32x30 tiles. As we increment horizontally + // we may cross into a neighbouring nametable, or wrap around to + // a neighbouring nametable + if (vram_addr.coarse_x == 31) + { + // Leaving nametable so wrap address round + vram_addr.coarse_x = 0; + // Flip target nametable bit + vram_addr.nametable_x = ~vram_addr.nametable_x; + } + else + { + // Staying in current nametable, so just increment + vram_addr.coarse_x++; + } + } + }; + + // ============================================================================== + // Increment the background tile "pointer" one scanline vertically + auto IncrementScrollY = [&]() + { + // Incrementing vertically is more complicated. The visible nametable + // is 32x30 tiles, but in memory there is enough room for 32x32 tiles. + // The bottom two rows of tiles are in fact not tiles at all, they + // contain the "attribute" information for the entire table. This is + // information that describes which palettes are used for different + // regions of the nametable. + + // In addition, the NES doesnt scroll vertically in chunks of 8 pixels + // i.e. the height of a tile, it can perform fine scrolling by using + // the fine_y component of the register. This means an increment in Y + // first adjusts the fine offset, but may need to adjust the whole + // row offset, since fine_y is a value 0 to 7, and a row is 8 pixels high + + // Ony if rendering is enabled + if (mask.render_background || mask.render_sprites) + { + // If possible, just increment the fine y offset + if (vram_addr.fine_y < 7) + { + vram_addr.fine_y++; + } + else + { + // If we have gone beyond the height of a row, we need to + // increment the row, potentially wrapping into neighbouring + // vertical nametables. Dont forget however, the bottom two rows + // do not contain tile information. The coarse y offset is used + // to identify which row of the nametable we want, and the fine + // y offset is the specific "scanline" + + // Reset fine y offset + vram_addr.fine_y = 0; + + // Check if we need to swap vertical nametable targets + if (vram_addr.coarse_y == 29) + { + // We do, so reset coarse y offset + vram_addr.coarse_y = 0; + // And flip the target nametable bit + vram_addr.nametable_y = ~vram_addr.nametable_y; + } + else if (vram_addr.coarse_y == 31) + { + // In case the pointer is in the attribute memory, we + // just wrap around the current nametable + vram_addr.coarse_y = 0; + } + else + { + // None of the above boundary/wrapping conditions apply + // so just increment the coarse y offset + vram_addr.coarse_y++; + } + } + } + }; + + // ============================================================================== + // Transfer the temporarily stored horizontal nametable access information + // into the "pointer". Note that fine x scrolling is not part of the "pointer" + // addressing mechanism + auto TransferAddressX = [&]() + { + // Ony if rendering is enabled + if (mask.render_background || mask.render_sprites) + { + vram_addr.nametable_x = tram_addr.nametable_x; + vram_addr.coarse_x = tram_addr.coarse_x; + } + }; + + // ============================================================================== + // Transfer the temporarily stored vertical nametable access information + // into the "pointer". Note that fine y scrolling is part of the "pointer" + // addressing mechanism + auto TransferAddressY = [&]() + { + // Ony if rendering is enabled + if (mask.render_background || mask.render_sprites) + { + vram_addr.fine_y = tram_addr.fine_y; + vram_addr.nametable_y = tram_addr.nametable_y; + vram_addr.coarse_y = tram_addr.coarse_y; + } + }; + + + // ============================================================================== + // Prime the "in-effect" background tile shifters ready for outputting next + // 8 pixels in scanline. + auto LoadBackgroundShifters = [&]() + { + // Each PPU update we calculate one pixel. These shifters shift 1 bit along + // feeding the pixel compositor with the binary information it needs. Its + // 16 bits wide, because the top 8 bits are the current 8 pixels being drawn + // and the bottom 8 bits are the next 8 pixels to be drawn. Naturally this means + // the required bit is always the MSB of the shifter. However, "fine x" scrolling + // plays a part in this too, whcih is seen later, so in fact we can choose + // any one of the top 8 bits. + bg_shifter_pattern_lo = (bg_shifter_pattern_lo & 0xFF00) | bg_next_tile_lsb; + bg_shifter_pattern_hi = (bg_shifter_pattern_hi & 0xFF00) | bg_next_tile_msb; + + // Attribute bits do not change per pixel, rather they change every 8 pixels + // but are synchronised with the pattern shifters for convenience, so here + // we take the bottom 2 bits of the attribute word which represent which + // palette is being used for the current 8 pixels and the next 8 pixels, and + // "inflate" them to 8 bit words. + bg_shifter_attrib_lo = (bg_shifter_attrib_lo & 0xFF00) | ((bg_next_tile_attrib & 0b01) ? 0xFF : 0x00); + bg_shifter_attrib_hi = (bg_shifter_attrib_hi & 0xFF00) | ((bg_next_tile_attrib & 0b10) ? 0xFF : 0x00); + }; + + + // ============================================================================== + // Every cycle the shifters storing pattern and attribute information shift + // their contents by 1 bit. This is because every cycle, the output progresses + // by 1 pixel. This means relatively, the state of the shifter is in sync + // with the pixels being drawn for that 8 pixel section of the scanline. + auto UpdateShifters = [&]() + { + if (mask.render_background) + { + // Shifting background tile pattern row + bg_shifter_pattern_lo <<= 1; + bg_shifter_pattern_hi <<= 1; + + // Shifting palette attributes by 1 + bg_shifter_attrib_lo <<= 1; + bg_shifter_attrib_hi <<= 1; + } + + if (mask.render_sprites && cycle >= 1 && cycle < 258) + { + for (int i = 0; i < sprite_count; i++) + { + if (spriteScanline[i].x > 0) + { + spriteScanline[i].x--; + } + else + { + sprite_shifter_pattern_lo[i] <<= 1; + sprite_shifter_pattern_hi[i] <<= 1; + } + } + } + }; + + + + // All but 1 of the secanlines is visible to the user. The pre-render scanline + // at -1, is used to configure the "shifters" for the first visible scanline, 0. + if (scanline >= -1 && scanline < 240) + { + // Background Rendering ====================================================== + + if (scanline == 0 && cycle == 0 && odd_frame && (mask.render_background || mask.render_sprites)) + { + // "Odd Frame" cycle skip + cycle = 1; + } + + if (scanline == -1 && cycle == 1) + { + // Effectively start of new frame, so clear vertical blank flag + status.vertical_blank = 0; + + // Clear sprite overflow flag + status.sprite_overflow = 0; + + // Clear the sprite zero hit flag + status.sprite_zero_hit = 0; + + // Clear Shifters + for (int i = 0; i < 8; i++) + { + sprite_shifter_pattern_lo[i] = 0; + sprite_shifter_pattern_hi[i] = 0; + } + } + + + if ((cycle >= 2 && cycle < 258) || (cycle >= 321 && cycle < 338)) + { + UpdateShifters(); + + + // In these cycles we are collecting and working with visible data + // The "shifters" have been preloaded by the end of the previous + // scanline with the data for the start of this scanline. Once we + // leave the visible region, we go dormant until the shifters are + // preloaded for the next scanline. + + // Fortunately, for background rendering, we go through a fairly + // repeatable sequence of events, every 2 clock cycles. + switch ((cycle - 1) % 8) + { + case 0: + // Load the current background tile pattern and attributes into the "shifter" + LoadBackgroundShifters(); + + // Fetch the next background tile ID + // "(vram_addr.reg & 0x0FFF)" : Mask to 12 bits that are relevant + // "| 0x2000" : Offset into nametable space on PPU address bus + bg_next_tile_id = ppuRead(0x2000 | (vram_addr.reg & 0x0FFF)); + + // Explanation: + // The bottom 12 bits of the loopy register provide an index into + // the 4 nametables, regardless of nametable mirroring configuration. + // nametable_y(1) nametable_x(1) coarse_y(5) coarse_x(5) + // + // Consider a single nametable is a 32x32 array, and we have four of them + // 0 1 + // 0 +----------------+----------------+ + // | | | + // | | | + // | (32x32) | (32x32) | + // | | | + // | | | + // 1 +----------------+----------------+ + // | | | + // | | | + // | (32x32) | (32x32) | + // | | | + // | | | + // +----------------+----------------+ + // + // This means there are 4096 potential locations in this array, which + // just so happens to be 2^12! + break; + case 2: + // Fetch the next background tile attribute. OK, so this one is a bit + // more involved :P + + // Recall that each nametable has two rows of cells that are not tile + // information, instead they represent the attribute information that + // indicates which palettes are applied to which area on the screen. + // Importantly (and frustratingly) there is not a 1 to 1 correspondance + // between background tile and palette. Two rows of tile data holds + // 64 attributes. Therfore we can assume that the attributes affect + // 8x8 zones on the screen for that nametable. Given a working resolution + // of 256x240, we can further assume that each zone is 32x32 pixels + // in screen space, or 4x4 tiles. Four system palettes are allocated + // to background rendering, so a palette can be specified using just + // 2 bits. The attribute byte therefore can specify 4 distinct palettes. + // Therefore we can even further assume that a single palette is + // applied to a 2x2 tile combination of the 4x4 tile zone. The very fact + // that background tiles "share" a palette locally is the reason why + // in some games you see distortion in the colours at screen edges. + + // As before when choosing the tile ID, we can use the bottom 12 bits of + // the loopy register, but we need to make the implementation "coarser" + // because instead of a specific tile, we want the attribute byte for a + // group of 4x4 tiles, or in other words, we divide our 32x32 address + // by 4 to give us an equivalent 8x8 address, and we offset this address + // into the attribute section of the target nametable. + + // Reconstruct the 12 bit loopy address into an offset into the + // attribute memory + + // "(vram_addr.coarse_x >> 2)" : integer divide coarse x by 4, + // from 5 bits to 3 bits + // "((vram_addr.coarse_y >> 2) << 3)" : integer divide coarse y by 4, + // from 5 bits to 3 bits, + // shift to make room for coarse x + + // Result so far: YX00 00yy yxxx + + // All attribute memory begins at 0x03C0 within a nametable, so OR with + // result to select target nametable, and attribute byte offset. Finally + // OR with 0x2000 to offset into nametable address space on PPU bus. + bg_next_tile_attrib = ppuRead(0x23C0 | (vram_addr.nametable_y << 11) + | (vram_addr.nametable_x << 10) + | ((vram_addr.coarse_y >> 2) << 3) + | (vram_addr.coarse_x >> 2)); + + // Right we've read the correct attribute byte for a specified address, + // but the byte itself is broken down further into the 2x2 tile groups + // in the 4x4 attribute zone. + + // The attribute byte is assembled thus: BR(76) BL(54) TR(32) TL(10) + // + // +----+----+ +----+----+ + // | TL | TR | | ID | ID | + // +----+----+ where TL = +----+----+ + // | BL | BR | | ID | ID | + // +----+----+ +----+----+ + // + // Since we know we can access a tile directly from the 12 bit address, we + // can analyse the bottom bits of the coarse coordinates to provide us with + // the correct offset into the 8-bit word, to yield the 2 bits we are + // actually interested in which specifies the palette for the 2x2 group of + // tiles. We know if "coarse y % 4" < 2 we are in the top half else bottom half. + // Likewise if "coarse x % 4" < 2 we are in the left half else right half. + // Ultimately we want the bottom two bits of our attribute word to be the + // palette selected. So shift as required... + if (vram_addr.coarse_y & 0x02) bg_next_tile_attrib >>= 4; + if (vram_addr.coarse_x & 0x02) bg_next_tile_attrib >>= 2; + bg_next_tile_attrib &= 0x03; + break; + + // Compared to the last two, the next two are the easy ones... :P + + case 4: + // Fetch the next background tile LSB bit plane from the pattern memory + // The Tile ID has been read from the nametable. We will use this id to + // index into the pattern memory to find the correct sprite (assuming + // the sprites lie on 8x8 pixel boundaries in that memory, which they do + // even though 8x16 sprites exist, as background tiles are always 8x8). + // + // Since the sprites are effectively 1 bit deep, but 8 pixels wide, we + // can represent a whole sprite row as a single byte, so offsetting + // into the pattern memory is easy. In total there is 8KB so we need a + // 13 bit address. + + // "(control.pattern_background << 12)" : the pattern memory selector + // from control register, either 0K + // or 4K offset + // "((uint16_t)bg_next_tile_id << 4)" : the tile id multiplied by 16, as + // 2 lots of 8 rows of 8 bit pixels + // "(vram_addr.fine_y)" : Offset into which row based on + // vertical scroll offset + // "+ 0" : Mental clarity for plane offset + // Note: No PPU address bus offset required as it starts at 0x0000 + bg_next_tile_lsb = ppuRead((control.pattern_background << 12) + + ((uint16_t)bg_next_tile_id << 4) + + (vram_addr.fine_y) + 0); + + break; + case 6: + // Fetch the next background tile MSB bit plane from the pattern memory + // This is the same as above, but has a +8 offset to select the next bit plane + bg_next_tile_msb = ppuRead((control.pattern_background << 12) + + ((uint16_t)bg_next_tile_id << 4) + + (vram_addr.fine_y) + 8); + break; + case 7: + // Increment the background tile "pointer" to the next tile horizontally + // in the nametable memory. Note this may cross nametable boundaries which + // is a little complex, but essential to implement scrolling + IncrementScrollX(); + break; + } + } + + // End of a visible scanline, so increment downwards... + if (cycle == 256) + { + IncrementScrollY(); + } + + //...and reset the x position + if (cycle == 257) + { + LoadBackgroundShifters(); + TransferAddressX(); + } + + // Superfluous reads of tile id at end of scanline + if (cycle == 338 || cycle == 340) + { + bg_next_tile_id = ppuRead(0x2000 | (vram_addr.reg & 0x0FFF)); + } + + if (scanline == -1 && cycle >= 280 && cycle < 305) + { + // End of vertical blank period so reset the Y address ready for rendering + TransferAddressY(); + } + + + // Foreground Rendering ======================================================== + // I'm gonna cheat a bit here, which may reduce compatibility, but greatly + // simplifies delivering an intuitive understanding of what exactly is going + // on. The PPU loads sprite information successively during the region that + // background tiles are not being drawn. Instead, I'm going to perform + // all sprite evaluation in one hit. THE NES DOES NOT DO IT LIKE THIS! This makes + // it easier to see the process of sprite evaluation. + if (cycle == 257 && scanline >= 0) + { + // We've reached the end of a visible scanline. It is now time to determine + // which sprites are visible on the next scanline, and preload this info + // into buffers that we can work with while the scanline scans the row. + + // Firstly, clear out the sprite memory. This memory is used to store the + // sprites to be rendered. It is not the OAM. + std::memset(spriteScanline, 0xFF, 8 * sizeof(sObjectAttributeEntry)); + + // The NES supports a maximum number of sprites per scanline. Nominally + // this is 8 or fewer sprites. This is why in some games you see sprites + // flicker or disappear when the scene gets busy. + sprite_count = 0; + + // Secondly, clear out any residual information in sprite pattern shifters + for (uint8_t i = 0; i < 8; i++) + { + sprite_shifter_pattern_lo[i] = 0; + sprite_shifter_pattern_hi[i] = 0; + } + + // Thirdly, Evaluate which sprites are visible in the next scanline. We need + // to iterate through the OAM until we have found 8 sprites that have Y-positions + // and heights that are within vertical range of the next scanline. Once we have + // found 8 or exhausted the OAM we stop. Now, notice I count to 9 sprites. This + // is so I can set the sprite overflow flag in the event of there being > 8 sprites. + uint8_t nOAMEntry = 0; + + // New set of sprites. Sprite zero may not exist in the new set, so clear this + // flag. + bSpriteZeroHitPossible = false; + + while (nOAMEntry < 64 && sprite_count < 9) + { + // Note the conversion to signed numbers here + int16_t diff = ((int16_t)scanline - (int16_t)OAM[nOAMEntry].y); + + // If the difference is positive then the scanline is at least at the + // same height as the sprite, so check if it resides in the sprite vertically + // depending on the current "sprite height mode" + // FLAGGED + + if (diff >= 0 && diff < (control.sprite_size ? 16 : 8) && sprite_count < 8) + { + // Sprite is visible, so copy the attribute entry over to our + // scanline sprite cache. Ive added < 8 here to guard the array + // being written to. + if (sprite_count < 8) + { + // Is this sprite sprite zero? + if (nOAMEntry == 0) + { + // It is, so its possible it may trigger a + // sprite zero hit when drawn + bSpriteZeroHitPossible = true; + } + + memcpy(&spriteScanline[sprite_count], &OAM[nOAMEntry], sizeof(sObjectAttributeEntry)); + } + sprite_count++; + } + nOAMEntry++; + } // End of sprite evaluation for next scanline + + // Set sprite overflow flag + status.sprite_overflow = (sprite_count >= 8); + + // Now we have an array of the 8 visible sprites for the next scanline. By + // the nature of this search, they are also ranked in priority, because + // those lower down in the OAM have the higher priority. + + // We also guarantee that "Sprite Zero" will exist in spriteScanline[0] if + // it is evaluated to be visible. + } + + if (cycle == 340) + { + // Now we're at the very end of the scanline, I'm going to prepare the + // sprite shifters with the 8 or less selected sprites. + + for (uint8_t i = 0; i < sprite_count; i++) + { + // We need to extract the 8-bit row patterns of the sprite with the + // correct vertical offset. The "Sprite Mode" also affects this as + // the sprites may be 8 or 16 rows high. Additionally, the sprite + // can be flipped both vertically and horizontally. So there's a lot + // going on here :P + + uint8_t sprite_pattern_bits_lo, sprite_pattern_bits_hi; + uint16_t sprite_pattern_addr_lo, sprite_pattern_addr_hi; + + // Determine the memory addresses that contain the byte of pattern data. We + // only need the lo pattern address, because the hi pattern address is always + // offset by 8 from the lo address. + if (!control.sprite_size) + { + // 8x8 Sprite Mode - The control register determines the pattern table + if (!(spriteScanline[i].attribute & 0x80)) + { + // Sprite is NOT flipped vertically, i.e. normal + sprite_pattern_addr_lo = + (control.pattern_sprite << 12 ) // Which Pattern Table? 0KB or 4KB offset + | (spriteScanline[i].id << 4 ) // Which Cell? Tile ID * 16 (16 bytes per tile) + | (scanline - spriteScanline[i].y); // Which Row in cell? (0->7) + + } + else + { + // Sprite is flipped vertically, i.e. upside down + sprite_pattern_addr_lo = + (control.pattern_sprite << 12 ) // Which Pattern Table? 0KB or 4KB offset + | (spriteScanline[i].id << 4 ) // Which Cell? Tile ID * 16 (16 bytes per tile) + | (7 - (scanline - spriteScanline[i].y)); // Which Row in cell? (7->0) + } + + } + else + { + // 8x16 Sprite Mode - The sprite attribute determines the pattern table + if (!(spriteScanline[i].attribute & 0x80)) + { + // Sprite is NOT flipped vertically, i.e. normal + if (scanline - spriteScanline[i].y < 8) + { + // Reading Top half Tile + sprite_pattern_addr_lo = + ((spriteScanline[i].id & 0x01) << 12) // Which Pattern Table? 0KB or 4KB offset + | ((spriteScanline[i].id & 0xFE) << 4 ) // Which Cell? Tile ID * 16 (16 bytes per tile) + | ((scanline - spriteScanline[i].y) & 0x07 ); // Which Row in cell? (0->7) + } + else + { + // Reading Bottom Half Tile + sprite_pattern_addr_lo = + ( (spriteScanline[i].id & 0x01) << 12) // Which Pattern Table? 0KB or 4KB offset + | (((spriteScanline[i].id & 0xFE) + 1) << 4 ) // Which Cell? Tile ID * 16 (16 bytes per tile) + | ((scanline - spriteScanline[i].y) & 0x07 ); // Which Row in cell? (0->7) + } + } + else + { + // Sprite is flipped vertically, i.e. upside down + if (scanline - spriteScanline[i].y < 8) + { + // Reading Top half Tile + sprite_pattern_addr_lo = + ( (spriteScanline[i].id & 0x01) << 12) // Which Pattern Table? 0KB or 4KB offset + | (((spriteScanline[i].id & 0xFE) + 1) << 4 ) // Which Cell? Tile ID * 16 (16 bytes per tile) + | (7 - (scanline - spriteScanline[i].y) & 0x07); // Which Row in cell? (0->7) + } + else + { + // Reading Bottom Half Tile + sprite_pattern_addr_lo = + ((spriteScanline[i].id & 0x01) << 12) // Which Pattern Table? 0KB or 4KB offset + | ((spriteScanline[i].id & 0xFE) << 4 ) // Which Cell? Tile ID * 16 (16 bytes per tile) + | (7 - (scanline - spriteScanline[i].y) & 0x07); // Which Row in cell? (0->7) + } + } + } + + // Phew... XD I'm absolutely certain you can use some fantastic bit + // manipulation to reduce all of that to a few one liners, but in this + // form it's easy to see the processes required for the different + // sizes and vertical orientations + + // Hi bit plane equivalent is always offset by 8 bytes from lo bit plane + sprite_pattern_addr_hi = sprite_pattern_addr_lo + 8; + + // Now we have the address of the sprite patterns, we can read them + sprite_pattern_bits_lo = ppuRead(sprite_pattern_addr_lo); + sprite_pattern_bits_hi = ppuRead(sprite_pattern_addr_hi); + + // If the sprite is flipped horizontally, we need to flip the + // pattern bytes. + if (spriteScanline[i].attribute & 0x40) + { + // This little lambda function "flips" a byte + // so 0b11100000 becomes 0b00000111. It's very + // clever, and stolen completely from here: + // https://stackoverflow.com/a/2602885 + auto flipbyte = [](uint8_t b) + { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; + }; + + // Flip Patterns Horizontally + sprite_pattern_bits_lo = flipbyte(sprite_pattern_bits_lo); + sprite_pattern_bits_hi = flipbyte(sprite_pattern_bits_hi); + } + + // Finally! We can load the pattern into our sprite shift registers + // ready for rendering on the next scanline + sprite_shifter_pattern_lo[i] = sprite_pattern_bits_lo; + sprite_shifter_pattern_hi[i] = sprite_pattern_bits_hi; + } + } + } + + if (scanline == 240) + { + // Post Render Scanline - Do Nothing! + } + + if (scanline >= 241 && scanline < 261) + { + if (scanline == 241 && cycle == 1) + { + // Effectively end of frame, so set vertical blank flag + status.vertical_blank = 1; + + // If the control register tells us to emit a NMI when + // entering vertical blanking period, do it! The CPU + // will be informed that rendering is complete so it can + // perform operations with the PPU knowing it wont + // produce visible artefacts + if (control.enable_nmi) + nmi = true; + } + } + + + + // Composition - We now have background & foreground pixel information for this cycle + + // Background ============================================================= + uint8_t bg_pixel = 0x00; // The 2-bit pixel to be rendered + uint8_t bg_palette = 0x00; // The 3-bit index of the palette the pixel indexes + + // We only render backgrounds if the PPU is enabled to do so. Note if + // background rendering is disabled, the pixel and palette combine + // to form 0x00. This will fall through the colour tables to yield + // the current background colour in effect + if (mask.render_background) + { + if (mask.render_background_left || (cycle >= 9)) + { + // Handle Pixel Selection by selecting the relevant bit + // depending upon fine x scolling. This has the effect of + // offsetting ALL background rendering by a set number + // of pixels, permitting smooth scrolling + uint16_t bit_mux = 0x8000 >> fine_x; + + // Select Plane pixels by extracting from the shifter + // at the required location. + uint8_t p0_pixel = (bg_shifter_pattern_lo & bit_mux) > 0; + uint8_t p1_pixel = (bg_shifter_pattern_hi & bit_mux) > 0; + + // Combine to form pixel index + bg_pixel = (p1_pixel << 1) | p0_pixel; + + // Get palette + uint8_t bg_pal0 = (bg_shifter_attrib_lo & bit_mux) > 0; + uint8_t bg_pal1 = (bg_shifter_attrib_hi & bit_mux) > 0; + bg_palette = (bg_pal1 << 1) | bg_pal0; + } + } + + // Foreground ============================================================= + uint8_t fg_pixel = 0x00; // The 2-bit pixel to be rendered + uint8_t fg_palette = 0x00; // The 3-bit index of the palette the pixel indexes + uint8_t fg_priority = 0x00;// A bit of the sprite attribute indicates if its + // more important than the background + if (mask.render_sprites) + { + // Iterate through all sprites for this scanline. This is to maintain + // sprite priority. As soon as we find a non transparent pixel of + // a sprite we can abort + if (mask.render_sprites_left || (cycle >= 9)) + { + + bSpriteZeroBeingRendered = false; + + for (uint8_t i = 0; i < sprite_count; i++) + { + // Scanline cycle has "collided" with sprite, shifters taking over + if (spriteScanline[i].x == 0) + { + // Note Fine X scrolling does not apply to sprites, the game + // should maintain their relationship with the background. So + // we'll just use the MSB of the shifter + + // Determine the pixel value... + uint8_t fg_pixel_lo = (sprite_shifter_pattern_lo[i] & 0x80) > 0; + uint8_t fg_pixel_hi = (sprite_shifter_pattern_hi[i] & 0x80) > 0; + fg_pixel = (fg_pixel_hi << 1) | fg_pixel_lo; + + // Extract the palette from the bottom two bits. Recall + // that foreground palettes are the latter 4 in the + // palette memory. + fg_palette = (spriteScanline[i].attribute & 0x03) + 0x04; + fg_priority = (spriteScanline[i].attribute & 0x20) == 0; + + // If pixel is not transparent, we render it, and dont + // bother checking the rest because the earlier sprites + // in the list are higher priority + if (fg_pixel != 0) + { + if (i == 0) // Is this sprite zero? + { + bSpriteZeroBeingRendered = true; + } + + break; + } + } + } + } + } + + // Now we have a background pixel and a foreground pixel. They need + // to be combined. It is possible for sprites to go behind background + // tiles that are not "transparent", yet another neat trick of the PPU + // that adds complexity for us poor emulator developers... + + uint8_t pixel = 0x00; // The FINAL Pixel... + uint8_t palette = 0x00; // The FINAL Palette... + + if (bg_pixel == 0 && fg_pixel == 0) + { + // The background pixel is transparent + // The foreground pixel is transparent + // No winner, draw "background" colour + pixel = 0x00; + palette = 0x00; + } + else if (bg_pixel == 0 && fg_pixel > 0) + { + // The background pixel is transparent + // The foreground pixel is visible + // Foreground wins! + pixel = fg_pixel; + palette = fg_palette; + } + else if (bg_pixel > 0 && fg_pixel == 0) + { + // The background pixel is visible + // The foreground pixel is transparent + // Background wins! + pixel = bg_pixel; + palette = bg_palette; + } + else if (bg_pixel > 0 && fg_pixel > 0) + { + // The background pixel is visible + // The foreground pixel is visible + // Hmmm... + if (fg_priority) + { + // Foreground cheats its way to victory! + pixel = fg_pixel; + palette = fg_palette; + } + else + { + // Background is considered more important! + pixel = bg_pixel; + palette = bg_palette; + } + + // Sprite Zero Hit detection + if (bSpriteZeroHitPossible && bSpriteZeroBeingRendered) + { + // Sprite zero is a collision between foreground and background + // so they must both be enabled + if (mask.render_background & mask.render_sprites) + { + // The left edge of the screen has specific switches to control + // its appearance. This is used to smooth inconsistencies when + // scrolling (since sprites x coord must be >= 0) + if (!(mask.render_background_left | mask.render_sprites_left)) + { + if (cycle >= 9 && cycle < 258) + { + status.sprite_zero_hit = 1; + } + } + else + { + if (cycle >= 1 && cycle < 258) + { + status.sprite_zero_hit = 1; + } + } + } + } + } + + // Now we have a final pixel colour, and a palette for this cycle + // of the current scanline. Let's at long last, draw that ^&%*er :P + sprScreen.SetPixel(cycle - 1, scanline, GetColourFromPaletteRam(palette, pixel)); + + // Advance renderer - it never stops, it's relentless + cycle++; + if(mask.render_background || mask.render_sprites) + if (cycle == 260 && scanline < 240) + { + cart->GetMapper()->scanline(); + } + + + + if (cycle >= 341) + { + cycle = 0; + scanline++; + if (scanline >= 261) + { + scanline = -1; + frame_complete = true; + odd_frame = !odd_frame; + } + } +} diff --git a/Part #7 - Mappers & Basic Sounds/olc2C02.h b/Part #7 - Mappers & Basic Sounds/olc2C02.h new file mode 100644 index 0000000..d232099 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olc2C02.h @@ -0,0 +1,239 @@ +/* + olc::NES - Picture Processing Unit (PPU) 2C02 + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/-THeUXqR3zY + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once +#include +#include + +#include "olcPixelGameEngine.h" + +#include "Cartridge.h" + +class olc2C02 +{ +public: + olc2C02(); + ~olc2C02(); + +private: + uint8_t tblName[2][1024]; + uint8_t tblPattern[2][4096]; + uint8_t tblPalette[32]; + +private: + olc::Pixel palScreen[0x40]; + olc::Sprite sprScreen = olc::Sprite(256, 240); + olc::Sprite sprNameTable[2] = { olc::Sprite(256, 240), olc::Sprite(256, 240) }; + olc::Sprite sprPatternTable[2] = { olc::Sprite(128, 128), olc::Sprite(128, 128) }; + +public: + // Debugging Utilities + olc::Sprite& GetScreen(); + olc::Sprite& GetNameTable(uint8_t i); + olc::Sprite& GetPatternTable(uint8_t i, uint8_t palette); + olc::Pixel& GetColourFromPaletteRam(uint8_t palette, uint8_t pixel); + bool frame_complete = false; + +private: + + union PPUSTATUS + { + struct + { + uint8_t unused : 5; + uint8_t sprite_overflow : 1; + uint8_t sprite_zero_hit : 1; + uint8_t vertical_blank : 1; + }; + + uint8_t reg; + } status; + + + union PPUMASK + { + struct + { + uint8_t grayscale : 1; + uint8_t render_background_left : 1; + uint8_t render_sprites_left : 1; + uint8_t render_background : 1; + uint8_t render_sprites : 1; + uint8_t enhance_red : 1; + uint8_t enhance_green : 1; + uint8_t enhance_blue : 1; + }; + + uint8_t reg; + } mask; + + union PPUCTRL + { + struct + { + uint8_t nametable_x : 1; + uint8_t nametable_y : 1; + uint8_t increment_mode : 1; + uint8_t pattern_sprite : 1; + uint8_t pattern_background : 1; + uint8_t sprite_size : 1; + uint8_t slave_mode : 1; // unused + uint8_t enable_nmi : 1; + }; + + uint8_t reg; + } control; + + union loopy_register + { + // Credit to Loopy for working this out :D + struct + { + + uint16_t coarse_x : 5; + uint16_t coarse_y : 5; + uint16_t nametable_x : 1; + uint16_t nametable_y : 1; + uint16_t fine_y : 3; + uint16_t unused : 1; + }; + + uint16_t reg = 0x0000; + }; + + + loopy_register vram_addr; // Active "pointer" address into nametable to extract background tile info + loopy_register tram_addr; // Temporary store of information to be "transferred" into "pointer" at various times + + // Pixel offset horizontally + uint8_t fine_x = 0x00; + + // Internal communications + uint8_t address_latch = 0x00; + uint8_t ppu_data_buffer = 0x00; + + // Pixel "dot" position information + int16_t scanline = 0; + int16_t cycle = 0; + bool odd_frame = false; + + // Background rendering ========================================= + uint8_t bg_next_tile_id = 0x00; + uint8_t bg_next_tile_attrib = 0x00; + uint8_t bg_next_tile_lsb = 0x00; + uint8_t bg_next_tile_msb = 0x00; + uint16_t bg_shifter_pattern_lo = 0x0000; + uint16_t bg_shifter_pattern_hi = 0x0000; + uint16_t bg_shifter_attrib_lo = 0x0000; + uint16_t bg_shifter_attrib_hi = 0x0000; + + + // Foreground "Sprite" rendering ================================ + // The OAM is an additional memory internal to the PPU. It is + // not connected via the any bus. It stores the locations of + // 64off 8x8 (or 8x16) tiles to be drawn on the next frame. + struct sObjectAttributeEntry + { + uint8_t y; // Y position of sprite + uint8_t id; // ID of tile from pattern memory + uint8_t attribute; // Flags define how sprite should be rendered + uint8_t x; // X position of sprite + } OAM[64]; + + // A register to store the address when the CPU manually communicates + // with OAM via PPU registers. This is not commonly used because it + // is very slow, and instead a 256-Byte DMA transfer is used. See + // the Bus header for a description of this. + uint8_t oam_addr = 0x00; + + + sObjectAttributeEntry spriteScanline[8]; + uint8_t sprite_count; + uint8_t sprite_shifter_pattern_lo[8]; + uint8_t sprite_shifter_pattern_hi[8]; + + // Sprite Zero Collision Flags + bool bSpriteZeroHitPossible = false; + bool bSpriteZeroBeingRendered = false; + + // The OAM is conveniently package above to work with, but the DMA + // mechanism will need access to it for writing one byute at a time +public: + uint8_t* pOAM = (uint8_t*)OAM; + + +public: + // Communications with Main Bus + uint8_t cpuRead(uint16_t addr, bool rdonly = false); + void cpuWrite(uint16_t addr, uint8_t data); + + // Communications with PPU Bus + uint8_t ppuRead(uint16_t addr, bool rdonly = false); + void ppuWrite(uint16_t addr, uint8_t data); + +private: + // The Cartridge or "GamePak" + std::shared_ptr cart; + +public: + // Interface + void ConnectCartridge(const std::shared_ptr& cartridge); + void clock(); + void reset(); + bool nmi = false; + bool scanline_trigger = false; +}; + diff --git a/Part #7 - Mappers & Basic Sounds/olc6502.cpp b/Part #7 - Mappers & Basic Sounds/olc6502.cpp new file mode 100644 index 0000000..0570036 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olc6502.cpp @@ -0,0 +1,1589 @@ +/* + olc6502 - An emulation of the 6502/2A03 processor + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Background + ~~~~~~~~~~ + I love this microprocessor. It was at the heart of two of my favourite + machines, the BBC Micro, and the Nintendo Entertainment System, as well + as countless others in that era. I learnt to program on the Model B, and + I learnt to love games on the NES, so in many ways, this processor is + why I am the way I am today. + + In February 2019, I decided to undertake a selfish personal project and + build a NES emulator. Ive always wanted to, and as such I've avoided + looking at source code for such things. This made making this a real + personal challenge. I know its been done countless times, and very likely + in far more clever and accurate ways than mine, but I'm proud of this. + + Datasheet: http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf + + Files: olc6502.h, olc6502.cpp + + Relevant Video: + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include "olc6502.h" +#include "Bus.h" + +// Constructor +olc6502::olc6502() +{ + // Assembles the translation table. It's big, it's ugly, but it yields a convenient way + // to emulate the 6502. I'm certain there are some "code-golf" strategies to reduce this + // but I've deliberately kept it verbose for study and alteration + + // It is 16x16 entries. This gives 256 instructions. It is arranged to that the bottom + // 4 bits of the instruction choose the column, and the top 4 bits choose the row. + + // For convenience to get function pointers to members of this class, I'm using this + // or else it will be much much larger :D + + // The table is one big initialiser list of initialiser lists... + using a = olc6502; + lookup = + { + { "BRK", &a::BRK, &a::IMM, 7 },{ "ORA", &a::ORA, &a::IZX, 6 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 3 },{ "ORA", &a::ORA, &a::ZP0, 3 },{ "ASL", &a::ASL, &a::ZP0, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "PHP", &a::PHP, &a::IMP, 3 },{ "ORA", &a::ORA, &a::IMM, 2 },{ "ASL", &a::ASL, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::NOP, &a::IMP, 4 },{ "ORA", &a::ORA, &a::ABS, 4 },{ "ASL", &a::ASL, &a::ABS, 6 },{ "???", &a::XXX, &a::IMP, 6 }, + { "BPL", &a::BPL, &a::REL, 2 },{ "ORA", &a::ORA, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 4 },{ "ORA", &a::ORA, &a::ZPX, 4 },{ "ASL", &a::ASL, &a::ZPX, 6 },{ "???", &a::XXX, &a::IMP, 6 },{ "CLC", &a::CLC, &a::IMP, 2 },{ "ORA", &a::ORA, &a::ABY, 4 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 7 },{ "???", &a::NOP, &a::IMP, 4 },{ "ORA", &a::ORA, &a::ABX, 4 },{ "ASL", &a::ASL, &a::ABX, 7 },{ "???", &a::XXX, &a::IMP, 7 }, + { "JSR", &a::JSR, &a::ABS, 6 },{ "AND", &a::AND, &a::IZX, 6 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "BIT", &a::BIT, &a::ZP0, 3 },{ "AND", &a::AND, &a::ZP0, 3 },{ "ROL", &a::ROL, &a::ZP0, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "PLP", &a::PLP, &a::IMP, 4 },{ "AND", &a::AND, &a::IMM, 2 },{ "ROL", &a::ROL, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "BIT", &a::BIT, &a::ABS, 4 },{ "AND", &a::AND, &a::ABS, 4 },{ "ROL", &a::ROL, &a::ABS, 6 },{ "???", &a::XXX, &a::IMP, 6 }, + { "BMI", &a::BMI, &a::REL, 2 },{ "AND", &a::AND, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 4 },{ "AND", &a::AND, &a::ZPX, 4 },{ "ROL", &a::ROL, &a::ZPX, 6 },{ "???", &a::XXX, &a::IMP, 6 },{ "SEC", &a::SEC, &a::IMP, 2 },{ "AND", &a::AND, &a::ABY, 4 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 7 },{ "???", &a::NOP, &a::IMP, 4 },{ "AND", &a::AND, &a::ABX, 4 },{ "ROL", &a::ROL, &a::ABX, 7 },{ "???", &a::XXX, &a::IMP, 7 }, + { "RTI", &a::RTI, &a::IMP, 6 },{ "EOR", &a::EOR, &a::IZX, 6 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 3 },{ "EOR", &a::EOR, &a::ZP0, 3 },{ "LSR", &a::LSR, &a::ZP0, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "PHA", &a::PHA, &a::IMP, 3 },{ "EOR", &a::EOR, &a::IMM, 2 },{ "LSR", &a::LSR, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "JMP", &a::JMP, &a::ABS, 3 },{ "EOR", &a::EOR, &a::ABS, 4 },{ "LSR", &a::LSR, &a::ABS, 6 },{ "???", &a::XXX, &a::IMP, 6 }, + { "BVC", &a::BVC, &a::REL, 2 },{ "EOR", &a::EOR, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 4 },{ "EOR", &a::EOR, &a::ZPX, 4 },{ "LSR", &a::LSR, &a::ZPX, 6 },{ "???", &a::XXX, &a::IMP, 6 },{ "CLI", &a::CLI, &a::IMP, 2 },{ "EOR", &a::EOR, &a::ABY, 4 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 7 },{ "???", &a::NOP, &a::IMP, 4 },{ "EOR", &a::EOR, &a::ABX, 4 },{ "LSR", &a::LSR, &a::ABX, 7 },{ "???", &a::XXX, &a::IMP, 7 }, + { "RTS", &a::RTS, &a::IMP, 6 },{ "ADC", &a::ADC, &a::IZX, 6 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 3 },{ "ADC", &a::ADC, &a::ZP0, 3 },{ "ROR", &a::ROR, &a::ZP0, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "PLA", &a::PLA, &a::IMP, 4 },{ "ADC", &a::ADC, &a::IMM, 2 },{ "ROR", &a::ROR, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "JMP", &a::JMP, &a::IND, 5 },{ "ADC", &a::ADC, &a::ABS, 4 },{ "ROR", &a::ROR, &a::ABS, 6 },{ "???", &a::XXX, &a::IMP, 6 }, + { "BVS", &a::BVS, &a::REL, 2 },{ "ADC", &a::ADC, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 4 },{ "ADC", &a::ADC, &a::ZPX, 4 },{ "ROR", &a::ROR, &a::ZPX, 6 },{ "???", &a::XXX, &a::IMP, 6 },{ "SEI", &a::SEI, &a::IMP, 2 },{ "ADC", &a::ADC, &a::ABY, 4 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 7 },{ "???", &a::NOP, &a::IMP, 4 },{ "ADC", &a::ADC, &a::ABX, 4 },{ "ROR", &a::ROR, &a::ABX, 7 },{ "???", &a::XXX, &a::IMP, 7 }, + { "???", &a::NOP, &a::IMP, 2 },{ "STA", &a::STA, &a::IZX, 6 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 6 },{ "STY", &a::STY, &a::ZP0, 3 },{ "STA", &a::STA, &a::ZP0, 3 },{ "STX", &a::STX, &a::ZP0, 3 },{ "???", &a::XXX, &a::IMP, 3 },{ "DEY", &a::DEY, &a::IMP, 2 },{ "???", &a::NOP, &a::IMP, 2 },{ "TXA", &a::TXA, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "STY", &a::STY, &a::ABS, 4 },{ "STA", &a::STA, &a::ABS, 4 },{ "STX", &a::STX, &a::ABS, 4 },{ "???", &a::XXX, &a::IMP, 4 }, + { "BCC", &a::BCC, &a::REL, 2 },{ "STA", &a::STA, &a::IZY, 6 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 6 },{ "STY", &a::STY, &a::ZPX, 4 },{ "STA", &a::STA, &a::ZPX, 4 },{ "STX", &a::STX, &a::ZPY, 4 },{ "???", &a::XXX, &a::IMP, 4 },{ "TYA", &a::TYA, &a::IMP, 2 },{ "STA", &a::STA, &a::ABY, 5 },{ "TXS", &a::TXS, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 5 },{ "???", &a::NOP, &a::IMP, 5 },{ "STA", &a::STA, &a::ABX, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "???", &a::XXX, &a::IMP, 5 }, + { "LDY", &a::LDY, &a::IMM, 2 },{ "LDA", &a::LDA, &a::IZX, 6 },{ "LDX", &a::LDX, &a::IMM, 2 },{ "???", &a::XXX, &a::IMP, 6 },{ "LDY", &a::LDY, &a::ZP0, 3 },{ "LDA", &a::LDA, &a::ZP0, 3 },{ "LDX", &a::LDX, &a::ZP0, 3 },{ "???", &a::XXX, &a::IMP, 3 },{ "TAY", &a::TAY, &a::IMP, 2 },{ "LDA", &a::LDA, &a::IMM, 2 },{ "TAX", &a::TAX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "LDY", &a::LDY, &a::ABS, 4 },{ "LDA", &a::LDA, &a::ABS, 4 },{ "LDX", &a::LDX, &a::ABS, 4 },{ "???", &a::XXX, &a::IMP, 4 }, + { "BCS", &a::BCS, &a::REL, 2 },{ "LDA", &a::LDA, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 5 },{ "LDY", &a::LDY, &a::ZPX, 4 },{ "LDA", &a::LDA, &a::ZPX, 4 },{ "LDX", &a::LDX, &a::ZPY, 4 },{ "???", &a::XXX, &a::IMP, 4 },{ "CLV", &a::CLV, &a::IMP, 2 },{ "LDA", &a::LDA, &a::ABY, 4 },{ "TSX", &a::TSX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 4 },{ "LDY", &a::LDY, &a::ABX, 4 },{ "LDA", &a::LDA, &a::ABX, 4 },{ "LDX", &a::LDX, &a::ABY, 4 },{ "???", &a::XXX, &a::IMP, 4 }, + { "CPY", &a::CPY, &a::IMM, 2 },{ "CMP", &a::CMP, &a::IZX, 6 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "CPY", &a::CPY, &a::ZP0, 3 },{ "CMP", &a::CMP, &a::ZP0, 3 },{ "DEC", &a::DEC, &a::ZP0, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "INY", &a::INY, &a::IMP, 2 },{ "CMP", &a::CMP, &a::IMM, 2 },{ "DEX", &a::DEX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 2 },{ "CPY", &a::CPY, &a::ABS, 4 },{ "CMP", &a::CMP, &a::ABS, 4 },{ "DEC", &a::DEC, &a::ABS, 6 },{ "???", &a::XXX, &a::IMP, 6 }, + { "BNE", &a::BNE, &a::REL, 2 },{ "CMP", &a::CMP, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 4 },{ "CMP", &a::CMP, &a::ZPX, 4 },{ "DEC", &a::DEC, &a::ZPX, 6 },{ "???", &a::XXX, &a::IMP, 6 },{ "CLD", &a::CLD, &a::IMP, 2 },{ "CMP", &a::CMP, &a::ABY, 4 },{ "NOP", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 7 },{ "???", &a::NOP, &a::IMP, 4 },{ "CMP", &a::CMP, &a::ABX, 4 },{ "DEC", &a::DEC, &a::ABX, 7 },{ "???", &a::XXX, &a::IMP, 7 }, + { "CPX", &a::CPX, &a::IMM, 2 },{ "SBC", &a::SBC, &a::IZX, 6 },{ "???", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "CPX", &a::CPX, &a::ZP0, 3 },{ "SBC", &a::SBC, &a::ZP0, 3 },{ "INC", &a::INC, &a::ZP0, 5 },{ "???", &a::XXX, &a::IMP, 5 },{ "INX", &a::INX, &a::IMP, 2 },{ "SBC", &a::SBC, &a::IMM, 2 },{ "NOP", &a::NOP, &a::IMP, 2 },{ "???", &a::SBC, &a::IMP, 2 },{ "CPX", &a::CPX, &a::ABS, 4 },{ "SBC", &a::SBC, &a::ABS, 4 },{ "INC", &a::INC, &a::ABS, 6 },{ "???", &a::XXX, &a::IMP, 6 }, + { "BEQ", &a::BEQ, &a::REL, 2 },{ "SBC", &a::SBC, &a::IZY, 5 },{ "???", &a::XXX, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 8 },{ "???", &a::NOP, &a::IMP, 4 },{ "SBC", &a::SBC, &a::ZPX, 4 },{ "INC", &a::INC, &a::ZPX, 6 },{ "???", &a::XXX, &a::IMP, 6 },{ "SED", &a::SED, &a::IMP, 2 },{ "SBC", &a::SBC, &a::ABY, 4 },{ "NOP", &a::NOP, &a::IMP, 2 },{ "???", &a::XXX, &a::IMP, 7 },{ "???", &a::NOP, &a::IMP, 4 },{ "SBC", &a::SBC, &a::ABX, 4 },{ "INC", &a::INC, &a::ABX, 7 },{ "???", &a::XXX, &a::IMP, 7 }, + }; +} + +olc6502::~olc6502() +{ + // Destructor - has nothing to do +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// BUS CONNECTIVITY + +// Reads an 8-bit byte from the bus, located at the specified 16-bit address +uint8_t olc6502::read(uint16_t a) +{ + // In normal operation "read only" is set to false. This may seem odd. Some + // devices on the bus may change state when they are read from, and this + // is intentional under normal circumstances. However the disassembler will + // want to read the data at an address without changing the state of the + // devices on the bus + return bus->cpuRead(a, false); +} + +// Writes a byte to the bus at the specified address +void olc6502::write(uint16_t a, uint8_t d) +{ + bus->cpuWrite(a, d); +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// EXTERNAL INPUTS + +// Forces the 6502 into a known state. This is hard-wired inside the CPU. The +// registers are set to 0x00, the status register is cleared except for unused +// bit which remains at 1. An absolute address is read from location 0xFFFC +// which contains a second address that the program counter is set to. This +// allows the programmer to jump to a known and programmable location in the +// memory to start executing from. Typically the programmer would set the value +// at location 0xFFFC at compile time. +void olc6502::reset() +{ + // Get address to set program counter to + addr_abs = 0xFFFC; + uint16_t lo = read(addr_abs + 0); + uint16_t hi = read(addr_abs + 1); + + // Set it + pc = (hi << 8) | lo; + + // Reset internal registers + a = 0; + x = 0; + y = 0; + stkp = 0xFD; + status = 0x00 | U; + + // Clear internal helper variables + addr_rel = 0x0000; + addr_abs = 0x0000; + fetched = 0x00; + + // Reset takes time + cycles = 8; +} + + +// Interrupt requests are a complex operation and only happen if the +// "disable interrupt" flag is 0. IRQs can happen at any time, but +// you dont want them to be destructive to the operation of the running +// program. Therefore the current instruction is allowed to finish +// (which I facilitate by doing the whole thing when cycles == 0) and +// then the current program counter is stored on the stack. Then the +// current status register is stored on the stack. When the routine +// that services the interrupt has finished, the status register +// and program counter can be restored to how they where before it +// occurred. This is impemented by the "RTI" instruction. Once the IRQ +// has happened, in a similar way to a reset, a programmable address +// is read form hard coded location 0xFFFE, which is subsequently +// set to the program counter. +void olc6502::irq() +{ + // If interrupts are allowed + if (GetFlag(I) == 0) + { + // Push the program counter to the stack. It's 16-bits dont + // forget so that takes two pushes + write(0x0100 + stkp, (pc >> 8) & 0x00FF); + stkp--; + write(0x0100 + stkp, pc & 0x00FF); + stkp--; + + // Then Push the status register to the stack + SetFlag(B, 0); + SetFlag(U, 1); + SetFlag(I, 1); + write(0x0100 + stkp, status); + stkp--; + + // Read new program counter location from fixed address + addr_abs = 0xFFFE; + uint16_t lo = read(addr_abs + 0); + uint16_t hi = read(addr_abs + 1); + pc = (hi << 8) | lo; + + // IRQs take time + cycles = 7; + } +} + + +// A Non-Maskable Interrupt cannot be ignored. It behaves in exactly the +// same way as a regular IRQ, but reads the new program counter address +// form location 0xFFFA. +void olc6502::nmi() +{ + write(0x0100 + stkp, (pc >> 8) & 0x00FF); + stkp--; + write(0x0100 + stkp, pc & 0x00FF); + stkp--; + + SetFlag(B, 0); + SetFlag(U, 1); + SetFlag(I, 1); + write(0x0100 + stkp, status); + stkp--; + + addr_abs = 0xFFFA; + uint16_t lo = read(addr_abs + 0); + uint16_t hi = read(addr_abs + 1); + pc = (hi << 8) | lo; + + cycles = 8; +} + +// Perform one clock cycles worth of emulation +void olc6502::clock() +{ + // Each instruction requires a variable number of clock cycles to execute. + // In my emulation, I only care about the final result and so I perform + // the entire computation in one hit. In hardware, each clock cycle would + // perform "microcode" style transformations of the CPUs state. + // + // To remain compliant with connected devices, it's important that the + // emulation also takes "time" in order to execute instructions, so I + // implement that delay by simply counting down the cycles required by + // the instruction. When it reaches 0, the instruction is complete, and + // the next one is ready to be executed. + if (cycles == 0) + { + // Read next instruction byte. This 8-bit value is used to index + // the translation table to get the relevant information about + // how to implement the instruction + opcode = read(pc); + +#ifdef LOGMODE + uint16_t log_pc = pc; +#endif + + // Always set the unused status flag bit to 1 + SetFlag(U, true); + + // Increment program counter, we read the opcode byte + pc++; + + // Get Starting number of cycles + cycles = lookup[opcode].cycles; + + // Perform fetch of intermmediate data using the + // required addressing mode + uint8_t additional_cycle1 = (this->*lookup[opcode].addrmode)(); + + // Perform operation + uint8_t additional_cycle2 = (this->*lookup[opcode].operate)(); + + // The addressmode and opcode may have altered the number + // of cycles this instruction requires before its completed + cycles += (additional_cycle1 & additional_cycle2); + + // Always set the unused status flag bit to 1 + SetFlag(U, true); + +#ifdef LOGMODE + // This logger dumps every cycle the entire processor state for analysis. + // This can be used for debugging the emulation, but has little utility + // during emulation. Its also very slow, so only use if you have to. + if (logfile == nullptr) logfile = fopen("olc6502.txt", "wt"); + if (logfile != nullptr) + { + fprintf(logfile, "%10d:%02d PC:%04X %s A:%02X X:%02X Y:%02X %s%s%s%s%s%s%s%s STKP:%02X\n", + clock_count, 0, log_pc, "XXX", a, x, y, + GetFlag(N) ? "N" : ".", GetFlag(V) ? "V" : ".", GetFlag(U) ? "U" : ".", + GetFlag(B) ? "B" : ".", GetFlag(D) ? "D" : ".", GetFlag(I) ? "I" : ".", + GetFlag(Z) ? "Z" : ".", GetFlag(C) ? "C" : ".", stkp); + } +#endif + } + + // Increment global clock count - This is actually unused unless logging is enabled + // but I've kept it in because its a handy watch variable for debugging + clock_count++; + + // Decrement the number of cycles remaining for this instruction + cycles--; +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// FLAG FUNCTIONS + +// Returns the value of a specific bit of the status register +uint8_t olc6502::GetFlag(FLAGS6502 f) +{ + return ((status & f) > 0) ? 1 : 0; +} + +// Sets or clears a specific bit of the status register +void olc6502::SetFlag(FLAGS6502 f, bool v) +{ + if (v) + status |= f; + else + status &= ~f; +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// ADDRESSING MODES + +// The 6502 can address between 0x0000 - 0xFFFF. The high byte is often referred +// to as the "page", and the low byte is the offset into that page. This implies +// there are 256 pages, each containing 256 bytes. +// +// Several addressing modes have the potential to require an additional clock +// cycle if they cross a page boundary. This is combined with several instructions +// that enable this additional clock cycle. So each addressing function returns +// a flag saying it has potential, as does each instruction. If both instruction +// and address function return 1, then an additional clock cycle is required. + + +// Address Mode: Implied +// There is no additional data required for this instruction. The instruction +// does something very simple like like sets a status bit. However, we will +// target the accumulator, for instructions like PHA +uint8_t olc6502::IMP() +{ + fetched = a; + return 0; +} + + +// Address Mode: Immediate +// The instruction expects the next byte to be used as a value, so we'll prep +// the read address to point to the next byte +uint8_t olc6502::IMM() +{ + addr_abs = pc++; + return 0; +} + + + +// Address Mode: Zero Page +// To save program bytes, zero page addressing allows you to absolutely address +// a location in first 0xFF bytes of address range. Clearly this only requires +// one byte instead of the usual two. +uint8_t olc6502::ZP0() +{ + addr_abs = read(pc); + pc++; + addr_abs &= 0x00FF; + return 0; +} + + + +// Address Mode: Zero Page with X Offset +// Fundamentally the same as Zero Page addressing, but the contents of the X Register +// is added to the supplied single byte address. This is useful for iterating through +// ranges within the first page. +uint8_t olc6502::ZPX() +{ + addr_abs = (read(pc) + x); + pc++; + addr_abs &= 0x00FF; + return 0; +} + + +// Address Mode: Zero Page with Y Offset +// Same as above but uses Y Register for offset +uint8_t olc6502::ZPY() +{ + addr_abs = (read(pc) + y); + pc++; + addr_abs &= 0x00FF; + return 0; +} + + +// Address Mode: Relative +// This address mode is exclusive to branch instructions. The address +// must reside within -128 to +127 of the branch instruction, i.e. +// you cant directly branch to any address in the addressable range. +uint8_t olc6502::REL() +{ + addr_rel = read(pc); + pc++; + if (addr_rel & 0x80) + addr_rel |= 0xFF00; + return 0; +} + + +// Address Mode: Absolute +// A full 16-bit address is loaded and used +uint8_t olc6502::ABS() +{ + uint16_t lo = read(pc); + pc++; + uint16_t hi = read(pc); + pc++; + + addr_abs = (hi << 8) | lo; + + return 0; +} + + +// Address Mode: Absolute with X Offset +// Fundamentally the same as absolute addressing, but the contents of the X Register +// is added to the supplied two byte address. If the resulting address changes +// the page, an additional clock cycle is required +uint8_t olc6502::ABX() +{ + uint16_t lo = read(pc); + pc++; + uint16_t hi = read(pc); + pc++; + + addr_abs = (hi << 8) | lo; + addr_abs += x; + + if ((addr_abs & 0xFF00) != (hi << 8)) + return 1; + else + return 0; +} + + +// Address Mode: Absolute with Y Offset +// Fundamentally the same as absolute addressing, but the contents of the Y Register +// is added to the supplied two byte address. If the resulting address changes +// the page, an additional clock cycle is required +uint8_t olc6502::ABY() +{ + uint16_t lo = read(pc); + pc++; + uint16_t hi = read(pc); + pc++; + + addr_abs = (hi << 8) | lo; + addr_abs += y; + + if ((addr_abs & 0xFF00) != (hi << 8)) + return 1; + else + return 0; +} + +// Note: The next 3 address modes use indirection (aka Pointers!) + +// Address Mode: Indirect +// The supplied 16-bit address is read to get the actual 16-bit address. This is +// instruction is unusual in that it has a bug in the hardware! To emulate its +// function accurately, we also need to emulate this bug. If the low byte of the +// supplied address is 0xFF, then to read the high byte of the actual address +// we need to cross a page boundary. This doesnt actually work on the chip as +// designed, instead it wraps back around in the same page, yielding an +// invalid actual address +uint8_t olc6502::IND() +{ + uint16_t ptr_lo = read(pc); + pc++; + uint16_t ptr_hi = read(pc); + pc++; + + uint16_t ptr = (ptr_hi << 8) | ptr_lo; + + if (ptr_lo == 0x00FF) // Simulate page boundary hardware bug + { + addr_abs = (read(ptr & 0xFF00) << 8) | read(ptr + 0); + } + else // Behave normally + { + addr_abs = (read(ptr + 1) << 8) | read(ptr + 0); + } + + return 0; +} + + +// Address Mode: Indirect X +// The supplied 8-bit address is offset by X Register to index +// a location in page 0x00. The actual 16-bit address is read +// from this location +uint8_t olc6502::IZX() +{ + uint16_t t = read(pc); + pc++; + + uint16_t lo = read((uint16_t)(t + (uint16_t)x) & 0x00FF); + uint16_t hi = read((uint16_t)(t + (uint16_t)x + 1) & 0x00FF); + + addr_abs = (hi << 8) | lo; + + return 0; +} + + +// Address Mode: Indirect Y +// The supplied 8-bit address indexes a location in page 0x00. From +// here the actual 16-bit address is read, and the contents of +// Y Register is added to it to offset it. If the offset causes a +// change in page then an additional clock cycle is required. +uint8_t olc6502::IZY() +{ + uint16_t t = read(pc); + pc++; + + uint16_t lo = read(t & 0x00FF); + uint16_t hi = read((t + 1) & 0x00FF); + + addr_abs = (hi << 8) | lo; + addr_abs += y; + + if ((addr_abs & 0xFF00) != (hi << 8)) + return 1; + else + return 0; +} + + + +// This function sources the data used by the instruction into +// a convenient numeric variable. Some instructions dont have to +// fetch data as the source is implied by the instruction. For example +// "INX" increments the X register. There is no additional data +// required. For all other addressing modes, the data resides at +// the location held within addr_abs, so it is read from there. +// Immediate adress mode exploits this slightly, as that has +// set addr_abs = pc + 1, so it fetches the data from the +// next byte for example "LDA $FF" just loads the accumulator with +// 256, i.e. no far reaching memory fetch is required. "fetched" +// is a variable global to the CPU, and is set by calling this +// function. It also returns it for convenience. +uint8_t olc6502::fetch() +{ + if (!(lookup[opcode].addrmode == &olc6502::IMP)) + fetched = read(addr_abs); + return fetched; +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// INSTRUCTION IMPLEMENTATIONS + +// Note: Ive started with the two most complicated instructions to emulate, which +// ironically is addition and subtraction! Ive tried to include a detailed +// explanation as to why they are so complex, yet so fundamental. Im also NOT +// going to do this through the explanation of 1 and 2's complement. + +// Instruction: Add with Carry In +// Function: A = A + M + C +// Flags Out: C, V, N, Z +// +// Explanation: +// The purpose of this function is to add a value to the accumulator and a carry bit. If +// the result is > 255 there is an overflow setting the carry bit. Ths allows you to +// chain together ADC instructions to add numbers larger than 8-bits. This in itself is +// simple, however the 6502 supports the concepts of Negativity/Positivity and Signed Overflow. +// +// 10000100 = 128 + 4 = 132 in normal circumstances, we know this as unsigned and it allows +// us to represent numbers between 0 and 255 (given 8 bits). The 6502 can also interpret +// this word as something else if we assume those 8 bits represent the range -128 to +127, +// i.e. it has become signed. +// +// Since 132 > 127, it effectively wraps around, through -128, to -124. This wraparound is +// called overflow, and this is a useful to know as it indicates that the calculation has +// gone outside the permissable range, and therefore no longer makes numeric sense. +// +// Note the implementation of ADD is the same in binary, this is just about how the numbers +// are represented, so the word 10000100 can be both -124 and 132 depending upon the +// context the programming is using it in. We can prove this! +// +// 10000100 = 132 or -124 +// +00010001 = + 17 + 17 +// ======== === === See, both are valid additions, but our interpretation of +// 10010101 = 149 or -107 the context changes the value, not the hardware! +// +// In principle under the -128 to 127 range: +// 10000000 = -128, 11111111 = -1, 00000000 = 0, 00000000 = +1, 01111111 = +127 +// therefore negative numbers have the most significant set, positive numbers do not +// +// To assist us, the 6502 can set the overflow flag, if the result of the addition has +// wrapped around. V <- ~(A^M) & A^(A+M+C) :D lol, let's work out why! +// +// Let's suppose we have A = 30, M = 10 and C = 0 +// A = 30 = 00011110 +// M = 10 = 00001010+ +// RESULT = 40 = 00101000 +// +// Here we have not gone out of range. The resulting significant bit has not changed. +// So let's make a truth table to understand when overflow has occurred. Here I take +// the MSB of each component, where R is RESULT. +// +// A M R | V | A^R | A^M |~(A^M) | +// 0 0 0 | 0 | 0 | 0 | 1 | +// 0 0 1 | 1 | 1 | 0 | 1 | +// 0 1 0 | 0 | 0 | 1 | 0 | +// 0 1 1 | 0 | 1 | 1 | 0 | so V = ~(A^M) & (A^R) +// 1 0 0 | 0 | 1 | 1 | 0 | +// 1 0 1 | 0 | 0 | 1 | 0 | +// 1 1 0 | 1 | 1 | 0 | 1 | +// 1 1 1 | 0 | 0 | 0 | 1 | +// +// We can see how the above equation calculates V, based on A, M and R. V was chosen +// based on the following hypothesis: +// Positive Number + Positive Number = Negative Result -> Overflow +// Negative Number + Negative Number = Positive Result -> Overflow +// Positive Number + Negative Number = Either Result -> Cannot Overflow +// Positive Number + Positive Number = Positive Result -> OK! No Overflow +// Negative Number + Negative Number = Negative Result -> OK! NO Overflow + +uint8_t olc6502::ADC() +{ + // Grab the data that we are adding to the accumulator + fetch(); + + // Add is performed in 16-bit domain for emulation to capture any + // carry bit, which will exist in bit 8 of the 16-bit word + temp = (uint16_t)a + (uint16_t)fetched + (uint16_t)GetFlag(C); + + // The carry flag out exists in the high byte bit 0 + SetFlag(C, temp > 255); + + // The Zero flag is set if the result is 0 + SetFlag(Z, (temp & 0x00FF) == 0); + + // The signed Overflow flag is set based on all that up there! :D + SetFlag(V, (~((uint16_t)a ^ (uint16_t)fetched) & ((uint16_t)a ^ (uint16_t)temp)) & 0x0080); + + // The negative flag is set to the most significant bit of the result + SetFlag(N, temp & 0x80); + + // Load the result into the accumulator (it's 8-bit dont forget!) + a = temp & 0x00FF; + + // This instruction has the potential to require an additional clock cycle + return 1; +} + + +// Instruction: Subtraction with Borrow In +// Function: A = A - M - (1 - C) +// Flags Out: C, V, N, Z +// +// Explanation: +// Given the explanation for ADC above, we can reorganise our data +// to use the same computation for addition, for subtraction by multiplying +// the data by -1, i.e. make it negative +// +// A = A - M - (1 - C) -> A = A + -1 * (M - (1 - C)) -> A = A + (-M + 1 + C) +// +// To make a signed positive number negative, we can invert the bits and add 1 +// (OK, I lied, a little bit of 1 and 2s complement :P) +// +// 5 = 00000101 +// -5 = 11111010 + 00000001 = 11111011 (or 251 in our 0 to 255 range) +// +// The range is actually unimportant, because if I take the value 15, and add 251 +// to it, given we wrap around at 256, the result is 10, so it has effectively +// subtracted 5, which was the original intention. (15 + 251) % 256 = 10 +// +// Note that the equation above used (1-C), but this got converted to + 1 + C. +// This means we already have the +1, so all we need to do is invert the bits +// of M, the data(!) therfore we can simply add, exactly the same way we did +// before. + +uint8_t olc6502::SBC() +{ + fetch(); + + // Operating in 16-bit domain to capture carry out + + // We can invert the bottom 8 bits with bitwise xor + uint16_t value = ((uint16_t)fetched) ^ 0x00FF; + + // Notice this is exactly the same as addition from here! + temp = (uint16_t)a + value + (uint16_t)GetFlag(C); + SetFlag(C, temp & 0xFF00); + SetFlag(Z, ((temp & 0x00FF) == 0)); + SetFlag(V, (temp ^ (uint16_t)a) & (temp ^ value) & 0x0080); + SetFlag(N, temp & 0x0080); + a = temp & 0x00FF; + return 1; +} + +// OK! Complicated operations are done! the following are much simpler +// and conventional. The typical order of events is: +// 1) Fetch the data you are working with +// 2) Perform calculation +// 3) Store the result in desired place +// 4) Set Flags of the status register +// 5) Return if instruction has potential to require additional +// clock cycle + + +// Instruction: Bitwise Logic AND +// Function: A = A & M +// Flags Out: N, Z +uint8_t olc6502::AND() +{ + fetch(); + a = a & fetched; + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 1; +} + + +// Instruction: Arithmetic Shift Left +// Function: A = C <- (A << 1) <- 0 +// Flags Out: N, Z, C +uint8_t olc6502::ASL() +{ + fetch(); + temp = (uint16_t)fetched << 1; + SetFlag(C, (temp & 0xFF00) > 0); + SetFlag(Z, (temp & 0x00FF) == 0x00); + SetFlag(N, temp & 0x80); + if (lookup[opcode].addrmode == &olc6502::IMP) + a = temp & 0x00FF; + else + write(addr_abs, temp & 0x00FF); + return 0; +} + + +// Instruction: Branch if Carry Clear +// Function: if(C == 0) pc = address +uint8_t olc6502::BCC() +{ + if (GetFlag(C) == 0) + { + cycles++; + addr_abs = pc + addr_rel; + + if((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + + +// Instruction: Branch if Carry Set +// Function: if(C == 1) pc = address +uint8_t olc6502::BCS() +{ + if (GetFlag(C) == 1) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + + +// Instruction: Branch if Equal +// Function: if(Z == 1) pc = address +uint8_t olc6502::BEQ() +{ + if (GetFlag(Z) == 1) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + +uint8_t olc6502::BIT() +{ + fetch(); + temp = a & fetched; + SetFlag(Z, (temp & 0x00FF) == 0x00); + SetFlag(N, fetched & (1 << 7)); + SetFlag(V, fetched & (1 << 6)); + return 0; +} + + +// Instruction: Branch if Negative +// Function: if(N == 1) pc = address +uint8_t olc6502::BMI() +{ + if (GetFlag(N) == 1) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + + +// Instruction: Branch if Not Equal +// Function: if(Z == 0) pc = address +uint8_t olc6502::BNE() +{ + if (GetFlag(Z) == 0) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + + +// Instruction: Branch if Positive +// Function: if(N == 0) pc = address +uint8_t olc6502::BPL() +{ + if (GetFlag(N) == 0) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + +// Instruction: Break +// Function: Program Sourced Interrupt +uint8_t olc6502::BRK() +{ + pc++; + + SetFlag(I, 1); + write(0x0100 + stkp, (pc >> 8) & 0x00FF); + stkp--; + write(0x0100 + stkp, pc & 0x00FF); + stkp--; + + SetFlag(B, 1); + write(0x0100 + stkp, status); + stkp--; + SetFlag(B, 0); + + pc = (uint16_t)read(0xFFFE) | ((uint16_t)read(0xFFFF) << 8); + return 0; +} + + +// Instruction: Branch if Overflow Clear +// Function: if(V == 0) pc = address +uint8_t olc6502::BVC() +{ + if (GetFlag(V) == 0) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + + +// Instruction: Branch if Overflow Set +// Function: if(V == 1) pc = address +uint8_t olc6502::BVS() +{ + if (GetFlag(V) == 1) + { + cycles++; + addr_abs = pc + addr_rel; + + if ((addr_abs & 0xFF00) != (pc & 0xFF00)) + cycles++; + + pc = addr_abs; + } + return 0; +} + + +// Instruction: Clear Carry Flag +// Function: C = 0 +uint8_t olc6502::CLC() +{ + SetFlag(C, false); + return 0; +} + + +// Instruction: Clear Decimal Flag +// Function: D = 0 +uint8_t olc6502::CLD() +{ + SetFlag(D, false); + return 0; +} + + +// Instruction: Disable Interrupts / Clear Interrupt Flag +// Function: I = 0 +uint8_t olc6502::CLI() +{ + SetFlag(I, false); + return 0; +} + + +// Instruction: Clear Overflow Flag +// Function: V = 0 +uint8_t olc6502::CLV() +{ + SetFlag(V, false); + return 0; +} + +// Instruction: Compare Accumulator +// Function: C <- A >= M Z <- (A - M) == 0 +// Flags Out: N, C, Z +uint8_t olc6502::CMP() +{ + fetch(); + temp = (uint16_t)a - (uint16_t)fetched; + SetFlag(C, a >= fetched); + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + return 1; +} + + +// Instruction: Compare X Register +// Function: C <- X >= M Z <- (X - M) == 0 +// Flags Out: N, C, Z +uint8_t olc6502::CPX() +{ + fetch(); + temp = (uint16_t)x - (uint16_t)fetched; + SetFlag(C, x >= fetched); + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + return 0; +} + + +// Instruction: Compare Y Register +// Function: C <- Y >= M Z <- (Y - M) == 0 +// Flags Out: N, C, Z +uint8_t olc6502::CPY() +{ + fetch(); + temp = (uint16_t)y - (uint16_t)fetched; + SetFlag(C, y >= fetched); + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + return 0; +} + + +// Instruction: Decrement Value at Memory Location +// Function: M = M - 1 +// Flags Out: N, Z +uint8_t olc6502::DEC() +{ + fetch(); + temp = fetched - 1; + write(addr_abs, temp & 0x00FF); + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + return 0; +} + + +// Instruction: Decrement X Register +// Function: X = X - 1 +// Flags Out: N, Z +uint8_t olc6502::DEX() +{ + x--; + SetFlag(Z, x == 0x00); + SetFlag(N, x & 0x80); + return 0; +} + + +// Instruction: Decrement Y Register +// Function: Y = Y - 1 +// Flags Out: N, Z +uint8_t olc6502::DEY() +{ + y--; + SetFlag(Z, y == 0x00); + SetFlag(N, y & 0x80); + return 0; +} + + +// Instruction: Bitwise Logic XOR +// Function: A = A xor M +// Flags Out: N, Z +uint8_t olc6502::EOR() +{ + fetch(); + a = a ^ fetched; + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 1; +} + + +// Instruction: Increment Value at Memory Location +// Function: M = M + 1 +// Flags Out: N, Z +uint8_t olc6502::INC() +{ + fetch(); + temp = fetched + 1; + write(addr_abs, temp & 0x00FF); + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + return 0; +} + + +// Instruction: Increment X Register +// Function: X = X + 1 +// Flags Out: N, Z +uint8_t olc6502::INX() +{ + x++; + SetFlag(Z, x == 0x00); + SetFlag(N, x & 0x80); + return 0; +} + + +// Instruction: Increment Y Register +// Function: Y = Y + 1 +// Flags Out: N, Z +uint8_t olc6502::INY() +{ + y++; + SetFlag(Z, y == 0x00); + SetFlag(N, y & 0x80); + return 0; +} + + +// Instruction: Jump To Location +// Function: pc = address +uint8_t olc6502::JMP() +{ + pc = addr_abs; + return 0; +} + + +// Instruction: Jump To Sub-Routine +// Function: Push current pc to stack, pc = address +uint8_t olc6502::JSR() +{ + pc--; + + write(0x0100 + stkp, (pc >> 8) & 0x00FF); + stkp--; + write(0x0100 + stkp, pc & 0x00FF); + stkp--; + + pc = addr_abs; + return 0; +} + + +// Instruction: Load The Accumulator +// Function: A = M +// Flags Out: N, Z +uint8_t olc6502::LDA() +{ + fetch(); + a = fetched; + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 1; +} + + +// Instruction: Load The X Register +// Function: X = M +// Flags Out: N, Z +uint8_t olc6502::LDX() +{ + fetch(); + x = fetched; + SetFlag(Z, x == 0x00); + SetFlag(N, x & 0x80); + return 1; +} + + +// Instruction: Load The Y Register +// Function: Y = M +// Flags Out: N, Z +uint8_t olc6502::LDY() +{ + fetch(); + y = fetched; + SetFlag(Z, y == 0x00); + SetFlag(N, y & 0x80); + return 1; +} + +uint8_t olc6502::LSR() +{ + fetch(); + SetFlag(C, fetched & 0x0001); + temp = fetched >> 1; + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + if (lookup[opcode].addrmode == &olc6502::IMP) + a = temp & 0x00FF; + else + write(addr_abs, temp & 0x00FF); + return 0; +} + +uint8_t olc6502::NOP() +{ + switch (opcode) { + case 0x1C: + case 0x3C: + case 0x5C: + case 0x7C: + case 0xDC: + case 0xFC: + return 1; + break; + } + return 0; +} + + +// Instruction: Bitwise Logic OR +// Function: A = A | M +// Flags Out: N, Z +uint8_t olc6502::ORA() +{ + fetch(); + a = a | fetched; + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 1; +} + + +// Instruction: Push Accumulator to Stack +// Function: A -> stack +uint8_t olc6502::PHA() +{ + write(0x0100 + stkp, a); + stkp--; + return 0; +} + + +// Instruction: Push Status Register to Stack +// Function: status -> stack +// Note: Break flag is set to 1 before push +uint8_t olc6502::PHP() +{ + write(0x0100 + stkp, status | B | U); + SetFlag(B, 0); + SetFlag(U, 0); + stkp--; + return 0; +} + + +// Instruction: Pop Accumulator off Stack +// Function: A <- stack +// Flags Out: N, Z +uint8_t olc6502::PLA() +{ + stkp++; + a = read(0x0100 + stkp); + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 0; +} + + +// Instruction: Pop Status Register off Stack +// Function: Status <- stack +uint8_t olc6502::PLP() +{ + stkp++; + status = read(0x0100 + stkp); + SetFlag(U, 1); + return 0; +} + +uint8_t olc6502::ROL() +{ + fetch(); + temp = (uint16_t)(fetched << 1) | GetFlag(C); + SetFlag(C, temp & 0xFF00); + SetFlag(Z, (temp & 0x00FF) == 0x0000); + SetFlag(N, temp & 0x0080); + if (lookup[opcode].addrmode == &olc6502::IMP) + a = temp & 0x00FF; + else + write(addr_abs, temp & 0x00FF); + return 0; +} + +uint8_t olc6502::ROR() +{ + fetch(); + temp = (uint16_t)(GetFlag(C) << 7) | (fetched >> 1); + SetFlag(C, fetched & 0x01); + SetFlag(Z, (temp & 0x00FF) == 0x00); + SetFlag(N, temp & 0x0080); + if (lookup[opcode].addrmode == &olc6502::IMP) + a = temp & 0x00FF; + else + write(addr_abs, temp & 0x00FF); + return 0; +} + +uint8_t olc6502::RTI() +{ + stkp++; + status = read(0x0100 + stkp); + status &= ~B; + status &= ~U; + + stkp++; + pc = (uint16_t)read(0x0100 + stkp); + stkp++; + pc |= (uint16_t)read(0x0100 + stkp) << 8; + return 0; +} + +uint8_t olc6502::RTS() +{ + stkp++; + pc = (uint16_t)read(0x0100 + stkp); + stkp++; + pc |= (uint16_t)read(0x0100 + stkp) << 8; + + pc++; + return 0; +} + + + + +// Instruction: Set Carry Flag +// Function: C = 1 +uint8_t olc6502::SEC() +{ + SetFlag(C, true); + return 0; +} + + +// Instruction: Set Decimal Flag +// Function: D = 1 +uint8_t olc6502::SED() +{ + SetFlag(D, true); + return 0; +} + + +// Instruction: Set Interrupt Flag / Enable Interrupts +// Function: I = 1 +uint8_t olc6502::SEI() +{ + SetFlag(I, true); + return 0; +} + + +// Instruction: Store Accumulator at Address +// Function: M = A +uint8_t olc6502::STA() +{ + write(addr_abs, a); + return 0; +} + + +// Instruction: Store X Register at Address +// Function: M = X +uint8_t olc6502::STX() +{ + write(addr_abs, x); + return 0; +} + + +// Instruction: Store Y Register at Address +// Function: M = Y +uint8_t olc6502::STY() +{ + write(addr_abs, y); + return 0; +} + + +// Instruction: Transfer Accumulator to X Register +// Function: X = A +// Flags Out: N, Z +uint8_t olc6502::TAX() +{ + x = a; + SetFlag(Z, x == 0x00); + SetFlag(N, x & 0x80); + return 0; +} + + +// Instruction: Transfer Accumulator to Y Register +// Function: Y = A +// Flags Out: N, Z +uint8_t olc6502::TAY() +{ + y = a; + SetFlag(Z, y == 0x00); + SetFlag(N, y & 0x80); + return 0; +} + + +// Instruction: Transfer Stack Pointer to X Register +// Function: X = stack pointer +// Flags Out: N, Z +uint8_t olc6502::TSX() +{ + x = stkp; + SetFlag(Z, x == 0x00); + SetFlag(N, x & 0x80); + return 0; +} + + +// Instruction: Transfer X Register to Accumulator +// Function: A = X +// Flags Out: N, Z +uint8_t olc6502::TXA() +{ + a = x; + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 0; +} + + +// Instruction: Transfer X Register to Stack Pointer +// Function: stack pointer = X +uint8_t olc6502::TXS() +{ + stkp = x; + return 0; +} + + +// Instruction: Transfer Y Register to Accumulator +// Function: A = Y +// Flags Out: N, Z +uint8_t olc6502::TYA() +{ + a = y; + SetFlag(Z, a == 0x00); + SetFlag(N, a & 0x80); + return 0; +} + + +// This function captures illegal opcodes +uint8_t olc6502::XXX() +{ + return 0; +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +// HELPER FUNCTIONS + +bool olc6502::complete() +{ + return cycles == 0; +} + +// This is the disassembly function. Its workings are not required for emulation. +// It is merely a convenience function to turn the binary instruction code into +// human readable form. Its included as part of the emulator because it can take +// advantage of many of the CPUs internal operations to do this. +std::map olc6502::disassemble(uint16_t nStart, uint16_t nStop) +{ + uint32_t addr = nStart; + uint8_t value = 0x00, lo = 0x00, hi = 0x00; + std::map mapLines; + uint16_t line_addr = 0; + + // A convenient utility to convert variables into + // hex strings because "modern C++"'s method with + // streams is atrocious + auto hex = [](uint32_t n, uint8_t d) + { + std::string s(d, '0'); + for (int i = d - 1; i >= 0; i--, n >>= 4) + s[i] = "0123456789ABCDEF"[n & 0xF]; + return s; + }; + + // Starting at the specified address we read an instruction + // byte, which in turn yields information from the lookup table + // as to how many additional bytes we need to read and what the + // addressing mode is. I need this info to assemble human readable + // syntax, which is different depending upon the addressing mode + + // As the instruction is decoded, a std::string is assembled + // with the readable output + while (addr <= (uint32_t)nStop) + { + line_addr = addr; + + // Prefix line with instruction address + std::string sInst = "$" + hex(addr, 4) + ": "; + + // Read instruction, and get its readable name + uint8_t opcode = bus->cpuRead(addr, true); addr++; + sInst += lookup[opcode].name + " "; + + // Get oprands from desired locations, and form the + // instruction based upon its addressing mode. These + // routines mimmick the actual fetch routine of the + // 6502 in order to get accurate data as part of the + // instruction + if (lookup[opcode].addrmode == &olc6502::IMP) + { + sInst += " {IMP}"; + } + else if (lookup[opcode].addrmode == &olc6502::IMM) + { + value = bus->cpuRead(addr, true); addr++; + sInst += "#$" + hex(value, 2) + " {IMM}"; + } + else if (lookup[opcode].addrmode == &olc6502::ZP0) + { + lo = bus->cpuRead(addr, true); addr++; + hi = 0x00; + sInst += "$" + hex(lo, 2) + " {ZP0}"; + } + else if (lookup[opcode].addrmode == &olc6502::ZPX) + { + lo = bus->cpuRead(addr, true); addr++; + hi = 0x00; + sInst += "$" + hex(lo, 2) + ", X {ZPX}"; + } + else if (lookup[opcode].addrmode == &olc6502::ZPY) + { + lo = bus->cpuRead(addr, true); addr++; + hi = 0x00; + sInst += "$" + hex(lo, 2) + ", Y {ZPY}"; + } + else if (lookup[opcode].addrmode == &olc6502::IZX) + { + lo = bus->cpuRead(addr, true); addr++; + hi = 0x00; + sInst += "($" + hex(lo, 2) + ", X) {IZX}"; + } + else if (lookup[opcode].addrmode == &olc6502::IZY) + { + lo = bus->cpuRead(addr, true); addr++; + hi = 0x00; + sInst += "($" + hex(lo, 2) + "), Y {IZY}"; + } + else if (lookup[opcode].addrmode == &olc6502::ABS) + { + lo = bus->cpuRead(addr, true); addr++; + hi = bus->cpuRead(addr, true); addr++; + sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + " {ABS}"; + } + else if (lookup[opcode].addrmode == &olc6502::ABX) + { + lo = bus->cpuRead(addr, true); addr++; + hi = bus->cpuRead(addr, true); addr++; + sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + ", X {ABX}"; + } + else if (lookup[opcode].addrmode == &olc6502::ABY) + { + lo = bus->cpuRead(addr, true); addr++; + hi = bus->cpuRead(addr, true); addr++; + sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + ", Y {ABY}"; + } + else if (lookup[opcode].addrmode == &olc6502::IND) + { + lo = bus->cpuRead(addr, true); addr++; + hi = bus->cpuRead(addr, true); addr++; + sInst += "($" + hex((uint16_t)(hi << 8) | lo, 4) + ") {IND}"; + } + else if (lookup[opcode].addrmode == &olc6502::REL) + { + value = bus->cpuRead(addr, true); addr++; + sInst += "$" + hex(value, 2) + " [$" + hex(addr + (int8_t)value, 4) + "] {REL}"; + } + + // Add the formed string to a std::map, using the instruction's + // address as the key. This makes it convenient to look for later + // as the instructions are variable in length, so a straight up + // incremental index is not sufficient. + mapLines[line_addr] = sInst; + } + + return mapLines; +} + +// End of File - Jx9 diff --git a/Part #7 - Mappers & Basic Sounds/olc6502.h b/Part #7 - Mappers & Basic Sounds/olc6502.h new file mode 100644 index 0000000..89dcfeb --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olc6502.h @@ -0,0 +1,275 @@ +/* + olc6502 - An emulation of the 6502/2A03 processor + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Background + ~~~~~~~~~~ + I love this microprocessor. It was at the heart of two of my favourite + machines, the BBC Micro, and the Nintendo Entertainment System, as well + as countless others in that era. I learnt to program on the Model B, and + I learnt to love games on the NES, so in many ways, this processor is + why I am the way I am today. + + In February 2019, I decided to undertake a selfish personal project and + build a NES emulator. Ive always wanted to, and as such I've avoided + looking at source code for such things. This made making this a real + personal challenge. I know its been done countless times, and very likely + in far more clever and accurate ways than mine, but I'm proud of this. + + Datasheet: http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf + + Files: olc6502.h, olc6502.cpp + + Relevant Video: https://youtu.be/8XmxKPJDGU0 + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#pragma once + +// With little modification, reliance upon the stdlib can +// be removed entirely if required. + +// Ths is required for translation table and disassembler. The table +// could be implemented straight up as an array, but I used a vector. +#include + +// These are required for disassembler. If you dont require disassembly +// then just remove the function. +#include +#include + +// Emulation Behaviour Logging ====================================== +// Uncomment this to create a logfile entry for each clock tick of +// the CPU. Beware: this slows down emulation considerably and +// generates extremely large files. I recommend "glogg" to view the +// data as it is designed to handle enormous files. +// +//#define LOGMODE // <- Uncomment me to enable logging! + +#ifdef LOGMODE +#include +#endif + +// Forward declaration of generic communications bus class to +// prevent circular inclusions +class Bus; + + +// The 6502 Emulation Class. This is it! +class olc6502 +{ +public: + olc6502(); + ~olc6502(); + +public: + // CPU Core registers, exposed as public here for ease of access from external + // examinors. This is all the 6502 has. + uint8_t a = 0x00; // Accumulator Register + uint8_t x = 0x00; // X Register + uint8_t y = 0x00; // Y Register + uint8_t stkp = 0x00; // Stack Pointer (points to location on bus) + uint16_t pc = 0x0000; // Program Counter + uint8_t status = 0x00; // Status Register + + // External event functions. In hardware these represent pins that are asserted + // to produce a change in state. + void reset(); // Reset Interrupt - Forces CPU into known state + void irq(); // Interrupt Request - Executes an instruction at a specific location + void nmi(); // Non-Maskable Interrupt Request - As above, but cannot be disabled + void clock(); // Perform one clock cycle's worth of update + + // Indicates the current instruction has completed by returning true. This is + // a utility function to enable "step-by-step" execution, without manually + // clocking every cycle + bool complete(); + + // Link this CPU to a communications bus + void ConnectBus(Bus *n) { bus = n; } + + // Produces a map of strings, with keys equivalent to instruction start locations + // in memory, for the specified address range + std::map disassemble(uint16_t nStart, uint16_t nStop); + + // The status register stores 8 flags. Ive enumerated these here for ease + // of access. You can access the status register directly since its public. + // The bits have different interpretations depending upon the context and + // instruction being executed. + enum FLAGS6502 + { + C = (1 << 0), // Carry Bit + Z = (1 << 1), // Zero + I = (1 << 2), // Disable Interrupts + D = (1 << 3), // Decimal Mode (unused in this implementation) + B = (1 << 4), // Break + U = (1 << 5), // Unused + V = (1 << 6), // Overflow + N = (1 << 7), // Negative + }; + +private: + // Convenience functions to access status register + uint8_t GetFlag(FLAGS6502 f); + void SetFlag(FLAGS6502 f, bool v); + + // Assisstive variables to facilitate emulation + uint8_t fetched = 0x00; // Represents the working input value to the ALU + uint16_t temp = 0x0000; // A convenience variable used everywhere + uint16_t addr_abs = 0x0000; // All used memory addresses end up in here + uint16_t addr_rel = 0x00; // Represents absolute address following a branch + uint8_t opcode = 0x00; // Is the instruction byte + uint8_t cycles = 0; // Counts how many cycles the instruction has remaining + uint32_t clock_count = 0; // A global accumulation of the number of clocks + + // Linkage to the communications bus + Bus *bus = nullptr; + uint8_t read(uint16_t a); + void write(uint16_t a, uint8_t d); + + // The read location of data can come from two sources, a memory address, or + // its immediately available as part of the instruction. This function decides + // depending on address mode of instruction byte + uint8_t fetch(); + + // This structure and the following vector are used to compile and store + // the opcode translation table. The 6502 can effectively have 256 + // different instructions. Each of these are stored in a table in numerical + // order so they can be looked up easily, with no decoding required. + // Each table entry holds: + // Pneumonic : A textual representation of the instruction (used for disassembly) + // Opcode Function: A function pointer to the implementation of the opcode + // Opcode Address Mode : A function pointer to the implementation of the + // addressing mechanism used by the instruction + // Cycle Count : An integer that represents the base number of clock cycles the + // CPU requires to perform the instruction + + struct INSTRUCTION + { + std::string name; + uint8_t (olc6502::*operate )(void) = nullptr; + uint8_t (olc6502::*addrmode)(void) = nullptr; + uint8_t cycles = 0; + }; + + std::vector lookup; + +private: + // Addressing Modes ============================================= + // The 6502 has a variety of addressing modes to access data in + // memory, some of which are direct and some are indirect (like + // pointers in C++). Each opcode contains information about which + // addressing mode should be employed to facilitate the + // instruction, in regards to where it reads/writes the data it + // uses. The address mode changes the number of bytes that + // makes up the full instruction, so we implement addressing + // before executing the instruction, to make sure the program + // counter is at the correct location, the instruction is + // primed with the addresses it needs, and the number of clock + // cycles the instruction requires is calculated. These functions + // may adjust the number of cycles required depending upon where + // and how the memory is accessed, so they return the required + // adjustment. + + uint8_t IMP(); uint8_t IMM(); + uint8_t ZP0(); uint8_t ZPX(); + uint8_t ZPY(); uint8_t REL(); + uint8_t ABS(); uint8_t ABX(); + uint8_t ABY(); uint8_t IND(); + uint8_t IZX(); uint8_t IZY(); + +private: + // Opcodes ====================================================== + // There are 56 "legitimate" opcodes provided by the 6502 CPU. I + // have not modelled "unofficial" opcodes. As each opcode is + // defined by 1 byte, there are potentially 256 possible codes. + // Codes are not used in a "switch case" style on a processor, + // instead they are repsonisble for switching individual parts of + // CPU circuits on and off. The opcodes listed here are official, + // meaning that the functionality of the chip when provided with + // these codes is as the developers intended it to be. Unofficial + // codes will of course also influence the CPU circuitry in + // interesting ways, and can be exploited to gain additional + // functionality! + // + // These functions return 0 normally, but some are capable of + // requiring more clock cycles when executed under certain + // conditions combined with certain addressing modes. If that is + // the case, they return 1. + // + // I have included detailed explanations of each function in + // the class implementation file. Note they are listed in + // alphabetical order here for ease of finding. + + uint8_t ADC(); uint8_t AND(); uint8_t ASL(); uint8_t BCC(); + uint8_t BCS(); uint8_t BEQ(); uint8_t BIT(); uint8_t BMI(); + uint8_t BNE(); uint8_t BPL(); uint8_t BRK(); uint8_t BVC(); + uint8_t BVS(); uint8_t CLC(); uint8_t CLD(); uint8_t CLI(); + uint8_t CLV(); uint8_t CMP(); uint8_t CPX(); uint8_t CPY(); + uint8_t DEC(); uint8_t DEX(); uint8_t DEY(); uint8_t EOR(); + uint8_t INC(); uint8_t INX(); uint8_t INY(); uint8_t JMP(); + uint8_t JSR(); uint8_t LDA(); uint8_t LDX(); uint8_t LDY(); + uint8_t LSR(); uint8_t NOP(); uint8_t ORA(); uint8_t PHA(); + uint8_t PHP(); uint8_t PLA(); uint8_t PLP(); uint8_t ROL(); + uint8_t ROR(); uint8_t RTI(); uint8_t RTS(); uint8_t SBC(); + uint8_t SEC(); uint8_t SED(); uint8_t SEI(); uint8_t STA(); + uint8_t STX(); uint8_t STY(); uint8_t TAX(); uint8_t TAY(); + uint8_t TSX(); uint8_t TXA(); uint8_t TXS(); uint8_t TYA(); + + // I capture all "unofficial" opcodes with this function. It is + // functionally identical to a NOP + uint8_t XXX(); + +#ifdef LOGMODE +private: + FILE* logfile = nullptr; +#endif +}; + +// End of File - Jx9 diff --git a/Part #7 - Mappers & Basic Sounds/olcNes_Sounds1.cpp b/Part #7 - Mappers & Basic Sounds/olcNes_Sounds1.cpp new file mode 100644 index 0000000..75962e8 --- /dev/null +++ b/Part #7 - Mappers & Basic Sounds/olcNes_Sounds1.cpp @@ -0,0 +1,420 @@ +/* + olc::NES - Part #5 - PPU Rendering - Foregrounds + "Thanks Dad for believing computers were gonna be a big deal..." - javidx9 + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018-2019 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice. This list of conditions and the following + disclaimer must be reproduced in the documentation and/or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Relevant Video: https://youtu.be/cksywUTZxlY + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Patreon: https://www.patreon.com/javidx9 + Homepage: https://www.onelonecoder.com + + Author + ~~~~~~ + David Barr, aka javidx9, ŠOneLoneCoder 2019 +*/ + +#include +#include +#include + +#include "Bus.h" + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +#define OLC_PGEX_SOUND +#include "olcPGEX_Sound.h" + + +class Demo_olcNES : public olc::PixelGameEngine +{ +public: + Demo_olcNES() { sAppName = "olcNES Sound Demonstration"; } + +private: + // The NES + Bus nes; + std::shared_ptr cart; + bool bEmulationRun = false; + float fResidualTime = 0.0f; + + uint8_t nSelectedPalette = 0x00; + + std::list audio[4]; + float fAccumulatedTime = 0.0f; + +private: + // Support Utilities + std::map mapAsm; + + std::string hex(uint32_t n, uint8_t d) + { + std::string s(d, '0'); + for (int i = d - 1; i >= 0; i--, n >>= 4) + s[i] = "0123456789ABCDEF"[n & 0xF]; + return s; + }; + + void DrawRam(int x, int y, uint16_t nAddr, int nRows, int nColumns) + { + int nRamX = x, nRamY = y; + for (int row = 0; row < nRows; row++) + { + std::string sOffset = "$" + hex(nAddr, 4) + ":"; + for (int col = 0; col < nColumns; col++) + { + sOffset += " " + hex(nes.cpuRead(nAddr, true), 2); + nAddr += 1; + } + DrawString(nRamX, nRamY, sOffset); + nRamY += 10; + } + } + + void DrawCpu(int x, int y) + { + std::string status = "STATUS: "; + DrawString(x , y , "STATUS:", olc::WHITE); + DrawString(x + 64, y, "N", nes.cpu.status & olc6502::N ? olc::GREEN : olc::RED); + DrawString(x + 80, y , "V", nes.cpu.status & olc6502::V ? olc::GREEN : olc::RED); + DrawString(x + 96, y , "-", nes.cpu.status & olc6502::U ? olc::GREEN : olc::RED); + DrawString(x + 112, y , "B", nes.cpu.status & olc6502::B ? olc::GREEN : olc::RED); + DrawString(x + 128, y , "D", nes.cpu.status & olc6502::D ? olc::GREEN : olc::RED); + DrawString(x + 144, y , "I", nes.cpu.status & olc6502::I ? olc::GREEN : olc::RED); + DrawString(x + 160, y , "Z", nes.cpu.status & olc6502::Z ? olc::GREEN : olc::RED); + DrawString(x + 178, y , "C", nes.cpu.status & olc6502::C ? olc::GREEN : olc::RED); + DrawString(x , y + 10, "PC: $" + hex(nes.cpu.pc, 4)); + DrawString(x , y + 20, "A: $" + hex(nes.cpu.a, 2) + " [" + std::to_string(nes.cpu.a) + "]"); + DrawString(x , y + 30, "X: $" + hex(nes.cpu.x, 2) + " [" + std::to_string(nes.cpu.x) + "]"); + DrawString(x , y + 40, "Y: $" + hex(nes.cpu.y, 2) + " [" + std::to_string(nes.cpu.y) + "]"); + DrawString(x , y + 50, "Stack P: $" + hex(nes.cpu.stkp, 4)); + } + + void DrawCode(int x, int y, int nLines) + { + auto it_a = mapAsm.find(nes.cpu.pc); + int nLineY = (nLines >> 1) * 10 + y; + if (it_a != mapAsm.end()) + { + DrawString(x, nLineY, (*it_a).second, olc::CYAN); + while (nLineY < (nLines * 10) + y) + { + nLineY += 10; + if (++it_a != mapAsm.end()) + { + DrawString(x, nLineY, (*it_a).second); + } + } + } + + it_a = mapAsm.find(nes.cpu.pc); + nLineY = (nLines >> 1) * 10 + y; + if (it_a != mapAsm.end()) + { + while (nLineY > y) + { + nLineY -= 10; + if (--it_a != mapAsm.end()) + { + DrawString(x, nLineY, (*it_a).second); + } + } + } + } + + void DrawAudio(int channel, int x, int y) + { + FillRect(x, y, 120, 120, olc::BLACK); + int i = 0; + for (auto s : audio[channel]) + { + Draw(x + i, y + (s >> (channel == 2 ? 5 : 4)), olc::YELLOW); + i++; + } + } + + // This function is called by the underlying sound hardware + // which runs in a different thread. It is automatically + // synchronised with the sample rate of the sound card, and + // expects a single "sample" to be returned, whcih ultimately + // makes its way to your speakers, and then your ears, for that + // lovely 8-bit bliss... but, that means we've some thread + // handling to deal with, since we want both the PGE thread + // and the sound system thread to interact with the emulator. + + static Demo_olcNES* pInstance; // Static variable that will hold a pointer to "this" + + static float SoundOut(int nChannel, float fGlobalTime, float fTimeStep) + { + if (nChannel == 0) + { + while (!pInstance->nes.clock()) {}; + return static_cast(pInstance->nes.dAudioSample); + } + else + return 0.0f; + } + + bool OnUserCreate() override + { + // Load the cartridge + cart = std::make_shared("roms/smb3.nes"); + + if (!cart->ImageValid()) + return false; + + // Insert into NES + nes.insertCartridge(cart); + + // Extract dissassembly + //mapAsm = nes.cpu.disassemble(0x0000, 0xFFFF); + + + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 120; j++) + audio[i].push_back(0); + } + + // Reset NES + nes.reset(); + + // Initialise PGEX sound system, and give it a function to + // call which returns a sound sample on demand + pInstance = this; + nes.SetSampleFrequency(44100); + olc::SOUND::InitialiseAudio(44100, 1, 8, 512); + olc::SOUND::SetUserSynthFunction(SoundOut); + return true; + } + + // We must play nicely now with the sound hardware, so unload + // it when the application terminates + bool OnUserDestroy() override + { + olc::SOUND::DestroyAudio(); + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + EmulatorUpdateWithAudio(fElapsedTime); + return true; + } + + // This performs an emulation update but synced to audio, so it cant + // perform stepping through code or frames. Essentially, it runs + // the emulation in real time now, so only accepts "controller" input + // and updates the display + bool EmulatorUpdateWithAudio(float fElapsedTime) + { + // Sample audio channel output roughly once per frame + fAccumulatedTime += fElapsedTime; + if (fAccumulatedTime >= 1.0f / 60.0f) + { + fAccumulatedTime -= (1.0f / 60.0f); + audio[0].pop_front(); + audio[0].push_back(nes.apu.pulse1_visual); + audio[1].pop_front(); + audio[1].push_back(nes.apu.pulse2_visual); + audio[2].pop_front(); + audio[2].push_back(nes.apu.noise_visual); + } + + + Clear(olc::DARK_BLUE); + + // Handle input for controller in port #1 + nes.controller[0] = 0x00; + nes.controller[0] |= GetKey(olc::Key::X).bHeld ? 0x80 : 0x00; // A Button + nes.controller[0] |= GetKey(olc::Key::Z).bHeld ? 0x40 : 0x00; // B Button + nes.controller[0] |= GetKey(olc::Key::A).bHeld ? 0x20 : 0x00; // Select + nes.controller[0] |= GetKey(olc::Key::S).bHeld ? 0x10 : 0x00; // Start + nes.controller[0] |= GetKey(olc::Key::UP).bHeld ? 0x08 : 0x00; + nes.controller[0] |= GetKey(olc::Key::DOWN).bHeld ? 0x04 : 0x00; + nes.controller[0] |= GetKey(olc::Key::LEFT).bHeld ? 0x02 : 0x00; + nes.controller[0] |= GetKey(olc::Key::RIGHT).bHeld ? 0x01 : 0x00; + + if (GetKey(olc::Key::R).bPressed) nes.reset(); + if (GetKey(olc::Key::P).bPressed) (++nSelectedPalette) &= 0x07; + + DrawCpu(516, 2); + //DrawCode(516, 72, 26); + + // Draw OAM Contents (first 26 out of 64) ====================================== + /*for (int i = 0; i < 26; i++) + { + std::string s = hex(i, 2) + ": (" + std::to_string(nes.ppu.pOAM[i * 4 + 3]) + + ", " + std::to_string(nes.ppu.pOAM[i * 4 + 0]) + ") " + + "ID: " + hex(nes.ppu.pOAM[i * 4 + 1], 2) + + +" AT: " + hex(nes.ppu.pOAM[i * 4 + 2], 2); + DrawString(516, 72 + i * 10, s); + }*/ + + // Draw AUDIO Channels + DrawAudio(0, 520, 72); + DrawAudio(1, 644, 72); + DrawAudio(2, 520, 196); + DrawAudio(3, 644, 196); + + // Draw Palettes & Pattern Tables ============================================== + const int nSwatchSize = 6; + for (int p = 0; p < 8; p++) // For each palette + for(int s = 0; s < 4; s++) // For each index + FillRect(516 + p * (nSwatchSize * 5) + s * nSwatchSize, 340, + nSwatchSize, nSwatchSize, nes.ppu.GetColourFromPaletteRam(p, s)); + + // Draw selection reticule around selected palette + DrawRect(516 + nSelectedPalette * (nSwatchSize * 5) - 1, 339, (nSwatchSize * 4), nSwatchSize, olc::WHITE); + + // Generate Pattern Tables + DrawSprite(516, 348, &nes.ppu.GetPatternTable(0, nSelectedPalette)); + DrawSprite(648, 348, &nes.ppu.GetPatternTable(1, nSelectedPalette)); + + // Draw rendered output ======================================================== + DrawSprite(0, 0, &nes.ppu.GetScreen(), 2); + return true; + } + + // This performs emulation with no audio synchronisation, so it is just + // as before, in all the previous videos + bool EmulatorUpdateWithoutAudio(float fElapsedTime) + { + Clear(olc::DARK_BLUE); + + // Handle input for controller in port #1 + nes.controller[0] = 0x00; + nes.controller[0] |= GetKey(olc::Key::X).bHeld ? 0x80 : 0x00; // A Button + nes.controller[0] |= GetKey(olc::Key::Z).bHeld ? 0x40 : 0x00; // B Button + nes.controller[0] |= GetKey(olc::Key::A).bHeld ? 0x20 : 0x00; // Select + nes.controller[0] |= GetKey(olc::Key::S).bHeld ? 0x10 : 0x00; // Start + nes.controller[0] |= GetKey(olc::Key::UP).bHeld ? 0x08 : 0x00; + nes.controller[0] |= GetKey(olc::Key::DOWN).bHeld ? 0x04 : 0x00; + nes.controller[0] |= GetKey(olc::Key::LEFT).bHeld ? 0x02 : 0x00; + nes.controller[0] |= GetKey(olc::Key::RIGHT).bHeld ? 0x01 : 0x00; + + if (GetKey(olc::Key::SPACE).bPressed) bEmulationRun = !bEmulationRun; + if (GetKey(olc::Key::R).bPressed) nes.reset(); + if (GetKey(olc::Key::P).bPressed) (++nSelectedPalette) &= 0x07; + + if (bEmulationRun) + { + if (fResidualTime > 0.0f) + fResidualTime -= fElapsedTime; + else + { + fResidualTime += (1.0f / 60.0f) - fElapsedTime; + do { nes.clock(); } while (!nes.ppu.frame_complete); + nes.ppu.frame_complete = false; + } + } + else + { + // Emulate code step-by-step + if (GetKey(olc::Key::C).bPressed) + { + // Clock enough times to execute a whole CPU instruction + do { nes.clock(); } while (!nes.cpu.complete()); + // CPU clock runs slower than system clock, so it may be + // complete for additional system clock cycles. Drain + // those out + do { nes.clock(); } while (nes.cpu.complete()); + } + + // Emulate one whole frame + if (GetKey(olc::Key::F).bPressed) + { + // Clock enough times to draw a single frame + do { nes.clock(); } while (!nes.ppu.frame_complete); + // Use residual clock cycles to complete current instruction + do { nes.clock(); } while (!nes.cpu.complete()); + // Reset frame completion flag + nes.ppu.frame_complete = false; + } + } + + DrawCpu(516, 2); + //DrawCode(516, 72, 26); + + // Draw OAM Contents (first 26 out of 64) ====================================== + /*for (int i = 0; i < 26; i++) + { + std::string s = hex(i, 2) + ": (" + std::to_string(nes.ppu.pOAM[i * 4 + 3]) + + ", " + std::to_string(nes.ppu.pOAM[i * 4 + 0]) + ") " + + "ID: " + hex(nes.ppu.pOAM[i * 4 + 1], 2) + + +" AT: " + hex(nes.ppu.pOAM[i * 4 + 2], 2); + DrawString(516, 72 + i * 10, s); + }*/ + + // Draw Palettes & Pattern Tables ============================================== + const int nSwatchSize = 6; + for (int p = 0; p < 8; p++) // For each palette + for (int s = 0; s < 4; s++) // For each index + FillRect(516 + p * (nSwatchSize * 5) + s * nSwatchSize, 340, + nSwatchSize, nSwatchSize, nes.ppu.GetColourFromPaletteRam(p, s)); + + // Draw selection reticule around selected palette + DrawRect(516 + nSelectedPalette * (nSwatchSize * 5) - 1, 339, (nSwatchSize * 4), nSwatchSize, olc::WHITE); + + // Generate Pattern Tables + DrawSprite(516, 348, &nes.ppu.GetPatternTable(0, nSelectedPalette)); + DrawSprite(648, 348, &nes.ppu.GetPatternTable(1, nSelectedPalette)); + + // Draw rendered output ======================================================== + DrawSprite(0, 0, &nes.ppu.GetScreen(), 2); + return true; + } +}; + +// Provide implementation for our static pointer +Demo_olcNES* Demo_olcNES::pInstance = nullptr; + +int main() +{ + Demo_olcNES demo; + demo.Construct(780, 480, 2, 2); + demo.Start(); + return 0; +} \ No newline at end of file