diff --git a/src/app/core/controller.cc b/src/app/core/controller.cc index bc40ef17..ad68f868 100644 --- a/src/app/core/controller.cc +++ b/src/app/core/controller.cc @@ -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(); } diff --git a/src/app/core/controller.h b/src/app/core/controller.h index 58973607..1ea8f43e 100644 --- a/src/app/core/controller.h +++ b/src/app/core/controller.h @@ -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_; diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 97bf3104..072c63ad 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -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(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(); diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index 4ca6e9b8..fd27ad0d 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -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& 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 diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index 6bf686a9..332eed39 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -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(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 \ No newline at end of file diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index b55d202b..5883657c 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -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_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 rom_data; - // Byte flag to indicate if the VBlank routine should be executed or not - std::atomic v_blank_flag_; - - // 32-bit counter to track the number of NMI interrupts - std::atomic 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