Avocado/src/system.cpp
2021-12-21 18:12:01 +01:00

539 lines
20 KiB
C++

#include "system.h"
#include <fmt/core.h>
#include <cstdlib>
#include <cstring>
#include "bios/functions.h"
#include "config.h"
#include "sound/sound.h"
#include "utils/address.h"
#include "utils/gpu_draw_list.h"
#include "utils/file.h"
#include "utils/psx_exe.h"
System::System() {
bios.fill(0);
ram.resize(!config.options.system.ram8mb ? RAM_SIZE_2MB : RAM_SIZE_8MB, 0);
scratchpad.fill(0);
expansion.fill(0);
cpu = std::make_unique<mips::CPU>(this);
gpu = std::make_unique<gpu::GPU>(this);
spu = std::make_unique<spu::SPU>(this);
mdec = std::make_unique<mdec::MDEC>();
cdrom = std::make_unique<device::cdrom::CDROM>(this);
controller = std::make_unique<device::controller::Controller>(this);
dma = std::make_unique<device::dma::DMA>(this);
expansion2 = std::make_unique<Expansion2>();
interrupt = std::make_unique<Interrupt>(this);
memoryControl = std::make_unique<MemoryControl>();
ramControl = std::make_unique<RamControl>();
cacheControl = std::make_unique<CacheControl>(this);
serial = std::make_unique<Serial>();
for (int t : {0, 1, 2}) {
timer[t] = std::make_unique<device::timer::Timer>(this, t);
}
debugOutput = config.debug.log.system;
biosLog = config.debug.log.bios;
cycles = 0;
}
// Note: stupid static_casts and asserts are only to suppress MSVC warnings
// Warning: This function does not check array boundaries. Make sure that address is aligned!
template <typename T>
constexpr T read_fast(uint8_t* device, uint32_t addr) {
static_assert(std::is_same<T, uint8_t>() || std::is_same<T, uint16_t>() || std::is_same<T, uint32_t>(), "Invalid type used");
if (sizeof(T) == 1) return static_cast<T>(((uint8_t*)device)[addr]);
if (sizeof(T) == 2) return static_cast<T>(((uint16_t*)device)[addr / 2]);
if (sizeof(T) == 4) return static_cast<T>(((uint32_t*)device)[addr / 4]);
return 0;
}
// Warning: This function does not check array boundaries. Make sure that address is aligned!
template <typename T>
constexpr void write_fast(uint8_t* device, uint32_t addr, T value) {
static_assert(std::is_same<T, uint8_t>() || std::is_same<T, uint16_t>() || std::is_same<T, uint32_t>(), "Invalid type used");
if (sizeof(T) == 1)
((uint8_t*)device)[addr] = static_cast<uint8_t>(value);
else if (sizeof(T) == 2)
((uint16_t*)device)[addr / 2] = static_cast<uint16_t>(value);
else if (sizeof(T) == 4)
((uint32_t*)device)[addr / 4] = static_cast<uint32_t>(value);
}
template <typename T, typename Device>
constexpr T read_io(Device& periph, uint32_t addr) {
static_assert(std::is_same<T, uint8_t>() || std::is_same<T, uint16_t>() || std::is_same<T, uint32_t>(), "Invalid type used");
if (sizeof(T) == 1) return periph->read(addr);
if (sizeof(T) == 2) return periph->read(addr) | periph->read(addr + 1) << 8;
if (sizeof(T) == 4)
return periph->read(addr) | periph->read(addr + 1) << 8 | periph->read(addr + 2) << 16 | periph->read(addr + 3) << 24;
return 0;
}
template <typename T, typename Device>
constexpr void write_io(Device& periph, uint32_t addr, T data) {
static_assert(std::is_same<T, uint8_t>() || std::is_same<T, uint16_t>() || std::is_same<T, uint32_t>(), "Invalid type used");
if (sizeof(T) == 1) {
periph->write(addr, (static_cast<uint8_t>(data)) & 0xff);
} else if (sizeof(T) == 2) {
periph->write(addr, (static_cast<uint16_t>(data)) & 0xff);
periph->write(addr + 1, (static_cast<uint16_t>(data) >> 8) & 0xff);
} else if (sizeof(T) == 4) {
periph->write(addr, (static_cast<uint32_t>(data)) & 0xff);
periph->write(addr + 1, (static_cast<uint32_t>(data) >> 8) & 0xff);
periph->write(addr + 2, (static_cast<uint32_t>(data) >> 16) & 0xff);
periph->write(addr + 3, (static_cast<uint32_t>(data) >> 24) & 0xff);
}
}
#ifdef ENABLE_IO_LOG
#define LOG_IO(mode, size, addr, data, pc) ioLogList.push_back({(mode), (size), (addr), (data), (pc)})
#else
#define LOG_IO(mode, size, addr, data, pc)
#endif
#define READ_IO(begin, end, periph) \
if (addr >= (begin) && addr < (end)) { \
auto data = read_io<T>((periph), addr - (begin)); \
\
LOG_IO(IO_LOG_ENTRY::MODE::READ, sizeof(T) * 8, address, data, cpu->PC); \
return data; \
}
#define READ_IO32(begin, end, periph) \
if (addr >= (begin) && addr < (end)) { \
T data = 0; \
if (sizeof(T) == 4) { \
data = (periph)->read(addr - (begin)); \
} else { \
fmt::print("[SYS] R Unsupported access to " #periph " with bit size {}\n", static_cast<int>(sizeof(T) * 8)); \
} \
\
LOG_IO(IO_LOG_ENTRY::MODE::READ, sizeof(T) * 8, address, data, cpu->PC); \
return data; \
}
#define WRITE_IO(begin, end, periph) \
if (addr >= (begin) && addr < (end)) { \
write_io<T>((periph), addr - (begin), data); \
\
LOG_IO(IO_LOG_ENTRY::MODE::WRITE, sizeof(T) * 8, address, data, cpu->PC); \
return; \
}
#define WRITE_IO32(begin, end, periph) \
if (addr >= (begin) && addr < (end)) { \
if (sizeof(T) == 4) { \
(periph)->write(addr - (begin), data); \
} else { \
fmt::print("[SYS] W Unsupported access to " #periph " with bit size {}\n", static_cast<int>(sizeof(T) * 8)); \
} \
\
LOG_IO(IO_LOG_ENTRY::MODE::WRITE, sizeof(T) * 8, address, data, cpu->PC); \
return; \
}
template <typename T>
INLINE T System::readMemory(uint32_t address) {
static_assert(std::is_same<T, uint8_t>() || std::is_same<T, uint16_t>() || std::is_same<T, uint32_t>(), "Invalid type used");
uint32_t addr = align_mips<T>(address);
if (in_range<RAM_BASE, RAM_SIZE_8MB>(addr)) {
return read_fast<T>(ram.data(), (addr - RAM_BASE) & (ram.size() - 1));
}
if (in_range<EXPANSION_BASE, EXPANSION_SIZE>(addr)) {
return read_fast<T>(expansion.data(), addr - EXPANSION_BASE);
}
if (in_range<SCRATCHPAD_BASE, SCRATCHPAD_SIZE>(addr)) {
return read_fast<T>(scratchpad.data(), addr - SCRATCHPAD_BASE);
}
if (in_range<BIOS_BASE, BIOS_SIZE>(addr)) {
return read_fast<T>(bios.data(), addr - BIOS_BASE);
}
READ_IO(0x1f801000, 0x1f801024, memoryControl);
READ_IO(0x1f801040, 0x1f801050, controller);
READ_IO(0x1f801050, 0x1f801060, serial);
READ_IO(0x1f801060, 0x1f801064, ramControl);
READ_IO(0x1f801070, 0x1f801078, interrupt);
READ_IO(0x1f801080, 0x1f801100, dma);
READ_IO(0x1f801100, 0x1f801110, timer[0]);
READ_IO(0x1f801110, 0x1f801120, timer[1]);
READ_IO(0x1f801120, 0x1f801130, timer[2]);
READ_IO(0x1f801800, 0x1f801804, cdrom);
READ_IO32(0x1f801810, 0x1f801818, gpu);
READ_IO32(0x1f801820, 0x1f801828, mdec);
READ_IO(0x1f801C00, 0x1f802000, spu);
READ_IO(0x1f802000, 0x1f804000, expansion2);
if (in_range<0xfffe0130, 4>(address) && sizeof(T) == 4) {
auto data = cacheControl->read(0);
LOG_IO(IO_LOG_ENTRY::MODE::READ, sizeof(T) * 8, address, data, cpu->PC);
return data;
}
fmt::print("[SYS] R Unhandled address at 0x{:08x}\n", address);
cpu->busError();
return 0;
}
template <typename T>
INLINE void System::writeMemory(uint32_t address, T data) {
static_assert(std::is_same<T, uint8_t>() || std::is_same<T, uint16_t>() || std::is_same<T, uint32_t>(), "Invalid type used");
if (unlikely(cpu->cop0.status.isolateCache)) {
uint32_t tag = (address & 0xfffff000) >> 12;
uint16_t index = (address & 0xffc) >> 2;
cpu->icache[index] = mips::CacheLine{tag, data};
return;
}
uint32_t addr = align_mips<T>(address);
if (in_range<RAM_BASE, RAM_SIZE_8MB>(addr)) {
return write_fast<T>(ram.data(), (addr - RAM_BASE) & (ram.size() - 1), data);
}
if (in_range<EXPANSION_BASE, EXPANSION_SIZE>(addr)) {
return write_fast<T>(expansion.data(), addr - EXPANSION_BASE, data);
}
if (in_range<SCRATCHPAD_BASE, SCRATCHPAD_SIZE>(addr)) {
return write_fast<T>(scratchpad.data(), addr - SCRATCHPAD_BASE, data);
}
WRITE_IO(0x1f801000, 0x1f801024, memoryControl);
WRITE_IO(0x1f801040, 0x1f801050, controller);
WRITE_IO(0x1f801050, 0x1f801060, serial);
WRITE_IO(0x1f801060, 0x1f801064, ramControl);
WRITE_IO(0x1f801070, 0x1f801078, interrupt);
WRITE_IO(0x1f801080, 0x1f801100, dma);
WRITE_IO(0x1f801100, 0x1f801110, timer[0]);
WRITE_IO(0x1f801110, 0x1f801120, timer[1]);
WRITE_IO(0x1f801120, 0x1f801130, timer[2]);
WRITE_IO(0x1f801800, 0x1f801804, cdrom);
WRITE_IO32(0x1f801810, 0x1f801818, gpu);
WRITE_IO32(0x1f801820, 0x1f801828, mdec);
WRITE_IO(0x1f801C00, 0x1f802000, spu);
WRITE_IO(0x1f802000, 0x1f804000, expansion2);
if (in_range<0xfffe0130, 4>(address) && sizeof(T) == 4) {
cacheControl->write(0, data);
LOG_IO(IO_LOG_ENTRY::MODE::WRITE, sizeof(T) * 8, address, data, cpu->PC);
return;
}
fmt::print("[SYS] W Unhandled address at 0x{:08x}: 0x{:02x}\n", address, data);
cpu->busError();
}
uint8_t System::readMemory8(uint32_t address) { return readMemory<uint8_t>(address); }
uint16_t System::readMemory16(uint32_t address) { return readMemory<uint16_t>(address); }
uint32_t System::readMemory32(uint32_t address) { return readMemory<uint32_t>(address); }
void System::writeMemory8(uint32_t address, uint8_t data) { writeMemory<uint8_t>(address, data); }
void System::writeMemory16(uint32_t address, uint16_t data) { writeMemory<uint16_t>(address, data); }
void System::writeMemory32(uint32_t address, uint32_t data) { writeMemory<uint32_t>(address, data); }
void System::printFunctionInfo(const char* functionNum, const bios::Function& f) {
fmt::print(" {}: {}(", functionNum, f.name);
unsigned int a = 0;
for (auto arg : f.args) {
uint32_t param = cpu->reg[4 + a];
if (true) {
fmt::print("{} = ", arg.name);
}
switch (arg.type) {
case bios::Type::INT:
case bios::Type::POINTER: fmt::print("0x{:x}", param); break;
case bios::Type::CHAR: fmt::print("'{:c}'", (char)param); break;
case bios::Type::STRING: {
fmt::print("\"");
for (int i = 0; i < 32; i++) {
uint8_t c = readMemory8(param + i);
if (c == 0) {
break;
} else if (c != 0 && i == 32 - 1) {
fmt::print("...");
} else {
fmt::print("{:c}", isprint(c) ? (char)c : '_');
}
}
fmt::print("\"");
break;
}
}
if (a < (f.args.size() - 1)) {
fmt::print(", ");
}
a++;
if (a > 4) break;
}
fmt::print(")\n");
}
void System::handleBiosFunction() {
uint32_t maskedPC = cpu->PC & 0x1FFFFF;
uint8_t functionNumber = cpu->reg[9];
bool log = biosLog;
int tableNum = (maskedPC - 0xA0) / 0x10;
if (tableNum > 2) return;
const auto& table = bios::tables[tableNum];
const auto& function = table.find(functionNumber);
if (function == table.end()) {
fmt::print(" BIOS {:1X}(0x{:02X}): Unknown function!\n", 0xA + tableNum, functionNumber);
return;
}
if (function->second.callback != nullptr) {
log = function->second.callback(this);
}
if (log) {
std::string type = fmt::format("BIOS {:1X}({:02X})", 0xA + tableNum, functionNumber);
printFunctionInfo(type.c_str(), function->second);
}
}
void System::handleSyscallFunction() {
uint8_t functionNumber = cpu->reg[4];
bool log = biosLog;
const auto& function = bios::SYSCALL.find(functionNumber);
if (function == bios::SYSCALL.end()) return;
if (function->second.callback != nullptr) {
log = function->second.callback(this);
}
if (log) {
std::string type = fmt::format("SYSCALL({:X})", functionNumber);
cpu->sys->printFunctionInfo(type.c_str(), function->second);
}
}
void System::singleStep() {
state = State::run;
cpu->executeInstructions(1);
state = State::pause;
dma->step();
cdrom->step(3);
timer[0]->step(3);
timer[1]->step(3);
timer[2]->step(3);
controller->step();
spu->step(cdrom.get());
if (gpu->emulateGpuCycles(3)) {
interrupt->trigger(interrupt::VBLANK);
}
}
void System::emulateFrame() {
#ifdef ENABLE_IO_LOG
ioLogList.clear();
#endif
cpu->gte.log.clear();
if (GpuDrawList::currentFrame == 0) {
gpu->prevVram = gpu->vram;
// Save initial state
if (gpu->gpuLogEnabled) {
gpu->gpuLogList.clear();
GpuDrawList::dumpInitialState(gpu.get());
}
}
if (++GpuDrawList::currentFrame >= GpuDrawList::framesToCapture) {
GpuDrawList::currentFrame = 0;
if (GpuDrawList::framesToCapture != 0) {
toast(fmt::format("{} frames capture complete", GpuDrawList::framesToCapture));
GpuDrawList::framesToCapture = 0;
state = State::pause;
return;
}
}
int systemCycles = 300;
for (;;) {
if (!cpu->executeInstructions(systemCycles / 3)) {
return;
}
dma->step();
cdrom->step(systemCycles / 1.5f);
timer[0]->step(systemCycles);
timer[1]->step(systemCycles);
timer[2]->step(systemCycles);
static float spuCounter = 0;
float magicNumber = 1.575f;
if (!gpu->isNtsc()) {
// Hack to prevent crackling audio on PAL games
// Note - this overclocks SPU clock, bugs might appear.
magicNumber *= 50.f / 60.f;
}
spuCounter += (float)systemCycles / magicNumber / (float)0x300;
if (spuCounter >= 1.f) {
spu->step(cdrom.get());
spuCounter -= 1.0f;
}
if (spu->bufferReady) {
spu->bufferReady = false;
Sound::appendBuffer(spu->audioBuffer.begin(), spu->audioBuffer.end());
}
controller->step();
if (gpu->emulateGpuCycles(systemCycles)) {
interrupt->trigger(interrupt::VBLANK);
return; // frame emulated
}
// TODO: Move this code to Timer class
if (gpu->gpuLine > gpu->linesPerFrame() - 20) {
auto& t = *timer[1];
if (t.mode.syncEnabled) {
using modes = device::timer::CounterMode::SyncMode1;
auto mode1 = static_cast<modes>(timer[1]->mode.syncMode);
if (mode1 == modes::resetAtVblank || mode1 == modes::resetAtVblankAndPauseOutside) {
timer[1]->current._reg = 0;
} else if (mode1 == modes::pauseUntilVblankAndFreerun) {
timer[1]->paused = false;
timer[1]->mode.syncEnabled = false;
}
}
}
// Handle Timer1 - Reset on VBlank
}
}
void System::softReset() {
// cdrom->reset();
// controller->reset();
dma->reset();
expansion2->reset();
gpu->reset();
interrupt->reset();
mdec->reset();
memoryControl->reset();
ramControl->reset();
cacheControl->reset();
// spu->reset();
serial->reset();
for (int t : {0, 1, 2}) {
// timer[t]->reset();
}
// cpu->reset();
cpu->setPC(0xBFC00000);
cpu->inBranchDelay = false;
state = State::run;
}
bool System::isSystemReady() { return biosLoaded; }
bool System::loadExeFile(const std::vector<uint8_t>& _exe) {
if (_exe.empty()) return false;
assert(_exe.size() >= 0x800);
PsxExe exe;
memcpy(&exe, _exe.data(), sizeof(exe));
if (exe.t_size > _exe.size() - 0x800) {
fmt::print("Invalid exe t_size: 0x{:08x}\n", exe.t_size);
exe.t_size = _exe.size() - 0x800;
}
for (uint32_t i = 0; i < exe.t_size; i++) {
writeMemory8(exe.t_addr + i, _exe[0x800 + i]);
}
cpu->setPC(exe.pc0);
cpu->setReg(28, exe.gp0);
if (exe.s_addr != 0) {
cpu->setReg(29, exe.s_addr + exe.s_size);
cpu->setReg(30, exe.s_addr + exe.s_size);
}
cpu->inBranchDelay = false;
return true;
}
bool System::loadBios(const std::string& path) {
const char* licenseString = "Sony Computer Entertainment Inc";
auto _bios = getFileContents(path);
if (_bios.empty()) {
fmt::print("[SYS] Cannot open BIOS {}", path);
return false;
}
assert(_bios.size() <= 512 * 1024);
if (memcmp(_bios.data() + 0x108, licenseString, strlen(licenseString)) != 0) {
fmt::print("[WARNING]: Loaded bios ({}) have invalid header, are you using correct file?\n", getFilenameExt(path));
}
std::copy(_bios.begin(), _bios.end(), bios.begin());
this->biosPath = path;
state = State::run;
biosLoaded = true;
auto patch = [&](uint32_t address, uint32_t opcode) {
address &= bios.size() - 1;
for (int i = 0; i < 4; i++) {
bios[address + i] = (opcode >> (i * 8)) & 0xff;
}
};
if (config.debug.log.system) {
fmt::print("[INFO] Patching BIOS for system log\n");
patch(0x6F0C, 0x24010001);
patch(0x6F14, 0xAF81A9C0);
}
return true;
}
bool System::loadExpansion(const std::vector<uint8_t>& _exp) {
const char* licenseString = "Licensed by Sony Computer Entertainment Inc";
if (_exp.empty()) {
return false;
}
assert(_exp.size() <= EXPANSION_SIZE);
if (memcmp(_exp.data() + 4, licenseString, strlen(licenseString)) != 0) {
fmt::print("[WARN]: Loaded expansion have invalid header, are you using correct file?\n");
}
std::copy(_exp.begin(), _exp.end(), expansion.begin());
return true;
}
void System::dumpRam() {
std::vector<uint8_t> ram(this->ram.begin(), this->ram.end());
putFileContents("ram.bin", ram);
}