From 2b00f1ecb2b5dcfc41f7fd3ae460fc5b470c1e6f Mon Sep 17 00:00:00 2001 From: Lauchmelder Date: Mon, 28 Feb 2022 17:14:32 +0100 Subject: [PATCH] added latch to ppu --- src/PPU.cpp | 28 +++++++++++++++++----------- src/PPU.hpp | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/PPU.cpp b/src/PPU.cpp index 531c26f..ebbf88d 100644 --- a/src/PPU.cpp +++ b/src/PPU.cpp @@ -35,8 +35,10 @@ void PPU::Reset() void PPU::Tick() { + // On this cycle the VBlankStarted bit is set in the ppustatus if (y == 241 && x == 1) { + // Set flag and send NMI if necessary ppustatus.Flag.VBlankStarted = 1; if (ppuctrl.Flag.VBlankNMI) bus->NMI(); @@ -44,9 +46,11 @@ void PPU::Tick() isFrameDone = true; } + // This cycle resets the VBlankStarted flag if (y == 261 && x == 1) ppustatus.Flag.VBlankStarted = 0; + // Advance pixel counters x++; if (x > 340) { @@ -59,34 +63,33 @@ void PPU::Tick() Byte PPU::ReadRegister(Byte id) { - Byte returnVal = 0x00; - + // Reading from a register fills the latch with the contents of the register + // Write-only regs don't fill the latch + // But in any case, the latch contents are returned switch (id) { case 0: - returnVal = ppuctrl.Raw; + latch = ppuctrl.Raw; break; case 1: - returnVal = ppumask.Raw; + latch = ppumask.Raw; break; case 2: - returnVal = ppustatus.Raw; + latch = ppustatus.Raw; ppustatus.Flag.VBlankStarted = 0; addressLatch = 0; break; case 5: - returnVal = 0x00; break; case 6: - returnVal = 0x00; break; case 7: - returnVal = bus->ReadPPU(ppuaddr.Raw); + latch = bus->ReadPPU(ppuaddr.Raw); ppuaddr.Raw += (ppuctrl.Flag.VRAMAddrIncrement ? 32 : 1); break; @@ -95,7 +98,7 @@ Byte PPU::ReadRegister(Byte id) break; } - return returnVal; + return latch; } void PPU::WriteRegister(Byte id, Byte val) @@ -114,13 +117,16 @@ void PPU::WriteRegister(Byte id, Byte val) ppustatus.Raw = val; break; + // PPUADDR and PPUSCROLL both take 2 accesses to fully set + // When writing to them the address latch is switched. The latch + // determines whether the hi or lo byte should be written next case 5: if (addressLatch == 0) ppuscroll.x = val; else ppuscroll.y = val; - addressLatch = 1 - addressLatch; + addressLatch = !addressLatch; break; case 6: @@ -129,7 +135,7 @@ void PPU::WriteRegister(Byte id, Byte val) else ppuaddr.Bytes.lo = val; - addressLatch = 1 - addressLatch; + addressLatch = !addressLatch; break; case 7: diff --git a/src/PPU.hpp b/src/PPU.hpp index 6fc0a85..58cfc9f 100644 --- a/src/PPU.hpp +++ b/src/PPU.hpp @@ -4,6 +4,9 @@ class Bus; +/** + * @brief The PPU of the NES. + */ class PPU { friend class PPUWatcher; @@ -11,18 +14,49 @@ class PPU public: PPU(Bus* bus); + /** + * @brief Powerup PPU. + * Internal state corresponds to powerup state + */ void Powerup(); + + /** + * @brief Powerup PPU. + * Internal state corresponds to reset state + */ void Reset(); + /** + * @brief Tick PPU forward once. + */ void Tick(); + /** + * @brief Read from memory mapped PPU regs. + */ Byte ReadRegister(Byte id); + + /** + * @brief Write to memory mapped PPU regs. + */ void WriteRegister(Byte id, Byte val); + /** + * @brief Check whether the PPU finished rendering a frame. + * Returns true if the VBlankStart cycle was hit previously. The function resets + * the boolearn when it is called + */ inline bool IsFrameDone() { bool returnVal = isFrameDone; isFrameDone = false; return returnVal; } private: + /** + * @brief Wraps Bus::ReadPPU. + */ Byte Read(Word addr); + + /** + * @brief Wraps Bus::WritePPU. + */ void Write(Word addr, Byte val); private: // Registers @@ -81,8 +115,10 @@ private: // Registers Address ppuaddr; + // Current x, y position the PPU operates on uint16_t x, y; - Byte addressLatch = 0; + Byte latch = 0; + bool addressLatch = false; private: bool isFrameDone = false;