Files
yaze/src/app/emu/snes.cc
2024-04-25 20:57:13 -04:00

579 lines
15 KiB
C++

#include "app/emu/snes.h"
#include <cstdint>
#include <memory>
#include <string>
#include <thread>
#include "app/emu/audio/apu.h"
#include "app/emu/audio/spc700.h"
#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"
namespace yaze {
namespace app {
namespace emu {
namespace {
void input_latch(Input* input, bool value) {
input->latch_line_ = value;
if (input->latch_line_) input->latched_state_ = input->current_state_;
}
uint8_t input_read(Input* input) {
if (input->latch_line_) input->latched_state_ = input->current_state_;
uint8_t ret = input->latched_state_ & 1;
input->latched_state_ >>= 1;
input->latched_state_ |= 0x8000;
return ret;
}
} // namespace
void SNES::Init(std::vector<uint8_t>& rom_data) {
// Initialize the CPU, PPU, and APU
ppu_.Init();
apu_.Init();
// Load the ROM into memory and set up the memory mapping
memory_.Initialize(rom_data);
Reset(true);
running_ = true;
}
void SNES::Reset(bool hard) {
cpu_.Reset(hard);
apu_.Reset();
ppu_.Reset();
memory::dma::Reset(&memory_);
input1.latch_line_ = false;
input2.latch_line_ = false;
input1.latched_state_ = 0;
input2.latched_state_ = 0;
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 * 4;
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;
ppu_latch_ = false;
multiply_a_ = 0xff;
multiply_result_ = 0xFE01;
divide_a_ = 0xffFF;
divide_result_ = 0x101;
fast_mem_ = false;
memory_.set_open_bus(0);
next_horiz_event = 16;
InitAccessTime(false);
}
void SNES::RunFrame() {
while (in_vblank_) {
cpu_.RunOpcode();
}
uint32_t frame = frames_;
while (!in_vblank_ && frame == frames_) {
cpu_.RunOpcode();
}
}
void SNES::CatchUpApu() { apu_.RunCycles(cycles_); }
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::RunCycle() {
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_ || !h_irq_enabled_));
if (!irq_condition_ && condition) {
in_irq_ = true;
cpu_.SetIrq(true);
}
irq_condition_ = condition;
// increment position; must come after irq checks! (hagane, cybernator)
memory_.set_h_pos(memory_.h_pos() + 2);
// handle positional stuff
if (memory_.h_pos() == next_horiz_event) {
switch (memory_.h_pos()) {
case 16: {
next_horiz_event = 512;
if (memory_.v_pos() == 0) memory_.init_hdma_request();
} break;
case 512: {
next_horiz_event = 1104;
// render the line halfway of the screen for better compatibility
if (!in_vblank_ && memory_.v_pos() > 0) ppu_.RunLine(memory_.v_pos());
} break;
case 1104: {
if (!in_vblank_) memory_.run_hdma_request();
if (!memory_.pal_timing()) {
// line 240 of odd frame with no interlace is 4 cycles shorter
next_horiz_event = (memory_.v_pos() == 240 && !ppu_.even_frame &&
!ppu_.frame_interlace)
? 1360
: 1364;
} else {
// line 311 of odd frame with interlace is 4 cycles longer
next_horiz_event = (memory_.v_pos() != 311 || ppu_.even_frame ||
!ppu_.frame_interlace)
? 1364
: 1368;
}
} break;
case 1360:
case 1364:
case 1368: { // this is the end (of the h-line)
next_horiz_event = 16;
memory_.set_h_pos(0);
memory_.set_v_pos(memory_.v_pos() + 1);
if (!memory_.pal_timing()) {
// 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 {
// 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_++;
}
}
// end of hblank, do most memory_.v_pos()-tests
bool starting_vblank = 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 memory_.v_pos() 240
// (overscan)
starting_vblank = !ppu_.CheckOverscan();
} else if (memory_.v_pos() == 240) {
// if we are not yet in vblank, we had an overscan frame, set
// starting_vblank
if (!in_vblank_) starting_vblank = true;
}
if (starting_vblank) {
// catch up the apu at end of emulated frame (we end frame @ start of
// vblank)
CatchUpApu();
// notify dsp of frame-end, because sometimes dma will extend much
// further past vblank (or even into the next frame) Megaman X2
// (titlescreen animation), Tales of Phantasia (game demo), Actraiser
// 2 (fade-in @ bootup)
apu_.dsp().NewFrame();
// 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();
}
}
} break;
}
}
// handle auto_joy_read_-timer
if (auto_joy_timer_ > 0) auto_joy_timer_ -= 2;
}
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, ppu_latch_);
}
if (adr < 0x80) {
CatchUpApu(); // catch up the apu before reading
return apu_.out_ports_[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 ppu_latch_ << 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_.in_ports_[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 in_nmi_ is still set, immediately generate nmi
if (!nmi_enabled_ && (val & 0x80) && in_nmi_) {
cpu_.Nmi();
}
nmi_enabled_ = val & 0x80;
cpu_.set_int_delay(true);
break;
}
case 0x4201: {
if (!(val & 0x80) && ppu_latch_) {
// latch the ppu
ppu_.LatchHV();
}
ppu_latch_ = 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) {
cpu_.set_int_delay(false);
const int cycles = access_time[adr] - 4;
memory::dma::HandleDma(this, &memory_, cycles);
RunCycles(cycles);
uint8_t rv = Read(adr);
memory::dma::HandleDma(this, &memory_, 4);
RunCycles(4);
return rv;
}
void SNES::CpuWrite(uint32_t adr, uint8_t val) {
cpu_.set_int_delay(false);
const int cycles = access_time[adr];
memory::dma::HandleDma(this, &memory_, cycles);
RunCycles(cycles);
Write(adr, val);
}
void SNES::CpuIdle(bool waiting) {
cpu_.set_int_delay(false);
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, memory_.pal_timing());
}
void SNES::SetPixels(uint8_t* pixel_data) { ppu_.PutPixels(pixel_data); }
void SNES::SetButtonState(int player, int button, bool pressed) {
// set key in controller
if (player == 1) {
if (pressed) {
input1.current_state_ |= 1 << button;
} else {
input1.current_state_ &= ~(1 << button);
}
} else {
if (pressed) {
input2.current_state_ |= 1 << button;
} else {
input2.current_state_ &= ~(1 << button);
}
}
}
void SNES::InitAccessTime(bool recalc) {
int start = (recalc) ? 0x800000 : 0; // recalc only updates fast rom
access_time.resize(0x1000000);
for (int i = start; i < 0x1000000; i++) {
access_time[i] = GetAccessTime(i);
}
}
} // namespace emu
} // namespace app
} // namespace yaze