Add SNES BBus, registers, input, nmi/irq, joypad handling, frame timing, cpu callbacks, etc

This commit is contained in:
scawful
2024-04-22 15:53:17 -04:00
parent 541e045c46
commit 917cd26a6e
6 changed files with 652 additions and 329 deletions

View File

@@ -171,9 +171,10 @@ void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); }
void Controller::PlayAudio() {
if (master_editor_.emulator().running()) {
master_editor_.emulator().snes().SetSamples(audio_buffer_, wanted_samples_);
if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) {
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
auto wanted_samples = master_editor_.emulator().wanted_samples();
master_editor_.emulator().snes().SetSamples(audio_buffer_, wanted_samples);
if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples * 4 * 6) {
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples * 4);
}
}
}
@@ -190,6 +191,7 @@ void Controller::OnExit() {
Mix_CloseAudio();
SDL_PauseAudioDevice(audio_device_, 1);
SDL_CloseAudioDevice(audio_device_);
delete audio_buffer_;
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
@@ -341,6 +343,8 @@ absl::Status Controller::LoadAudioDevice() {
return absl::InternalError(
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
}
audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
master_editor_.emulator().set_audio_buffer(audio_buffer_);
SDL_PauseAudioDevice(audio_device_, 0);
return absl::OkStatus();
}

View File

@@ -63,7 +63,6 @@ class Controller : public ExperimentFlags {
friend int ::main(int argc, char **argv);
bool active_;
int wanted_samples_;
int audio_frequency_ = 48000;
int16_t *audio_buffer_;
editor::MasterEditor master_editor_;

View File

@@ -50,19 +50,74 @@ using ImGui::Text;
void Emulator::Run() {
if (!snes_.running() && rom()->is_loaded()) {
ppu_texture_ =
SDL_CreateTexture(rom()->renderer().get(), SDL_PIXELFORMAT_RGBX8888,
SDL_TEXTUREACCESS_STREAMING, 512, 480);
if (ppu_texture_ == NULL) {
printf("Failed to create texture: %s\n", SDL_GetError());
return;
}
snes_.Init(*rom());
wanted_frames_ = 1.0 / (snes_.Memory()->pal_timing() ? 50.0 : 60.0);
wanted_samples_ = 48000 / (snes_.Memory()->pal_timing() ? 50 : 60);
countFreq = SDL_GetPerformanceFrequency();
lastCount = SDL_GetPerformanceCounter();
timeAdder = 0.0;
}
RenderNavBar();
if (running_) {
HandleEvents();
snes_.Run();
uint64_t curCount = SDL_GetPerformanceCounter();
uint64_t delta = curCount - lastCount;
lastCount = curCount;
float seconds = delta / (float)countFreq;
timeAdder += seconds;
// allow 2 ms earlier, to prevent skipping due to being just below wanted
while (timeAdder >= wanted_frames_ - 0.002) {
timeAdder -= wanted_frames_;
snes_.RunFrame();
void* ppu_pixels_;
int ppu_pitch_;
if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) != 0) {
printf("Failed to lock texture: %s\n", SDL_GetError());
return;
}
snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels_));
SDL_UnlockTexture(ppu_texture_);
}
}
gui::zeml::Render(emulator_node_);
}
void Emulator::RenderSnesPpu() {
ImVec2 size = ImVec2(320, 480);
if (snes_.running()) {
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
ImGui::Image((void*)ppu_texture_, size, ImVec2(0, 0), ImVec2(1, 1));
ImGui::EndChild();
} else {
ImGui::Text("Emulator output not available.");
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f);
ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f);
ImGui::Dummy(size);
ImGui::EndChild();
}
ImGui::Separator();
}
void Emulator::RenderNavBar() {
std::string navbar_layout = R"(
BeginMenuBar {
@@ -94,7 +149,7 @@ void Emulator::RenderNavBar() {
if (ImGui::Button(ICON_MD_SKIP_NEXT)) {
// Step through Code logic
snes_.StepRun();
// snes_.StepRun();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Step Through Code");
@@ -148,6 +203,10 @@ void Emulator::RenderNavBar() {
}
// About Debugger logic
}
SameLine();
ImGui::Checkbox("Logging", snes_.cpu().mutable_log_instructions());
static bool show_memory_viewer = false;
SameLine();
@@ -170,29 +229,6 @@ void Emulator::HandleEvents() {
// ...
}
void Emulator::RenderSnesPpu() {
ImVec2 size = ImVec2(320, 240);
if (snes_.running()) {
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
ImGui::Image((void*)snes_.ppu().GetScreen()->texture(), size, ImVec2(0, 0),
ImVec2(1, 1));
ImGui::EndChild();
} else {
ImGui::Text("Emulator output not available.");
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f);
ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f);
ImGui::Dummy(size);
ImGui::EndChild();
}
ImGui::Separator();
}
void Emulator::RenderBreakpointList() {
Text("Breakpoints");
Separator();

View File

@@ -71,6 +71,8 @@ class Emulator : public SharedRom {
auto snes() -> SNES& { return snes_; }
auto running() const -> bool { return running_; }
void set_audio_buffer(int16_t* audio_buffer) { audio_buffer_ = audio_buffer; }
auto wanted_samples() const -> int { return wanted_samples_; }
private:
void RenderNavBar();
@@ -89,15 +91,28 @@ class Emulator : public SharedRom {
void RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instructionLog);
SNES snes_;
uint16_t manual_pc_ = 0;
uint8_t manual_pb_ = 0;
gui::zeml::Node emulator_node_;
bool step_ = true;
bool power_ = false;
bool loading_ = false;
bool running_ = false;
bool step_ = true;
float wanted_frames_;
int wanted_samples_;
uint8_t manual_pb_ = 0;
uint16_t manual_pc_ = 0;
// timing
uint64_t countFreq;
uint64_t lastCount;
float timeAdder = 0.0;
int16_t* audio_buffer_;
SNES snes_;
SDL_Texture* ppu_texture_;
gui::zeml::Node emulator_node_;
};
} // namespace emu

View File

@@ -12,6 +12,7 @@
#include "app/emu/cpu/clock.h"
#include "app/emu/cpu/cpu.h"
#include "app/emu/debug/debugger.h"
#include "app/emu/memory/dma.h"
#include "app/emu/memory/memory.h"
#include "app/emu/video/ppu.h"
#include "app/rom.h"
@@ -20,104 +21,23 @@ namespace yaze {
namespace app {
namespace emu {
namespace {
uint16_t GetHeaderOffset(const Memory& memory) {
uint8_t mapMode = memory[(0x00 << 16) + 0xFFD5];
uint16_t offset;
switch (mapMode & 0x07) {
case 0: // LoROM
offset = 0x7FC0;
break;
case 1: // HiROM
offset = 0xFFC0;
break;
case 5: // ExHiROM
offset = 0x40;
break;
default:
throw std::invalid_argument(
"Unable to locate supported ROM mapping mode in the provided ROM "
"file. Please try another ROM file.");
}
return offset;
}
} // namespace
RomInfo SNES::ReadRomHeader(uint32_t offset) {
RomInfo romInfo;
// Read cartridge title
char title[22];
for (int i = 0; i < 21; ++i) {
title[i] = cpu_.ReadByte(offset + i);
}
title[21] = '\0'; // Null-terminate the string
romInfo.title = std::string(title);
// Read ROM speed and memory map mode
uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15);
romInfo.romSpeed = (RomSpeed)(romSpeedAndMapMode & 0x07);
romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01);
// Read ROM type
romInfo.romType = (RomType)cpu_.ReadByte(offset + 0x16);
// Read ROM size
romInfo.romSize = (RomSize)cpu_.ReadByte(offset + 0x17);
// Read RAM size
romInfo.sramSize = (SramSize)cpu_.ReadByte(offset + 0x18);
// Read country code
romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19);
// Read license
romInfo.license = (License)cpu_.ReadByte(offset + 0x1A);
// Read ROM version
romInfo.version = cpu_.ReadByte(offset + 0x1B);
// Read checksum complement
romInfo.checksumComplement = cpu_.ReadWord(offset + 0x1E);
// Read checksum
romInfo.checksum = cpu_.ReadWord(offset + 0x1C);
// Read NMI VBL vector
romInfo.nmiVblVector = cpu_.ReadWord(offset + 0x3E);
// Read reset vector
romInfo.resetVector = cpu_.ReadWord(offset + 0x3C);
return romInfo;
}
void SNES::Init(Rom& rom) {
// Setup observers for the memory space
memory_.AddObserver(&apu_);
memory_.AddObserver(&ppu_);
// Load the ROM into memory and set up the memory mapping
rom_data = rom.vector();
memory_.Initialize(rom_data);
// Perform a long jump into a FastROM bank (if the ROM speed is FastROM)
// Read the ROM header
rom_info_ = memory_.ReadRomHeader();
Reset();
// Disable the emulation flag (switch to 65816 native mode)
cpu_.E = 0;
cpu_.PB = 0x00;
cpu_.PC = 0x8000;
// Initialize CPU
cpu_.Init();
// Read the ROM header
auto header_offset = GetHeaderOffset(memory_);
rom_info_ = ReadRomHeader((0x00 << 16) + header_offset);
cpu_.PB = 0x00;
cpu_.PC = 0x8000;
// Initialize PPU
ppu_.Init();
@@ -131,31 +51,9 @@ void SNES::Init(Rom& rom) {
// 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
@@ -172,32 +70,6 @@ void SNES::Init(Rom& rom) {
// 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
@@ -206,140 +78,505 @@ void SNES::Init(Rom& rom) {
// Misc
memory_.WriteByte(0x2133, 0x00); // SETINI
// Psuedo-Init
memory_.WriteWord(0x2140, 0xBBAA);
running_ = true;
scanline = 0;
}
void SNES::Run() {
const double targetFPS = 60.0; // 60 frames per second
const double frame_time = 1.0 / targetFPS;
double frame_accumulated_time = 0.0;
void SNES::Reset(bool hard) {
cpu_.Reset(hard);
apu_.Reset();
ppu_.Reset();
input1.latchLine = false;
input2.latchLine = false;
input1.latchedState = 0;
input2.latchedState = 0;
// cart_reset();
// if(hard) memset(ram, 0, sizeof(ram));
ram_adr_ = 0;
memory_.set_h_pos(0);
memory_.set_v_pos(0);
frames_ = 0;
cycles_ = 0;
sync_cycle_ = 0;
apu_catchup_cycles_ = 0.0;
h_irq_enabled_ = false;
v_irq_enabled_ = false;
nmi_enabled_ = false;
h_timer_ = 0x1ff;
v_timer_ = 0x1ff;
in_nmi_ = false;
irq_condition_ = false;
in_irq_ = false;
in_vblank_ = false;
memset(port_auto_read_, 0, sizeof(port_auto_read_));
auto_joy_read_ = false;
auto_joy_timer_ = 0;
ppuLatch = false;
multiply_a_ = 0xff;
multiply_result_ = 0xFE01;
divide_a_ = 0xffFF;
divide_result_ = 0x101;
fast_mem_ = false;
memory_.set_open_bus(0);
}
auto last_time = std::chrono::high_resolution_clock::now();
if (running_) {
auto current_time = std::chrono::high_resolution_clock::now();
double delta_time =
std::chrono::duration<double>(current_time - last_time).count();
last_time = current_time;
frame_accumulated_time += delta_time;
// Update the CPU
cpu_.UpdateClock(delta_time);
cpu_.Update(GetCpuMode());
// Update the PPU
ppu_.UpdateClock(delta_time);
ppu_.Update();
// Update the APU
apu_.UpdateClock(delta_time);
apu_.Update();
if (frame_accumulated_time >= frame_time) {
// renderer.Render();
frame_accumulated_time -= frame_time;
}
HandleInput();
void SNES::RunFrame() {
while (in_vblank_) {
cpu_.RunOpcode();
}
}
void SNES::StepRun() {
// Update the CPU
cpu_.UpdateClock(0.0);
cpu_.Update(Cpu::UpdateMode::Step);
// Update the PPU
ppu_.UpdateClock(0.0);
ppu_.Update();
// Update the APU
apu_.UpdateClock(0.0);
apu_.Update();
HandleInput();
}
// Enable NMI Interrupts
void SNES::EnableVBlankInterrupts() {
v_blank_flag_ = false;
// 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() {
v_blank_flag_ = true;
// Loop until `v_blank_flag_` is clear
while (v_blank_flag_) {
std::this_thread::yield();
uint32_t frame = frames_;
while (!in_vblank_ && frame == frames_) {
cpu_.RunOpcode();
}
CatchUpApu();
}
// 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 (v_blank_flag_) {
VBlankRoutine();
// Clear `v_blank_flag_`
v_blank_flag_ = false;
}
// Increment 32-bit frame_counter_
frame_counter_++;
// Restore CPU registers
cpu_.PHB();
void SNES::CatchUpApu() {
int catchup_cycles = (int)apu_catchup_cycles_;
int ran_cycles = apu_.RunCycles(catchup_cycles);
apu_catchup_cycles_ -= ran_cycles;
}
// VBlank routine
void SNES::VBlankRoutine() {
// Read the joypad state
// ...
namespace {
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0);
// Update the PPU
// ...
// Update the APU
// ...
void input_latch(Input* input, bool value) {
input->latchLine = value;
if (input->latchLine) input->latchedState = input->currentState;
}
uint8_t input_read(Input* input) {
if (input->latchLine) input->latchedState = input->currentState;
uint8_t ret = input->latchedState & 1;
input->latchedState >>= 1;
input->latchedState |= 0x8000;
return ret;
}
} // namespace
void SNES::HandleInput() {
// ...
memset(port_auto_read_, 0, sizeof(port_auto_read_));
// latch controllers
input_latch(&input1, true);
input_latch(&input2, true);
input_latch(&input1, false);
input_latch(&input2, false);
for (int i = 0; i < 16; i++) {
uint8_t val = input_read(&input1);
port_auto_read_[0] |= ((val & 1) << (15 - i));
port_auto_read_[2] |= (((val >> 1) & 1) << (15 - i));
val = input_read(&input2);
port_auto_read_[1] |= ((val & 1) << (15 - i));
port_auto_read_[3] |= (((val >> 1) & 1) << (15 - i));
}
}
void SNES::SaveState(const std::string& path) {
// ...
void SNES::RunCycle() {
apu_catchup_cycles_ +=
(memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster) * 2.0;
cycles_ += 2;
// check for h/v timer irq's
bool condition = ((v_irq_enabled_ || h_irq_enabled_) &&
(memory_.v_pos() == v_timer_ || !v_irq_enabled_) &&
(memory_.h_pos() == h_timer_ * 4 || !h_irq_enabled_));
if (!irq_condition_ && condition) {
in_irq_ = true;
cpu_.SetIrq(true);
}
irq_condition_ = condition;
// handle positional stuff
if (memory_.h_pos() == 0) {
// end of hblank, do most v_pos_-tests
bool startingVblank = false;
if (memory_.v_pos() == 0) {
// end of vblank
in_vblank_ = false;
in_nmi_ = false;
ppu_.HandleFrameStart();
} else if (memory_.v_pos() == 225) {
// ask the ppu if we start vblank now or at v_pos_ 240 (overscan)
startingVblank = !ppu_.CheckOverscan();
} else if (memory_.v_pos() == 240) {
// if we are not yet in vblank, we had an overscan frame, set
// startingVblank
if (!in_vblank_) startingVblank = true;
}
if (startingVblank) {
// if we are starting vblank
ppu_.HandleVblank();
in_vblank_ = true;
in_nmi_ = true;
if (auto_joy_read_) {
// TODO: this starts a little after start of vblank
auto_joy_timer_ = 4224;
HandleInput();
}
if (nmi_enabled_) {
cpu_.Nmi();
}
}
} else if (memory_.h_pos() == 16) {
if (memory_.v_pos() == 0) memory_.init_hdma_request();
} else if (memory_.h_pos() == 512) {
// render the line halfway of the screen for better compatibility
if (!in_vblank_ && memory_.v_pos() > 0) ppu_.RunLine(memory_.v_pos());
} else if (memory_.h_pos() == 1104) {
if (!in_vblank_) memory_.run_hdma_request();
}
// handle autoJoyRead-timer
if (auto_joy_timer_ > 0) auto_joy_timer_ -= 2;
// increment position
memory_.set_h_pos(memory_.h_pos() + 2);
if (!memory_.pal_timing()) {
// line 240 of odd frame with no interlace is 4 cycles shorter
if ((memory_.h_pos() == 1360 && memory_.v_pos() == 240 &&
!ppu_.even_frame && !ppu_.frame_interlace) ||
memory_.h_pos() == 1364) {
memory_.set_h_pos(0);
memory_.set_v_pos(memory_.v_pos() + 1);
// even interlace frame is 263 lines
if ((memory_.v_pos() == 262 &&
(!ppu_.frame_interlace || !ppu_.even_frame)) ||
memory_.v_pos() == 263) {
memory_.set_v_pos(0);
frames_++;
}
}
} else {
// line 311 of odd frame with interlace is 4 cycles longer
if ((memory_.h_pos() == 1364 &&
(memory_.v_pos() != 311 || ppu_.even_frame ||
!ppu_.frame_interlace)) ||
memory_.h_pos() == 1368) {
memory_.set_h_pos(0);
memory_.set_v_pos(memory_.v_pos() + 1);
// even interlace frame is 313 lines
if ((memory_.v_pos() == 312 &&
(!ppu_.frame_interlace || !ppu_.even_frame)) ||
memory_.v_pos() == 313) {
memory_.set_v_pos(0);
frames_++;
}
}
}
}
void SNES::LoadState(const std::string& path) {
// ...
void SNES::RunCycles(int cycles) {
if (memory_.h_pos() + cycles >= 536 && memory_.h_pos() < 536) {
// if we go past 536, add 40 cycles for dram refersh
cycles += 40;
}
for (int i = 0; i < cycles; i += 2) {
RunCycle();
}
}
void SNES::SyncCycles(bool start, int sync_cycles) {
int count = 0;
if (start) {
sync_cycle_ = cycles_;
count = sync_cycles - (cycles_ % sync_cycles);
} else {
count = sync_cycles - ((cycles_ - sync_cycle_) % sync_cycles);
}
RunCycles(count);
}
uint8_t SNES::ReadBBus(uint8_t adr) {
if (adr < 0x40) {
return ppu_.Read(adr);
}
if (adr < 0x80) {
CatchUpApu(); // catch up the apu before reading
return apu_.outPorts[adr & 0x3];
}
if (adr == 0x80) {
uint8_t ret = ram[ram_adr_++];
ram_adr_ &= 0x1ffff;
return ret;
}
return memory_.open_bus();
}
uint8_t SNES::ReadReg(uint16_t adr) {
switch (adr) {
case 0x4210: {
uint8_t val = 0x2; // CPU version (4 bit)
val |= in_nmi_ << 7;
in_nmi_ = false;
return val | (memory_.open_bus() & 0x70);
}
case 0x4211: {
uint8_t val = in_irq_ << 7;
in_irq_ = false;
cpu_.SetIrq(false);
return val | (memory_.open_bus() & 0x7f);
}
case 0x4212: {
uint8_t val = (auto_joy_timer_ > 0);
val |= (memory_.h_pos() < 4 || memory_.h_pos() >= 1096) << 6;
val |= in_vblank_ << 7;
return val | (memory_.open_bus() & 0x3e);
}
case 0x4213: {
return ppuLatch << 7; // IO-port
}
case 0x4214: {
return divide_result_ & 0xff;
}
case 0x4215: {
return divide_result_ >> 8;
}
case 0x4216: {
return multiply_result_ & 0xff;
}
case 0x4217: {
return multiply_result_ >> 8;
}
case 0x4218:
case 0x421a:
case 0x421c:
case 0x421e: {
return port_auto_read_[(adr - 0x4218) / 2] & 0xff;
}
case 0x4219:
case 0x421b:
case 0x421d:
case 0x421f: {
return port_auto_read_[(adr - 0x4219) / 2] >> 8;
}
default: {
return memory_.open_bus();
}
}
}
uint8_t SNES::Rread(uint32_t adr) {
uint8_t bank = adr >> 16;
adr &= 0xffff;
if (bank == 0x7e || bank == 0x7f) {
return ram[((bank & 1) << 16) | adr]; // ram
}
if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
if (adr < 0x2000) {
return ram[adr]; // ram mirror
}
if (adr >= 0x2100 && adr < 0x2200) {
return ReadBBus(adr & 0xff); // B-bus
}
if (adr == 0x4016) {
return input_read(&input1) | (memory_.open_bus() & 0xfc);
}
if (adr == 0x4017) {
return input_read(&input2) | (memory_.open_bus() & 0xe0) | 0x1c;
}
if (adr >= 0x4200 && adr < 0x4220) {
return ReadReg(adr); // internal registers
}
if (adr >= 0x4300 && adr < 0x4380) {
return memory::dma::Read(&memory_, adr); // dma registers
}
}
// read from cart
return memory_.cart_read(bank, adr);
}
uint8_t SNES::Read(uint32_t adr) {
uint8_t val = Rread(adr);
memory_.set_open_bus(val);
return val;
}
void SNES::WriteBBus(uint8_t adr, uint8_t val) {
if (adr < 0x40) {
ppu_.Write(adr, val);
return;
}
if (adr < 0x80) {
CatchUpApu(); // catch up the apu before writing
apu_.inPorts[adr & 0x3] = val;
return;
}
switch (adr) {
case 0x80: {
ram[ram_adr_++] = val;
ram_adr_ &= 0x1ffff;
break;
}
case 0x81: {
ram_adr_ = (ram_adr_ & 0x1ff00) | val;
break;
}
case 0x82: {
ram_adr_ = (ram_adr_ & 0x100ff) | (val << 8);
break;
}
case 0x83: {
ram_adr_ = (ram_adr_ & 0x0ffff) | ((val & 1) << 16);
break;
}
}
}
void SNES::WriteReg(uint16_t adr, uint8_t val) {
switch (adr) {
case 0x4200: {
auto_joy_read_ = val & 0x1;
if (!auto_joy_read_) auto_joy_timer_ = 0;
h_irq_enabled_ = val & 0x10;
v_irq_enabled_ = val & 0x20;
if (!h_irq_enabled_ && !v_irq_enabled_) {
in_irq_ = false;
cpu_.SetIrq(false);
}
// if nmi is enabled while inNmi is still set, immediately generate nmi
if (!nmi_enabled_ && (val & 0x80) && in_nmi_) {
cpu_.Nmi();
}
nmi_enabled_ = val & 0x80;
break;
}
case 0x4201: {
if (!(val & 0x80) && ppuLatch) {
// latch the ppu
ppu_.Read(0x37);
}
ppuLatch = val & 0x80;
break;
}
case 0x4202: {
multiply_a_ = val;
break;
}
case 0x4203: {
multiply_result_ = multiply_a_ * val;
break;
}
case 0x4204: {
divide_a_ = (divide_a_ & 0xff00) | val;
break;
}
case 0x4205: {
divide_a_ = (divide_a_ & 0x00ff) | (val << 8);
break;
}
case 0x4206: {
if (val == 0) {
divide_result_ = 0xffff;
multiply_result_ = divide_a_;
} else {
divide_result_ = divide_a_ / val;
multiply_result_ = divide_a_ % val;
}
break;
}
case 0x4207: {
h_timer_ = (h_timer_ & 0x100) | val;
break;
}
case 0x4208: {
h_timer_ = (h_timer_ & 0x0ff) | ((val & 1) << 8);
break;
}
case 0x4209: {
v_timer_ = (v_timer_ & 0x100) | val;
break;
}
case 0x420a: {
v_timer_ = (v_timer_ & 0x0ff) | ((val & 1) << 8);
break;
}
case 0x420b: {
memory::dma::StartDma(&memory_, val, false);
break;
}
case 0x420c: {
memory::dma::StartDma(&memory_, val, true);
break;
}
case 0x420d: {
fast_mem_ = val & 0x1;
break;
}
default: {
break;
}
}
}
void SNES::Write(uint32_t adr, uint8_t val) {
memory_.set_open_bus(val);
uint8_t bank = adr >> 16;
adr &= 0xffff;
if (bank == 0x7e || bank == 0x7f) {
ram[((bank & 1) << 16) | adr] = val; // ram
}
if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
if (adr < 0x2000) {
ram[adr] = val; // ram mirror
}
if (adr >= 0x2100 && adr < 0x2200) {
WriteBBus(adr & 0xff, val); // B-bus
}
if (adr == 0x4016) {
input_latch(&input1, val & 1); // input latch
input_latch(&input2, val & 1);
}
if (adr >= 0x4200 && adr < 0x4220) {
WriteReg(adr, val); // internal registers
}
if (adr >= 0x4300 && adr < 0x4380) {
memory::dma::Write(&memory_, adr, val); // dma registers
}
}
// write to cart
memory_.cart_write(bank, adr, val);
}
int SNES::GetAccessTime(uint32_t adr) {
uint8_t bank = adr >> 16;
adr &= 0xffff;
if ((bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) && adr < 0x8000) {
// 00-3f,80-bf:0-7fff
if (adr < 0x2000 || adr >= 0x6000) return 8; // 0-1fff, 6000-7fff
if (adr < 0x4000 || adr >= 0x4200) return 6; // 2000-3fff, 4200-5fff
return 12; // 4000-41ff
}
// 40-7f,co-ff:0000-ffff, 00-3f,80-bf:8000-ffff
return (fast_mem_ && bank >= 0x80) ? 6
: 8; // depends on setting in banks 80+
}
uint8_t SNES::CpuRead(uint32_t adr) {
int cycles = GetAccessTime(adr);
memory::dma::HandleDma(this, &memory_, cycles);
RunCycles(cycles);
return Read(adr);
}
void SNES::CpuWrite(uint32_t adr, uint8_t val) {
int cycles = GetAccessTime(adr);
memory::dma::HandleDma(this, &memory_, cycles);
RunCycles(cycles);
Write(adr, val);
}
void SNES::CpuIdle(bool waiting) {
memory::dma::HandleDma(this, &memory_, 6);
RunCycles(6);
}
void SNES::SetSamples(int16_t* sample_data, int wanted_samples) {
apu_.dsp().GetSamples(sample_data, wanted_samples, pal_timing_);
apu_.dsp().GetSamples(sample_data, wanted_samples, memory_.pal_timing());
}
void SNES::SetPixels(uint8_t* pixel_data) { ppu_.PutPixels(pixel_data); }
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -11,7 +11,6 @@
#include "app/emu/cpu/clock.h"
#include "app/emu/cpu/cpu.h"
#include "app/emu/debug/debugger.h"
#include "app/emu/memory/dma.h"
#include "app/emu/memory/memory.h"
#include "app/emu/video/ppu.h"
#include "app/rom.h"
@@ -20,84 +19,117 @@ namespace yaze {
namespace app {
namespace emu {
using namespace memory;
struct Input {
uint8_t type;
// latchline
bool latchLine;
// for controller
uint16_t currentState; // actual state
uint16_t latchedState;
};
class SNES : public DirectMemoryAccess {
class SNES {
public:
SNES() = default;
~SNES() = default;
RomInfo ReadRomHeader(uint32_t offset);
// Initialization
void Init(Rom& rom);
void Reset(bool hard = false);
// Main emulation loop
void Run();
// Step through a single instruction
void StepRun();
// 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();
// Emulation
void RunFrame();
void CatchUpApu();
// Controller input handling
void HandleInput();
// Save/Load game state
void SaveState(const std::string& path);
void LoadState(const std::string& path);
// Clock cycling and synchronization
void RunCycle();
void RunCycles(int cycles);
void SyncCycles(bool start, int sync_cycles);
uint8_t ReadBBus(uint8_t adr);
uint8_t ReadReg(uint16_t adr);
uint8_t Rread(uint32_t adr);
uint8_t Read(uint32_t adr);
void WriteBBus(uint8_t adr, uint8_t val);
void WriteReg(uint16_t adr, uint8_t val);
void Write(uint32_t adr, uint8_t val);
int GetAccessTime(uint32_t adr);
uint8_t CpuRead(uint32_t adr);
void CpuWrite(uint32_t adr, uint8_t val);
void CpuIdle(bool waiting);
void SetSamples(int16_t* sample_data, int wanted_samples);
void SetPixels(uint8_t* pixel_data);
bool running() const { return running_; }
auto cpu() -> Cpu& { return cpu_; }
auto ppu() -> video::Ppu& { return ppu_; }
auto Memory() -> MemoryImpl* { return &memory_; }
void SetCpuMode(int mode) { cpu_mode_ = mode; }
Cpu::UpdateMode GetCpuMode() const {
return static_cast<Cpu::UpdateMode>(cpu_mode_);
}
auto Memory() -> memory::MemoryImpl* { return &memory_; }
private:
// Components of the SNES
MemoryImpl memory_;
ClockImpl clock_;
Debugger debugger;
memory::RomInfo rom_info_;
memory::MemoryImpl memory_;
audio::AudioRamImpl audio_ram_;
Cpu cpu_{memory_, clock_};
memory::CpuCallbacks cpu_callbacks_ = {
[this](uint32_t adr) { return CpuRead(adr); },
[this](uint32_t adr, uint8_t val) { CpuWrite(adr, val); },
[this](bool waiting) { CpuIdle(waiting); },
};
Cpu cpu_{memory_, clock_, cpu_callbacks_};
video::Ppu ppu_{memory_, clock_};
audio::Apu apu_{memory_, audio_ram_, clock_};
// Helper classes
RomInfo rom_info_;
Debugger debugger;
// Currently loaded ROM
std::vector<uint8_t> rom_data;
// Byte flag to indicate if the VBlank routine should be executed or not
std::atomic<bool> v_blank_flag_;
// 32-bit counter to track the number of NMI interrupts
std::atomic<uint32_t> frame_counter_;
// Other private member variables
// Emulation state
bool running_ = false;
bool pal_timing_ = false;
int scanline;
int cpu_mode_ = 0;
// ram
uint8_t ram[0x20000];
uint32_t ram_adr_;
// Frame timing
uint32_t frames_ = 0;
uint64_t cycles_ = 0;
uint64_t sync_cycle_ = 0;
double apu_catchup_cycles_;
// Nmi / Irq
bool h_irq_enabled_ = false;
bool v_irq_enabled_ = false;
bool nmi_enabled_ = false;
uint16_t h_timer_ = 0;
uint16_t v_timer_ = 0;
bool in_nmi_ = false;
bool irq_condition_ = false;
bool in_irq_ = false;
bool in_vblank_;
// Multiplication / Division
uint8_t multiply_a_;
uint16_t multiply_result_;
uint8_t divide_a_;
uint8_t divide_result_;
// Joypad State
Input input1;
Input input2;
uint16_t port_auto_read_[4]; // as read by auto-joypad read
bool auto_joy_read_ = false;
uint16_t auto_joy_timer_ = 0;
bool ppuLatch;
bool fast_mem_ = false;
};
} // namespace emu