diff --git a/src/Cpu.cpp b/src/Cpu.cpp index 37818a6..38f29a7 100644 --- a/src/Cpu.cpp +++ b/src/Cpu.cpp @@ -204,6 +204,22 @@ namespace nes { _ticks--; } + void Cpu::nmi() { + _bus->write(STACK_BASE + SP--, PC >> 8); + _bus->write(STACK_BASE + SP--, PC & 0x00FF); + + setFlag(Break, false); + setFlag(Unused, true); + setFlag(InterruptDisable, true); + _bus->write(STACK_BASE + SP--, flags); + + uint8_t lo = _bus->read(0xFFFA); + uint8_t hi = _bus->read(0xFFFB); + PC = (hi << 8) | lo; + + _ticks = 8; + } + void Cpu::setFlag(CpuFlags flag, bool value) { if(value) { flags |= flag; diff --git a/src/Cpu.h b/src/Cpu.h index 2a5ff80..0aa3cdc 100644 --- a/src/Cpu.h +++ b/src/Cpu.h @@ -47,6 +47,7 @@ namespace nes { bool getFlag(CpuFlags flag) const; void setStartAddress(uint16_t address); Bus* bus() const; + void nmi(); private: size_t _ticks; diff --git a/src/Nes.cpp b/src/Nes.cpp index a7ff577..d2e367c 100644 --- a/src/Nes.cpp +++ b/src/Nes.cpp @@ -19,6 +19,7 @@ namespace nes { _cartridge = std::make_shared(path); _cpu->bus()->connect(_cartridge); _cpu->bus()->connect(_ppu); + _ppu->connect(_cartridge); reset(address); } @@ -34,10 +35,15 @@ namespace nes { } void Nes::tick() { - _ppu->tick(); + bool needInterrupt = _ppu->tick(); if(_cycles % 3 == 0) { _cpu->tick(); } + + if(needInterrupt) { + _cpu->nmi(); + } + _cycles++; } diff --git a/src/Ppu.cpp b/src/Ppu.cpp index 25ee7df..51024ab 100644 --- a/src/Ppu.cpp +++ b/src/Ppu.cpp @@ -6,17 +6,97 @@ namespace nes { - Ppu::Ppu(): _column{}, _scanline{}, _status{} { + Ppu::Ppu(): _column{}, _scanline{}, _status{}, _control{}, _mask{} { _frameBuffer = std::make_unique(SCREEN_WIDTH*SCREEN_HEIGHT); + _nameTable = std::make_unique(1024); + _paletteTable = std::make_unique(32); + _palette = std::make_unique(64); + + _palette[0x00] = Pixel(84, 84, 84); + _palette[0x01] = Pixel(0, 30, 116); + _palette[0x02] = Pixel(8, 16, 144); + _palette[0x03] = Pixel(48, 0, 136); + _palette[0x04] = Pixel(68, 0, 100); + _palette[0x05] = Pixel(92, 0, 48); + _palette[0x06] = Pixel(84, 4, 0); + _palette[0x07] = Pixel(60, 24, 0); + _palette[0x08] = Pixel(32, 42, 0); + _palette[0x09] = Pixel(8, 58, 0); + _palette[0x0A] = Pixel(0, 64, 0); + _palette[0x0B] = Pixel(0, 60, 0); + _palette[0x0C] = Pixel(0, 50, 60); + _palette[0x0D] = Pixel(0, 0, 0); + _palette[0x0E] = Pixel(0, 0, 0); + _palette[0x0F] = Pixel(0, 0, 0); + _palette[0x10] = Pixel(152, 150, 152); + _palette[0x11] = Pixel(8, 76, 196); + _palette[0x12] = Pixel(48, 50, 236); + _palette[0x13] = Pixel(92, 30, 228); + _palette[0x14] = Pixel(136, 20, 176); + _palette[0x15] = Pixel(160, 20, 100); + _palette[0x16] = Pixel(152, 34, 32); + _palette[0x17] = Pixel(120, 60, 0); + _palette[0x18] = Pixel(84, 90, 0); + _palette[0x19] = Pixel(40, 114, 0); + _palette[0x1A] = Pixel(8, 124, 0); + _palette[0x1B] = Pixel(0, 118, 40); + _palette[0x1C] = Pixel(0, 102, 120); + _palette[0x1D] = Pixel(0, 0, 0); + _palette[0x1E] = Pixel(0, 0, 0); + _palette[0x1F] = Pixel(0, 0, 0); + _palette[0x20] = Pixel(236, 238, 236); + _palette[0x21] = Pixel(76, 154, 236); + _palette[0x22] = Pixel(120, 124, 236); + _palette[0x23] = Pixel(176, 98, 236); + _palette[0x24] = Pixel(228, 84, 236); + _palette[0x25] = Pixel(236, 88, 180); + _palette[0x26] = Pixel(236, 106, 100); + _palette[0x27] = Pixel(212, 136, 32); + _palette[0x28] = Pixel(160, 170, 0); + _palette[0x29] = Pixel(116, 196, 0); + _palette[0x2A] = Pixel(76, 208, 32); + _palette[0x2B] = Pixel(56, 204, 108); + _palette[0x2C] = Pixel(56, 180, 204); + _palette[0x2D] = Pixel(60, 60, 60); + _palette[0x2E] = Pixel(0, 0, 0); + _palette[0x2F] = Pixel(0, 0, 0); + _palette[0x30] = Pixel(236, 238, 236); + _palette[0x31] = Pixel(168, 204, 236); + _palette[0x32] = Pixel(188, 188, 236); + _palette[0x33] = Pixel(212, 178, 236); + _palette[0x34] = Pixel(236, 174, 236); + _palette[0x35] = Pixel(236, 174, 212); + _palette[0x36] = Pixel(236, 180, 176); + _palette[0x37] = Pixel(228, 196, 144); + _palette[0x38] = Pixel(204, 210, 120); + _palette[0x39] = Pixel(180, 222, 120); + _palette[0x3A] = Pixel(168, 226, 144); + _palette[0x3B] = Pixel(152, 226, 180); + _palette[0x3C] = Pixel(160, 214, 228); + _palette[0x3D] = Pixel(160, 162, 160); + _palette[0x3E] = Pixel(0, 0, 0); + _palette[0x3F] = Pixel(0, 0, 0); } - void Ppu::tick() { + bool Ppu::tick() { + + if(_scanline == 241 && _column == 1) { + _status.verticalBlank = 1; + } + + uint8_t colorIndex = 0; + uint8_t palette = 0; if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) { - Pixel black = {0, 0, 0, 100}; - Pixel white = {255, 255, 255, 100}; - Pixel pixel = std::rand() % 2 == 0 ? black : white; - setPixel(_scanline, _column, pixel); +// Pixel black = {0, 0, 0, 100}; +// Pixel white = {255, 255, 255, 100}; +// Pixel pixel = std::rand() % 2 == 0 ? black : white; +// setPixel(_scanline, _column, pixel); + + if(_mask.renderBackground) { + Pixel pixel = getColor(palette, colorIndex); + setPixel(_scanline, _column, pixel); + } } _column++; @@ -29,26 +109,108 @@ namespace nes { onNewFrame(_frameBuffer.get()); } } + + return _status.verticalBlank && _control.enableNmi; + } + + uint8_t Ppu::read(uint16_t address) { + uint8_t value = 0; + switch (address) { + case Status: + value = _status.value & 0xE0; + _status.verticalBlank = 0; + break; + case PpuData: + value = _dataTemp; + _dataTemp = internalRead(address); + if(address >= 0x3F00) { + value = _dataTemp; + } + _vRamAddress += _control.incrementMode ? 32 : 1; + break; + } + return value; } void Ppu::write(uint16_t address, uint8_t value) { switch (address) { + case Control: + _control.value = value; + break; + case Mask: + _mask.value = value; + break; + case PpuAddress: + if(_addressWriteInProgress) { + _vRamAddressTemp = (_vRamAddressTemp & 0xFF00) | value; + _vRamAddress = _vRamAddressTemp; + } else { + _vRamAddressTemp = ((value & 0x3F) << 8) | (_vRamAddressTemp & 0x00FF); + } + _addressWriteInProgress = !_addressWriteInProgress; + break; + case PpuData: + internalWrite(_vRamAddress, value); + _vRamAddress += _control.incrementMode ? 32 : 1; + break; default: break; } } - uint8_t Ppu::read(uint16_t address) { - switch (address) { - case Status: - uint8_t value = _status.value & 0xE0; - _status.verticalBlank = 0; - return value; - } - } - void Ppu::setPixel(uint16_t row, uint16_t column, Pixel pixel) { size_t index = row*SCREEN_WIDTH + column; _frameBuffer[index] = pixel; } + + void Ppu::connect(std::shared_ptr cartridge) { + _cartridge = std::move(cartridge); + } + + uint8_t Ppu::internalRead(uint16_t address) { + address &= 0x3FFF; + + if(address < 0x2000) { + return _cartridge->readChr(address); + } + else if(address >= 0x2000 && address < 0x3F00) { + address &= 0x03FF; + return _nameTable[address]; + } + else if(address >= 0x3F00 && address < 0x4000) { + address &= 0x1F; + if (address == 0x0010) address = 0x0000; + if (address == 0x0014) address = 0x0004; + if (address == 0x0018) address = 0x0008; + if (address == 0x001C) address = 0x000C; + return _paletteTable[address] & (_mask.grayscale ? 0x30 : 0x3F); + } + + return 0; + } + + void Ppu::internalWrite(uint16_t address, uint8_t value) { + address &= 0x3FFF; + + if(address < 0x2000) { + // Can't write to CHR ROM + } + else if(address >= 0x2000 && address < 0x3F00) { + address &= 0x03FF; + _nameTable[address] = value; + } + else if(address >= 0x3F00 && address < 0x4000) { + address &= 0x1F; + if (address == 0x0010) address = 0x0000; + if (address == 0x0014) address = 0x0004; + if (address == 0x0018) address = 0x0008; + if (address == 0x001C) address = 0x000C; + _paletteTable[address] = value; + } + } + + Pixel Ppu::getColor(uint8_t palette, uint8_t pixel) { + uint16_t address = 0x3F00 + palette*4 + pixel; + return _palette[internalRead(address) & 0x3F]; + } } \ No newline at end of file diff --git a/src/Ppu.h b/src/Ppu.h index 67d6481..0d455c1 100644 --- a/src/Ppu.h +++ b/src/Ppu.h @@ -5,6 +5,8 @@ #ifndef NES_PPU_H #define NES_PPU_H +#include "Cartridge.h" + #include #include #include @@ -12,10 +14,10 @@ namespace nes { struct Pixel { - uint8_t R; - uint8_t G; - uint8_t B; - uint8_t A; + uint8_t R = 0; + uint8_t G = 0; + uint8_t B = 0; + uint8_t A = 0xFF; }; class Ppu { @@ -44,21 +46,70 @@ namespace nes { uint8_t value; }; + union ControlRegister { + struct { + uint8_t nameTableX: 1; + uint8_t nameTableY: 1; + uint8_t incrementMode: 1; + uint8_t patternSprite: 1; + uint8_t patternBackground: 1; + uint8_t spriteSize: 1; + uint8_t slaveMode: 1; + uint8_t enableNmi: 1; + }; + uint8_t value; + }; + + union MaskRegister { + struct { + uint8_t grayscale: 1; + uint8_t renderBackgroundLeft: 1; + uint8_t renderSpritesLeft: 1; + uint8_t renderBackground: 1; + uint8_t renderSprites: 1; + uint8_t enhanceRed: 1; + uint8_t enhanceGreen: 1; + uint8_t enhanceBlue: 1; + }; + uint8_t value; + }; + public: Ppu(); - void tick(); + bool tick(); void write(uint16_t address, uint8_t value); uint8_t read(uint16_t address); void setPixel(uint16_t row, uint16_t column, Pixel pixel); + void connect(std::shared_ptr cartridge); public: std::function onNewFrame; + private: + uint8_t internalRead(uint16_t address); + void internalWrite(uint16_t address, uint8_t value); + Pixel getColor(uint8_t palette, uint8_t pixel); + private: int16_t _column; int16_t _scanline; StatusRegister _status; + ControlRegister _control; + MaskRegister _mask; + + uint16_t _vRamAddress; + uint16_t _vRamAddressTemp; + bool _addressWriteInProgress; + uint8_t _dataTemp; + + private: + std::unique_ptr _nameTable; + std::unique_ptr _palette; + std::unique_ptr _paletteTable; + + private: std::unique_ptr _frameBuffer; + std::shared_ptr _cartridge; }; }