From bc7accfe9e720f05a5d49553a9ad748bdfe0238b Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 20 Aug 2023 11:48:51 -0400 Subject: [PATCH] Add DMA and SNES Init, VBlank, NMI, etc --- src/app/emu/snes.cc | 212 +++++++++++++++++++++++++++++++++++++++++++- src/app/emu/snes.h | 84 +++++++++++++++--- 2 files changed, 281 insertions(+), 15 deletions(-) diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index 73945155..5e221697 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -1,7 +1,9 @@ #include "snes.h" #include +#include #include +#include #include "app/emu/apu.h" #include "app/emu/cpu.h" @@ -12,8 +14,73 @@ namespace yaze { namespace app { namespace emu { +void DMA::StartDMATransfer(uint8_t channelMask) { + for (int i = 0; i < 8; ++i) { + if ((channelMask & (1 << i)) != 0) { + Channel& ch = channels[i]; + + // Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, DASn) + // ... + + // Determine the transfer direction based on the DMAPn register + bool fromMemory = (ch.DMAPn & 0x80) != 0; + + // Determine the transfer size based on the DMAPn register + bool transferTwoBytes = (ch.DMAPn & 0x40) != 0; + + // Perform the DMA transfer based on the channel parameters + std::cout << "Starting DMA transfer for channel " << i << std::endl; + + for (uint16_t j = 0; j < ch.DASn; ++j) { + // Read a byte or two bytes from memory based on the transfer size + // ... + + // Write the data to the B-bus address (BBADn) if transferring from + // memory + // ... + + // Update the A1Tn register based on the transfer direction + if (fromMemory) { + ch.A1Tn += transferTwoBytes ? 2 : 1; + } else { + ch.A1Tn -= transferTwoBytes ? 2 : 1; + } + } + + // Update the channel registers after the transfer (e.g., A1Tn, DASn) + // ... + } + } + MDMAEN = channelMask; // Set the MDMAEN register to the channel mask +} + +void DMA::EnableHDMATransfers(uint8_t channelMask) { + for (int i = 0; i < 8; ++i) { + if ((channelMask & (1 << i)) != 0) { + Channel& ch = channels[i]; + + // Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn) + // ... + + // Perform the HDMA setup based on the channel parameters + std::cout << "Enabling HDMA transfer for channel " << i << std::endl; + + // Read the HDMA table from memory starting at A1Tn + // ... + + // Update the A2An register based on the HDMA table + // ... + + // Update the NLTRn register based on the HDMA table + // ... + } + } + HDMAEN = channelMask; // Set the HDMAEN register to the channel mask +} + void SNES::Init(ROM& rom) { - // Initialize CPU + // Perform a long jump into a FastROM bank (if the ROM speed is FastROM) + // Disable the emulation flag (switch to 65816 native mode)s cpu.Init(); // Initialize PPU @@ -22,8 +89,95 @@ void SNES::Init(ROM& rom) { // Initialize APU apu.Init(); - // Load ROM + // Disable interrupts and rendering + memory_.WriteByte(0x4200, 0x00); // NMITIMEN + memory_.WriteByte(0x420C, 0x00); // HDMAEN + + // Disable screen + memory_.WriteByte(0x2100, 0x8F); // INIDISP + + // Fill Work-RAM with zeros using two 64KiB fixed address DMA transfers to + // WMDATA + // TODO: Make this load from work ram, potentially in Memory class + std::memset((void*)memory_.ram_.data(), 0, sizeof(memory_.ram_)); + + // Reset PPU registers to a known good state + memory_.WriteByte(0x4201, 0xFF); // WRIO + + // Objects + memory_.WriteByte(0x2101, 0x00); // OBSEL + memory_.WriteByte(0x2102, 0x00); // OAMADDL + memory_.WriteByte(0x2103, 0x00); // OAMADDH + + // Backgrounds + memory_.WriteByte(0x2105, 0x00); // BGMODE + memory_.WriteByte(0x2106, 0x00); // MOSAIC + + memory_.WriteByte(0x2107, 0x00); // BG1SC + memory_.WriteByte(0x2108, 0x00); // BG2SC + memory_.WriteByte(0x2109, 0x00); // BG3SC + memory_.WriteByte(0x210A, 0x00); // BG4SC + + memory_.WriteByte(0x210B, 0x00); // BG12NBA + memory_.WriteByte(0x210C, 0x00); // BG34NBA + + // Scroll Registers + memory_.WriteByte(0x210D, 0x00); // BG1HOFS + memory_.WriteByte(0x210E, 0xFF); // BG1VOFS + + memory_.WriteByte(0x210F, 0x00); // BG2HOFS + memory_.WriteByte(0x2110, 0xFF); // BG2VOFS + + memory_.WriteByte(0x2111, 0x00); // BG3HOFS + memory_.WriteByte(0x2112, 0xFF); // BG3VOFS + + memory_.WriteByte(0x2113, 0x00); // BG4HOFS + memory_.WriteByte(0x2114, 0xFF); // BG4VOFS + + // VRAM Registers + memory_.WriteByte(0x2115, 0x80); // VMAIN + + // Mode 7 + memory_.WriteByte(0x211A, 0x00); // M7SEL + memory_.WriteByte(0x211B, 0x01); // M7A + memory_.WriteByte(0x211C, 0x00); // M7B + memory_.WriteByte(0x211D, 0x00); // M7C + memory_.WriteByte(0x211E, 0x01); // M7D + memory_.WriteByte(0x211F, 0x00); // M7X + memory_.WriteByte(0x2120, 0x00); // M7Y + + // Windows + memory_.WriteByte(0x2123, 0x00); // W12SEL + memory_.WriteByte(0x2124, 0x00); // W34SEL + memory_.WriteByte(0x2125, 0x00); // WOBJSEL + memory_.WriteByte(0x2126, 0x00); // WH0 + memory_.WriteByte(0x2127, 0x00); // WH1 + memory_.WriteByte(0x2128, 0x00); // WH2 + memory_.WriteByte(0x2129, 0x00); // WH3 + memory_.WriteByte(0x212A, 0x00); // WBGLOG + memory_.WriteByte(0x212B, 0x00); // WOBJLOG + + // Layer Enable + memory_.WriteByte(0x212C, 0x00); // TM + memory_.WriteByte(0x212D, 0x00); // TS + memory_.WriteByte(0x212E, 0x00); // TMW + memory_.WriteByte(0x212F, 0x00); // TSW + + // Color Math + memory_.WriteByte(0x2130, 0x30); // CGWSEL + memory_.WriteByte(0x2131, 0x00); // CGADSUB + memory_.WriteByte(0x2132, 0xE0); // COLDATA + + // Misc + memory_.WriteByte(0x2133, 0x00); // SETINI + + // Load ROM data into memory + // TODO: Load memory based on memory mapping and ROM format. memory_.SetMemory(rom.vector()); + + // Initialize other private member variables + running_ = true; + scanline = 0; } void SNES::Run() { @@ -81,6 +235,60 @@ void SNES::Run() { } } +// Enable NMI Interrupts +void SNES::EnableVBlankInterrupts() { + vBlankFlag = 0; + + // Clear the RDNMI VBlank flag + memory_.ReadByte(0x4210); // RDNMI + + // Enable vblank NMI interrupts and Joypad auto-read + memory_.WriteByte(0x4200, 0x81); // NMITIMEN +} + +// Wait until the VBlank routine has been processed +void SNES::WaitForVBlank() { + vBlankFlag = 1; + + // Loop until `vBlankFlag` is clear + while (vBlankFlag) { + std::this_thread::yield(); + } +} + +// NMI Interrupt Service Routine +void SNES::NmiIsr() { + // Switch to a FastROM bank (assuming NmiIsr is in bank 0x80) + // ... + + // Push CPU registers to stack + cpu.PHP(); + + // Reset DB and DP registers + cpu.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00 + cpu.D = 0; + + if (vBlankFlag) { + VBlankRoutine(); + + // Clear `vBlankFlag` + vBlankFlag = false; + } + + // Increment 32-bit frameCounter + frameCounter++; + + // Restore CPU registers + cpu.PHB(); +} + +// VBlank routine +void SNES::VBlankRoutine() { + // Execute code that needs to run during VBlank, such as transferring data to + // the PPU + // ... +} + void SNES::RenderScanline() { // Render background layers for (int layer = 0; layer < 4; layer++) { diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index 35a36804..240ac9bb 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -11,7 +11,53 @@ namespace yaze { namespace app { namespace emu { -class SNES { +// Direct Memory Address +class DMA { + public: + DMA() { + // Initialize DMA and HDMA channels + for (int i = 0; i < 8; ++i) { + channels[i].DMAPn = 0; + channels[i].BBADn = 0; + channels[i].UNUSEDn = 0; + channels[i].A1Tn = 0xFFFFFF; + channels[i].DASn = 0xFFFF; + channels[i].A2An = 0xFFFF; + channels[i].NLTRn = 0xFF; + } + } + + // DMA Transfer Modes + enum class DMA_TRANSFER_TYPE { + OAM, + PPUDATA, + CGDATA, + FILL_VRAM, + CLEAR_VRAM, + RESET_VRAM + }; + + // Functions for handling DMA and HDMA transfers + void StartDMATransfer(uint8_t channels); + void EnableHDMATransfers(uint8_t channels); + + // Structure for DMA and HDMA channel registers + struct Channel { + uint8_t DMAPn; // DMA/HDMA parameters + uint8_t BBADn; // B-bus address + uint8_t UNUSEDn; // Unused byte + uint32_t A1Tn; // DMA Current Address / HDMA Table Start Address + uint16_t DASn; // DMA Byte-Counter / HDMA indirect table address + uint16_t A2An; // HDMA Table Current Address + uint8_t NLTRn; // HDMA Line-Counter + }; + Channel channels[8]; + + uint8_t MDMAEN = 0; // Start DMA transfer + uint8_t HDMAEN = 0; // Enable HDMA transfers +}; + +class SNES : public DMA { public: SNES() = default; ~SNES() = default; @@ -22,21 +68,23 @@ class SNES { // Main emulation loop void Run(); - // Functions for CPU-related operations - void Fetch(); - void Decode(); - void Execute(); + // Enable NMI Interrupts + void EnableVBlankInterrupts(); + + // Wait until the VBlank routine has been processed + void WaitForVBlank(); + + // NMI Interrupt Service Routine + void NmiIsr(); + + // VBlank routine + void VBlankRoutine(); // Functions for PPU-related operations void RenderScanline(); - void UpdateSprites(); void DrawBackgroundLayer(int layer); void DrawSprites(); - // Memory-related functions - uint8_t ReadMemory(uint16_t address); - void WriteMemory(uint16_t address, uint8_t value); - // Controller input handling void HandleInput(); @@ -51,6 +99,10 @@ class SNES { bool running() const { return running_; } private: + void WriteToRegister(uint16_t address, uint8_t value) { + memory_.WriteByte(address, value); + } + // Components of the SNES MemoryImpl memory_; CPU cpu{memory_}; @@ -60,11 +112,17 @@ class SNES { // Helper classes Debugger debugger; - // Other private member variables std::vector rom_data; + + // Byte flag to indicate if the VBlank routine should be executed or not + std::atomic vBlankFlag; + + // 32-bit counter to track the number of NMI interrupts (useful for clocks and + // timers) + std::atomic frameCounter; + + // Other private member variables bool running_; - uint16_t pc; - uint32_t cycle; int scanline; };