Add DMA and SNES Init, VBlank, NMI, etc
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
#include "snes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#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++) {
|
||||
|
||||
@@ -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<uint8_t> rom_data;
|
||||
|
||||
// Byte flag to indicate if the VBlank routine should be executed or not
|
||||
std::atomic<bool> vBlankFlag;
|
||||
|
||||
// 32-bit counter to track the number of NMI interrupts (useful for clocks and
|
||||
// timers)
|
||||
std::atomic<uint32_t> frameCounter;
|
||||
|
||||
// Other private member variables
|
||||
bool running_;
|
||||
uint16_t pc;
|
||||
uint32_t cycle;
|
||||
int scanline;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user