Add SNES BBus, registers, input, nmi/irq, joypad handling, frame timing, cpu callbacks, etc
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user