diff --git a/CMakeLists.txt b/CMakeLists.txt index a5bdc14..5dd82d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ add_executable(nes src/Window.cpp src/Window.h src/Shifter.cpp - src/Shifter.h) + src/Shifter.h src/Logger.cpp src/Logger.h src/Controller.cpp src/Controller.h src/SdlKeyboardController.cpp src/SdlKeyboardController.h) find_package(SDL2 CONFIG REQUIRED) find_package(fmt REQUIRED) diff --git a/main.cpp b/main.cpp index bb94afb..20190f5 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,6 @@ #include "src/Nes.h" #include "src/Window.h" +#include "src/SdlKeyboardController.h" #include #include @@ -14,9 +15,10 @@ int main() { window.setSize(nes::Ppu::SCREEN_WIDTH * 4, nes::Ppu::SCREEN_HEIGHT * 4); device.setNewFrameCallback(std::bind(&nes::SdlWindow::drawFrame, &window, _1)); - //device.insertCartridge("/home/selim/Downloads/nestest.nes"); + device.connect(std::make_shared()); + device.insertCartridge("/home/selim/Downloads/smb.nes"); //device.insertCartridge("/Users/selim/Documents/nestest.nes"); - device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes"); + //device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes"); SDL_Event e; uint64_t cycles = 0; diff --git a/src/Bus.cpp b/src/Bus.cpp index 0fc397b..a31a2e9 100644 --- a/src/Bus.cpp +++ b/src/Bus.cpp @@ -23,6 +23,9 @@ namespace nes { else if(address >= 0x8000) { return _cartridge->readPrg(address); } + else if(address == 0x4016) { + return _controller1->read(); + } return 0; } @@ -37,6 +40,9 @@ namespace nes { else if(address >= 0x8000) { std::cout << "Cartridge write at address: " << address << std::endl; } + else if(address == 0x4016) { + _controller1->poll(); + } } void Bus::connect(std::shared_ptr cartridge) { @@ -46,4 +52,19 @@ namespace nes { void Bus::connect(std::shared_ptr ppu) { _ppu = std::move(ppu); } + + void Bus::connect(std::shared_ptr controller) { + _controller1 = std::move(controller); + } + + // For debug + // Calc "hash" - just sum of the bytes of first page + // Can be useful for detecting change in ram + uint32_t Bus::zpHash() const { + uint32_t sum = 0; + for(size_t i = 0; i < 2048; ++i) { + sum += _ram[i]; + } + return sum; + } } \ No newline at end of file diff --git a/src/Bus.h b/src/Bus.h index e27ea15..6e9db1e 100644 --- a/src/Bus.h +++ b/src/Bus.h @@ -7,6 +7,7 @@ #include "Cartridge.h" #include "Ppu.h" +#include "Controller.h" #include #include @@ -20,11 +21,14 @@ namespace nes { void write(uint16_t address, uint8_t value); void connect(std::shared_ptr cartridge); void connect(std::shared_ptr ppu); + void connect(std::shared_ptr controller); + [[nodiscard]] uint32_t zpHash() const; private: std::unique_ptr _ram; std::shared_ptr _cartridge; std::shared_ptr _ppu; + std::shared_ptr _controller1; }; } diff --git a/src/Controller.cpp b/src/Controller.cpp new file mode 100644 index 0000000..0423483 --- /dev/null +++ b/src/Controller.cpp @@ -0,0 +1,18 @@ +// +// Created by selim on 9/17/23. +// + +#include "Controller.h" + +namespace nes { + + Controller::Controller(): _data{} { + + } + + uint8_t Controller::read() { + uint8_t result = (_data & 0x80) > 0; + _data <<= 1; + return result; + } +} \ No newline at end of file diff --git a/src/Controller.h b/src/Controller.h new file mode 100644 index 0000000..86acc1e --- /dev/null +++ b/src/Controller.h @@ -0,0 +1,36 @@ +// +// Created by selim on 9/17/23. +// + +#ifndef NES_CONTROLLER_H +#define NES_CONTROLLER_H + +#include + +namespace nes { + + class Controller { + public: + enum Key: uint8_t { + A = 7, + B = 6, + Select = 5, + Start = 4, + Up = 3, + Down = 2, + Left = 1, + Right = 0 + }; + + public: + Controller(); + uint8_t read(); + virtual void poll() = 0; + + protected: + uint8_t _data; + }; + +} + +#endif //NES_CONTROLLER_H diff --git a/src/Cpu.cpp b/src/Cpu.cpp index 38f29a7..5ab294c 100644 --- a/src/Cpu.cpp +++ b/src/Cpu.cpp @@ -75,7 +75,7 @@ namespace nes { _instructions[0xD0] = {"BNE", &Cpu::BNE, &Cpu::REL, 2, false}; _instructions[0x81] = {"STA", &Cpu::STA, &Cpu::IZX, 6, false}; _instructions[0x8D] = {"STA", &Cpu::STA, &Cpu::ABS, 4, false}; - _instructions[0x91] = {"STA", &Cpu::STA, &Cpu::IZY, 5, true}; + _instructions[0x91] = {"STA", &Cpu::STA, &Cpu::IZY, 6, false}; _instructions[0x95] = {"STA", &Cpu::STA, &Cpu::ZPX, 4, false}; _instructions[0x99] = {"STA", &Cpu::STA, &Cpu::ABY, 5, false}; _instructions[0x9D] = {"STA", &Cpu::STA, &Cpu::ABX, 5, false}; @@ -174,34 +174,36 @@ namespace nes { uint16_t hi = _bus->read(0xFFFD); PC = (hi << 8) | lo; + + _ticks = 8; } - void Cpu::tick() { + bool Cpu::tick() { + bool executed = false; + if(_ticks == 0) { uint8_t opcode = _bus->read(PC++); auto instruction = _instructions[opcode]; + _currentOpcode = opcode; if(instruction.getAddress == nullptr) { std::cout << "Unknown instruction: " << (int)opcode << std::endl; - return; + return false; } auto args = (this->*instruction.getAddress)(); (this->*instruction.process)(args); -// auto str = std::format("{} ({:02X}), PC: {:X}, SP: {:X}, A: {:02X}, X: {:02X}, Y: {:02X}, [N:{}, V:{}, B{}, D{}, I{}, Z:{}, C:{}]", instruction.name, opcode, PC, SP, A, X, Y, -// (int)getFlag(Negative), (int)getFlag(Overflow), (int)getFlag(Break), (int)getFlag(DecimalMode), (int)getFlag(InterruptDisable), (int)getFlag(Zero), (int)getFlag(Carry)); -// std::cout << str << std::endl; - - //std::cout << instruction.name << std::hex << ", OpCode: " << (int)opcode << ", PC: " << (int)PC << ", SP: " << (int)SP << ", A: " << (int)A << ", X: " << (int)X << ", Y: " << (int)Y << std::endl; - - _ticks = instruction.cycles; + _ticks += instruction.cycles; if(instruction.variableCycles) { _ticks += args.cycles; } + + executed = true; } _ticks--; + return executed; } void Cpu::nmi() { @@ -220,6 +222,21 @@ namespace nes { _ticks = 8; } + std::string Cpu::state() const { + return std::format("{} ({:02X}), PC: {:X}, SP: {:X}, A: {:02X}, X: {:02X}, Y: {:02X}, [N:{}, V:{}, B{}, D{}, I{}, Z:{}, C:{}], H: {:08X}", + _instructions[_currentOpcode].name, + _currentOpcode, + PC, SP, A, X, Y, + (int)getFlag(Negative), + (int)getFlag(Overflow), + (int)getFlag(Break), + (int)getFlag(DecimalMode), + (int)getFlag(InterruptDisable), + (int)getFlag(Zero), + (int)getFlag(Carry), + _bus->zpHash()); + } + void Cpu::setFlag(CpuFlags flag, bool value) { if(value) { flags |= flag; diff --git a/src/Cpu.h b/src/Cpu.h index 0aa3cdc..b1ca692 100644 --- a/src/Cpu.h +++ b/src/Cpu.h @@ -42,12 +42,13 @@ namespace nes { public: Cpu(); void reset(); - void tick(); + bool tick(); void setFlag(CpuFlags flag, bool value); bool getFlag(CpuFlags flag) const; void setStartAddress(uint16_t address); Bus* bus() const; void nmi(); + std::string state() const; private: size_t _ticks; @@ -64,6 +65,10 @@ namespace nes { uint8_t SP; // Stack Pointer uint8_t flags; + private: + // Debug info + uint8_t _currentOpcode; + private: void branch(InstructionArgs args); diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..5bd6e24 --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,35 @@ +// +// Created by selim on 9/16/23. +// + +#include "Logger.h" + +#include +#include +#include + +namespace nes { + + Logger::Logger(size_t size): _size{size}, _offset{0} { + _data = std::make_unique(size); + } + + void Logger::addLine(uint64_t cycle, std::string_view line) { + if((_offset + line.size() + 10) >= _size) { + return; + } + + std::string cycleString = std::format("[{:06d}] ", cycle); + std::memcpy(_data.get() + _offset, cycleString.data(), cycleString.size()); + _offset += cycleString.size(); + + std::memcpy(_data.get() + _offset, line.data(), line.size()); + _offset += line.size(); + _data[_offset++] = '\n'; + } + + void Logger::dump(const fs::path &path) { + std::ofstream file(path, std::ios::binary); + file.write(reinterpret_cast(_data.get()), static_cast(_offset)); + } +} \ No newline at end of file diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..fbd5adb --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,30 @@ +// +// Created by selim on 9/16/23. +// + +#ifndef NES_LOGGER_H +#define NES_LOGGER_H + +#include +#include +#include + +namespace nes { + + namespace fs = std::filesystem; + + class Logger { + public: + explicit Logger(size_t size); + void addLine(uint64_t cycle, std::string_view line); + void dump(const fs::path& path); + + private: + size_t _size; + size_t _offset; + std::unique_ptr _data; + }; + +} + +#endif //NES_LOGGER_H diff --git a/src/Nes.cpp b/src/Nes.cpp index 6cee319..935667c 100644 --- a/src/Nes.cpp +++ b/src/Nes.cpp @@ -10,7 +10,7 @@ namespace nes { - Nes::Nes(): _cycles{} { + Nes::Nes(): _cycles{}, _logger(100*1024*1024) { _cpu = std::make_unique(); _ppu = std::make_shared(); } @@ -23,6 +23,10 @@ namespace nes { reset(address); } + void Nes::connect(std::shared_ptr controller) { + _cpu->bus()->connect(std::move(controller)); + } + void Nes::reset(std::optional address) { _cpu->reset(); _ppu->reset(); @@ -36,15 +40,27 @@ namespace nes { } void Nes::tick() { + bool needInterrupt = _ppu->tick(); +// _logger.addLine(_cycles, _ppu->state()); + if(_cycles % 3 == 0) { - _cpu->tick(); + bool instructionExecuted = _cpu->tick(); +// if(instructionExecuted) { +// _logger.addLine(_cycles, _cpu->state()); +// } } if(needInterrupt) { _cpu->nmi(); +// _logger.addLine(_cycles, "NMI"); +// _logger.addLine(_cycles, _cpu->state()); } +// if(_cycles == 500000) { +// _logger.dump("/home/selim/Documents/log.txt"); +// } + _cycles++; } diff --git a/src/Nes.h b/src/Nes.h index 066b011..6de4f2e 100644 --- a/src/Nes.h +++ b/src/Nes.h @@ -8,6 +8,7 @@ #include "Cpu.h" #include "Cartridge.h" #include "Ppu.h" +#include "Logger.h" #include #include @@ -20,6 +21,7 @@ namespace nes { public: Nes(); void insertCartridge(const fs::path& path, std::optional address = std::nullopt); + void connect(std::shared_ptr controller); void reset(std::optional address = std::nullopt); void setNewFrameCallback(std::function onNewFrame); void tick(); @@ -29,6 +31,7 @@ namespace nes { std::unique_ptr _cpu; std::shared_ptr _ppu; std::shared_ptr _cartridge; + Logger _logger; }; } diff --git a/src/Ppu.cpp b/src/Ppu.cpp index 6318a97..983b107 100644 --- a/src/Ppu.cpp +++ b/src/Ppu.cpp @@ -3,6 +3,7 @@ // #include "Ppu.h" +#include namespace nes { @@ -80,8 +81,13 @@ namespace nes { bool Ppu::tick() { + _needEmitNmi = false; + if(_scanline == 241 && _column == 1) { _status.verticalBlank = 1; + if(_control.enableNmi) { + _needEmitNmi = true; + } } // All visible scanlines @@ -97,8 +103,10 @@ namespace nes { // Preloading some data ahead of time if ((_column >= 2 && _column < 258) || (_column >= 321 && _column < 338)) { - _bgPatternShifter.shift(); - _bgAttribShifter.shift(); + if(_mask.renderBackground) { + _bgPatternShifter.shift(); + _bgAttribShifter.shift(); + } prepareNextBgTile(_column); } @@ -125,21 +133,15 @@ namespace nes { uint8_t colorIndex = 0; uint8_t palette = 0; - if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) { + if(_mask.renderBackground) { + colorIndex = _bgPatternShifter.getValue(_fineX); + palette = _bgAttribShifter.getValue(_fineX); + Pixel pixel = getColor(palette, colorIndex); - if(_mask.renderBackground) { - colorIndex = _bgPatternShifter.getValue(_fineX); - palette = _bgAttribShifter.getValue(_fineX); - Pixel pixel = getColor(palette, colorIndex); - - // For debugging - if (colorIndex != 0) { - pixel = Pixel(0xFF, 0xFF, 0xFF); - } - else { - pixel = Pixel(); - } + _curPalette = palette; + _curColorIndex = colorIndex; + if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) { setPixel(_scanline, _column, pixel); } } @@ -155,7 +157,7 @@ namespace nes { } } - return _status.verticalBlank && _control.enableNmi; + return _needEmitNmi; } uint8_t Ppu::read(uint16_t address) { @@ -204,7 +206,7 @@ namespace nes { _tRamAddress.value = (_tRamAddress.value & 0xFF00) | value; _vRamAddress.value = _tRamAddress.value; } else { - _tRamAddress.value = ((value & 0x3F) << 8) | (_tRamAddress.value & 0x00FF); + _tRamAddress.value = (((value & 0x3F) << 8) | (_tRamAddress.value & 0x00FF)); } _addressWriteInProgress = !_addressWriteInProgress; break; @@ -273,9 +275,6 @@ namespace nes { else if(address >= 0x2000 && address < 0x3F00) { address &= 0x0FFF; _nameTable[address & 0x03FF] = value; - if (value == 32) { - int x = 0; - } } else if(address >= 0x3F00 && address < 0x4000) { address &= 0x1F; @@ -325,7 +324,7 @@ namespace nes { uint16_t address = (_control.patternBackground << 12) | ((uint16_t)_nextBgTile.id << 4) | (_vRamAddress.fineY + 8); - _nextBgTile.lsb = internalRead(address); + _nextBgTile.msb = internalRead(address); } break; case 7: @@ -367,17 +366,43 @@ namespace nes { } void Ppu::updateScrollX() { - if(_mask.renderBackground && !_mask.renderSprites) { + if(_mask.renderBackground || _mask.renderSprites) { _vRamAddress.nameTableX = _tRamAddress.nameTableX; _vRamAddress.coarseX = _tRamAddress.coarseX; } } void Ppu::updateScrollY() { - if(_mask.renderBackground && !_mask.renderSprites) { + if(_mask.renderBackground || _mask.renderSprites) { _vRamAddress.nameTableY =_tRamAddress.nameTableY; _vRamAddress.coarseY = _tRamAddress.coarseY; _vRamAddress.fineY = _tRamAddress.fineY; } } + + std::string Ppu::state() const { + + auto palettes = (uint32_t*)_paletteTable.get(); + + return std::format("X:{:03d}, Y:{:03d}, V:{:d}, vR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], tR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], FX:{:d}, AL:{:d}, PI:{:02X}, CI:{:02X}, ASL:{:04X}, ASH:{:04X}", + _column, + _scanline, + _status.verticalBlank, + _vRamAddress.coarseX, + _vRamAddress.coarseY, + _vRamAddress.nameTableX, + _vRamAddress.nameTableY, + _vRamAddress.fineY, + _tRamAddress.coarseX, + _tRamAddress.coarseY, + _tRamAddress.nameTableX, + _tRamAddress.nameTableY, + _tRamAddress.fineY, + _fineX, + _addressWriteInProgress, + _curPalette, + _curColorIndex, + _bgAttribShifter._lo, + _bgAttribShifter._hi); + } } \ No newline at end of file diff --git a/src/Ppu.h b/src/Ppu.h index 564b7de..a01c61e 100644 --- a/src/Ppu.h +++ b/src/Ppu.h @@ -80,12 +80,12 @@ namespace nes { union LoopyRegister { struct { - uint8_t coarseX : 5; - uint8_t coarseY : 5; - uint8_t nameTableX : 1; - uint8_t nameTableY : 1; - uint8_t fineY : 3; - uint8_t unused : 1; + uint16_t coarseX : 5; + uint16_t coarseY : 5; + uint16_t nameTableX : 1; + uint16_t nameTableY : 1; + uint16_t fineY : 3; + uint16_t unused : 1; }; uint16_t value; }; @@ -105,6 +105,7 @@ namespace nes { void setPixel(uint16_t row, uint16_t column, Pixel pixel); void connect(std::shared_ptr cartridge); void reset(); + std::string state() const; public: std::function onNewFrame; @@ -134,6 +135,7 @@ namespace nes { TileInfo _nextBgTile; Shifter _bgPatternShifter; Shifter _bgAttribShifter; + bool _needEmitNmi; private: std::unique_ptr _nameTable; @@ -143,6 +145,10 @@ namespace nes { private: std::unique_ptr _frameBuffer; std::shared_ptr _cartridge; + + private: // For debug + uint8_t _curPalette; + uint8_t _curColorIndex; }; } diff --git a/src/SdlKeyboardController.cpp b/src/SdlKeyboardController.cpp new file mode 100644 index 0000000..db0ec95 --- /dev/null +++ b/src/SdlKeyboardController.cpp @@ -0,0 +1,19 @@ +// +// Created by selim on 9/17/23. +// + +#include "SdlKeyboardController.h" +#include + +void SdlKeyboardController::poll() { + auto state = SDL_GetKeyboardState(nullptr); + _data = 0; + _data |= state[SDL_SCANCODE_Z] << Key::A; + _data |= state[SDL_SCANCODE_X] << Key::B; + _data |= state[SDL_SCANCODE_TAB] << Key::Select; + _data |= state[SDL_SCANCODE_RETURN] << Key::Start; + _data |= state[SDL_SCANCODE_UP] << Key::Up; + _data |= state[SDL_SCANCODE_DOWN] << Key::Down; + _data |= state[SDL_SCANCODE_LEFT] << Key::Left; + _data |= state[SDL_SCANCODE_RIGHT] << Key::Right; +} diff --git a/src/SdlKeyboardController.h b/src/SdlKeyboardController.h new file mode 100644 index 0000000..cb1b649 --- /dev/null +++ b/src/SdlKeyboardController.h @@ -0,0 +1,16 @@ +// +// Created by selim on 9/17/23. +// + +#ifndef NES_SDLKEYBOARDCONTROLLER_H +#define NES_SDLKEYBOARDCONTROLLER_H + +#include "Controller.h" + +class SdlKeyboardController: public nes::Controller { +public: + void poll() override; +}; + + +#endif //NES_SDLKEYBOARDCONTROLLER_H diff --git a/src/Shifter.h b/src/Shifter.h index 1cbc23c..59a8992 100644 --- a/src/Shifter.h +++ b/src/Shifter.h @@ -16,7 +16,7 @@ namespace nes { void shift(); uint16_t getValue(uint8_t offset); - private: + public: uint16_t _lo; uint16_t _hi; };