mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-28 00:25:41 +00:00
560 lines
15 KiB
C++
560 lines
15 KiB
C++
#include "bus.h"
|
|
#include "cdrom.h"
|
|
#include "common/align.h"
|
|
#include "common/assert.h"
|
|
#include "common/log.h"
|
|
#include "common/state_wrapper.h"
|
|
#include "cpu_code_cache.h"
|
|
#include "cpu_core.h"
|
|
#include "cpu_disasm.h"
|
|
#include "dma.h"
|
|
#include "gpu.h"
|
|
#include "interrupt_controller.h"
|
|
#include "mdec.h"
|
|
#include "pad.h"
|
|
#include "sio.h"
|
|
#include "spu.h"
|
|
#include "timers.h"
|
|
#include <cstdio>
|
|
Log_SetChannel(Bus);
|
|
|
|
#define FIXUP_WORD_READ_OFFSET(offset) ((offset) & ~u32(3))
|
|
#define FIXUP_WORD_READ_VALUE(offset, value) ((value) >> (((offset)&u32(3)) * 8))
|
|
|
|
// Offset and value remapping for (w32) registers from nocash docs.
|
|
void FixupUnalignedWordAccessW32(u32& offset, u32& value)
|
|
{
|
|
const u32 byte_offset = offset & u32(3);
|
|
offset &= ~u32(3);
|
|
value <<= byte_offset * 8;
|
|
}
|
|
|
|
Bus::Bus() = default;
|
|
|
|
Bus::~Bus() = default;
|
|
|
|
void Bus::Initialize(CPU::Core* cpu, CPU::CodeCache* cpu_code_cache, DMA* dma,
|
|
InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad, Timers* timers,
|
|
SPU* spu, MDEC* mdec, SIO* sio)
|
|
{
|
|
m_cpu = cpu;
|
|
m_cpu_code_cache = cpu_code_cache;
|
|
m_dma = dma;
|
|
m_interrupt_controller = interrupt_controller;
|
|
m_gpu = gpu;
|
|
m_cdrom = cdrom;
|
|
m_pad = pad;
|
|
m_timers = timers;
|
|
m_spu = spu;
|
|
m_mdec = mdec;
|
|
m_sio = sio;
|
|
}
|
|
|
|
void Bus::Reset()
|
|
{
|
|
m_ram.fill(static_cast<u8>(0));
|
|
m_MEMCTRL.exp1_base = 0x1F000000;
|
|
m_MEMCTRL.exp2_base = 0x1F802000;
|
|
m_MEMCTRL.exp1_delay_size.bits = 0x0013243F;
|
|
m_MEMCTRL.exp3_delay_size.bits = 0x00003022;
|
|
m_MEMCTRL.bios_delay_size.bits = 0x0013243F;
|
|
m_MEMCTRL.spu_delay_size.bits = 0x200931E1;
|
|
m_MEMCTRL.cdrom_delay_size.bits = 0x00020843;
|
|
m_MEMCTRL.exp2_delay_size.bits = 0x00070777;
|
|
m_MEMCTRL.common_delay.bits = 0x00031125;
|
|
m_ram_size_reg = UINT32_C(0x00000B88);
|
|
RecalculateMemoryTimings();
|
|
}
|
|
|
|
bool Bus::DoState(StateWrapper& sw)
|
|
{
|
|
sw.Do(&m_exp1_access_time);
|
|
sw.Do(&m_exp2_access_time);
|
|
sw.Do(&m_bios_access_time);
|
|
sw.Do(&m_cdrom_access_time);
|
|
sw.Do(&m_spu_access_time);
|
|
sw.DoBytes(m_ram.data(), m_ram.size());
|
|
sw.DoBytes(m_bios.data(), m_bios.size());
|
|
sw.DoArray(m_MEMCTRL.regs, countof(m_MEMCTRL.regs));
|
|
sw.Do(&m_ram_size_reg);
|
|
sw.Do(&m_tty_line_buffer);
|
|
return !sw.HasError();
|
|
}
|
|
|
|
bool Bus::ReadByte(PhysicalMemoryAddress address, u8* value)
|
|
{
|
|
u32 temp = 0;
|
|
const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
|
|
*value = Truncate8(temp);
|
|
return result;
|
|
}
|
|
|
|
bool Bus::ReadHalfWord(PhysicalMemoryAddress address, u16* value)
|
|
{
|
|
u32 temp = 0;
|
|
const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
|
|
*value = Truncate16(temp);
|
|
return result;
|
|
}
|
|
|
|
bool Bus::ReadWord(PhysicalMemoryAddress address, u32* value)
|
|
{
|
|
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, *value);
|
|
}
|
|
|
|
bool Bus::WriteByte(PhysicalMemoryAddress address, u8 value)
|
|
{
|
|
u32 temp = ZeroExtend32(value);
|
|
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
|
|
}
|
|
|
|
bool Bus::WriteHalfWord(PhysicalMemoryAddress address, u16 value)
|
|
{
|
|
u32 temp = ZeroExtend32(value);
|
|
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
|
|
}
|
|
|
|
bool Bus::WriteWord(PhysicalMemoryAddress address, u32 value)
|
|
{
|
|
return DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
|
|
}
|
|
|
|
TickCount Bus::ReadWords(PhysicalMemoryAddress address, u32* words, u32 word_count)
|
|
{
|
|
if (address + (word_count * sizeof(u32)) > (RAM_BASE + RAM_SIZE))
|
|
{
|
|
// Not RAM, or RAM mirrors.
|
|
TickCount total_ticks = 0;
|
|
for (u32 i = 0; i < word_count; i++)
|
|
{
|
|
const TickCount ticks = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, words[i]);
|
|
if (ticks < 0)
|
|
return -1;
|
|
|
|
total_ticks += ticks;
|
|
address += sizeof(u32);
|
|
}
|
|
|
|
return total_ticks;
|
|
}
|
|
|
|
// DMA is using DRAM Hyper Page mode, allowing it to access DRAM rows at 1 clock cycle per word (effectively around 17
|
|
// clks per 16 words, due to required row address loading, probably plus some further minimal overload due to refresh
|
|
// cycles). This is making DMA much faster than CPU memory accesses (CPU DRAM access takes 1 opcode cycle plus 6
|
|
// waitstates, ie. 7 cycles in total).
|
|
std::memcpy(words, &m_ram[address], sizeof(u32) * word_count);
|
|
return static_cast<TickCount>(word_count + ((word_count + 15) / 16));
|
|
}
|
|
|
|
TickCount Bus::WriteWords(PhysicalMemoryAddress address, const u32* words, u32 word_count)
|
|
{
|
|
if (address + (word_count * sizeof(u32)) > (RAM_BASE + RAM_SIZE))
|
|
{
|
|
// Not RAM, or RAM mirrors.
|
|
TickCount total_ticks = 0;
|
|
for (u32 i = 0; i < word_count; i++)
|
|
{
|
|
u32 value = words[i];
|
|
const TickCount ticks = DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
|
|
if (ticks < 0)
|
|
return -1;
|
|
|
|
total_ticks += ticks;
|
|
address += sizeof(u32);
|
|
}
|
|
|
|
return total_ticks;
|
|
}
|
|
|
|
const u32 start_page = address / CPU_CODE_CACHE_PAGE_SIZE;
|
|
const u32 end_page = (address + word_count * sizeof(u32)) / CPU_CODE_CACHE_PAGE_SIZE;
|
|
for (u32 page = start_page; page <= end_page; page++)
|
|
{
|
|
if (m_ram_code_bits[page])
|
|
DoInvalidateCodeCache(page);
|
|
}
|
|
|
|
std::memcpy(&m_ram[address], words, sizeof(u32) * word_count);
|
|
return static_cast<TickCount>(word_count + ((word_count + 15) / 16));
|
|
}
|
|
|
|
void Bus::SetExpansionROM(std::vector<u8> data)
|
|
{
|
|
m_exp1_rom = std::move(data);
|
|
}
|
|
|
|
void Bus::SetBIOS(const std::vector<u8>& image)
|
|
{
|
|
if (image.size() != static_cast<u32>(BIOS_SIZE))
|
|
{
|
|
Panic("Incorrect BIOS image size");
|
|
return;
|
|
}
|
|
|
|
std::copy(image.cbegin(), image.cend(), m_bios.begin());
|
|
}
|
|
|
|
std::tuple<TickCount, TickCount, TickCount> Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay)
|
|
{
|
|
// from nocash spec
|
|
s32 first = 0, seq = 0, min = 0;
|
|
if (mem_delay.use_com0_time)
|
|
{
|
|
first += s32(common_delay.com0) - 1;
|
|
seq += s32(common_delay.com0) - 1;
|
|
}
|
|
if (mem_delay.use_com2_time)
|
|
{
|
|
first += s32(common_delay.com2);
|
|
seq += s32(common_delay.com2);
|
|
}
|
|
if (mem_delay.use_com3_time)
|
|
{
|
|
min = s32(common_delay.com3);
|
|
}
|
|
if (first < 6)
|
|
first++;
|
|
|
|
first = first + s32(mem_delay.access_time) + 2;
|
|
seq = seq + s32(mem_delay.access_time) + 2;
|
|
|
|
if (first < (min + 6))
|
|
first = min + 6;
|
|
if (seq < (min + 2))
|
|
seq = min + 2;
|
|
|
|
const TickCount byte_access_time = first;
|
|
const TickCount halfword_access_time = mem_delay.data_bus_16bit ? first : (first + seq);
|
|
const TickCount word_access_time = mem_delay.data_bus_16bit ? (first + seq) : (first + seq + seq + seq);
|
|
return std::tie(std::max(byte_access_time - 1, 0), std::max(halfword_access_time - 1, 0),
|
|
std::max(word_access_time - 1, 0));
|
|
}
|
|
|
|
void Bus::RecalculateMemoryTimings()
|
|
{
|
|
std::tie(m_bios_access_time[0], m_bios_access_time[1], m_bios_access_time[2]) =
|
|
CalculateMemoryTiming(m_MEMCTRL.bios_delay_size, m_MEMCTRL.common_delay);
|
|
std::tie(m_cdrom_access_time[0], m_cdrom_access_time[1], m_cdrom_access_time[2]) =
|
|
CalculateMemoryTiming(m_MEMCTRL.cdrom_delay_size, m_MEMCTRL.common_delay);
|
|
std::tie(m_spu_access_time[0], m_spu_access_time[1], m_spu_access_time[2]) =
|
|
CalculateMemoryTiming(m_MEMCTRL.spu_delay_size, m_MEMCTRL.common_delay);
|
|
|
|
Log_TracePrintf("BIOS Memory Timing: %u bit bus, byte=%d, halfword=%d, word=%d",
|
|
m_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, m_bios_access_time[0] + 1,
|
|
m_bios_access_time[1] + 1, m_bios_access_time[2] + 1);
|
|
Log_TracePrintf("CDROM Memory Timing: %u bit bus, byte=%d, halfword=%d, word=%d",
|
|
m_MEMCTRL.cdrom_delay_size.data_bus_16bit ? 16 : 8, m_cdrom_access_time[0] + 1,
|
|
m_cdrom_access_time[1] + 1, m_cdrom_access_time[2] + 1);
|
|
Log_TracePrintf("SPU Memory Timing: %u bit bus, byte=%d, halfword=%d, word=%d",
|
|
m_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, m_spu_access_time[0] + 1, m_spu_access_time[1] + 1,
|
|
m_spu_access_time[2] + 1);
|
|
}
|
|
|
|
TickCount Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, u32& value)
|
|
{
|
|
SmallString str;
|
|
str.AppendString("Invalid bus ");
|
|
if (size == MemoryAccessSize::Byte)
|
|
str.AppendString("byte");
|
|
if (size == MemoryAccessSize::HalfWord)
|
|
str.AppendString("word");
|
|
if (size == MemoryAccessSize::Word)
|
|
str.AppendString("dword");
|
|
str.AppendCharacter(' ');
|
|
if (type == MemoryAccessType::Read)
|
|
str.AppendString("read");
|
|
else
|
|
str.AppendString("write");
|
|
|
|
str.AppendFormattedString(" at address 0x%08X", address);
|
|
if (type == MemoryAccessType::Write)
|
|
str.AppendFormattedString(" (value 0x%08X)", value);
|
|
|
|
Log_ErrorPrint(str);
|
|
if (type == MemoryAccessType::Read)
|
|
value = UINT32_C(0xFFFFFFFF);
|
|
|
|
return 1;
|
|
}
|
|
|
|
u32 Bus::DoReadEXP1(MemoryAccessSize size, u32 offset)
|
|
{
|
|
if (m_exp1_rom.empty())
|
|
{
|
|
// EXP1 not present.
|
|
return UINT32_C(0xFFFFFFFF);
|
|
}
|
|
|
|
if (offset == 0x20018)
|
|
{
|
|
// Bit 0 - Action Replay On/Off
|
|
return UINT32_C(1);
|
|
}
|
|
|
|
const u32 transfer_size = u32(1) << static_cast<u32>(size);
|
|
if ((offset + transfer_size) > m_exp1_rom.size())
|
|
{
|
|
return UINT32_C(0);
|
|
}
|
|
|
|
u32 value;
|
|
if (size == MemoryAccessSize::Byte)
|
|
{
|
|
value = ZeroExtend32(m_exp1_rom[offset]);
|
|
}
|
|
else if (size == MemoryAccessSize::HalfWord)
|
|
{
|
|
u16 halfword;
|
|
std::memcpy(&halfword, &m_exp1_rom[offset], sizeof(halfword));
|
|
value = ZeroExtend32(halfword);
|
|
}
|
|
else
|
|
{
|
|
std::memcpy(&value, &m_exp1_rom[offset], sizeof(value));
|
|
}
|
|
|
|
// Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", EXP1_BASE | offset, value);
|
|
return value;
|
|
}
|
|
|
|
void Bus::DoWriteEXP1(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
Log_WarningPrintf("EXP1 write: 0x%08X <- 0x%08X", EXP1_BASE | offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadEXP2(MemoryAccessSize size, u32 offset)
|
|
{
|
|
// rx/tx buffer empty
|
|
if (offset == 0x21)
|
|
{
|
|
return 0x04 | 0x08;
|
|
}
|
|
|
|
Log_WarningPrintf("EXP2 read: 0x%08X", EXP2_BASE | offset);
|
|
return UINT32_C(0xFFFFFFFF);
|
|
}
|
|
|
|
void Bus::DoWriteEXP2(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
if (offset == 0x23)
|
|
{
|
|
if (value == '\r')
|
|
return;
|
|
|
|
if (value == '\n')
|
|
{
|
|
if (!m_tty_line_buffer.empty())
|
|
{
|
|
Log_InfoPrintf("TTY: %s", m_tty_line_buffer.c_str());
|
|
#ifdef _DEBUG
|
|
if (CPU::LOG_EXECUTION)
|
|
CPU::WriteToExecutionLog("TTY: %s\n", m_tty_line_buffer.c_str());
|
|
#endif
|
|
}
|
|
m_tty_line_buffer.clear();
|
|
}
|
|
else
|
|
{
|
|
m_tty_line_buffer += static_cast<char>(Truncate8(value));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (offset == 0x41)
|
|
{
|
|
Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F));
|
|
return;
|
|
}
|
|
|
|
Log_WarningPrintf("EXP2 write: 0x%08X <- 0x%08X", EXP2_BASE | offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadMemoryControl(MemoryAccessSize size, u32 offset)
|
|
{
|
|
u32 value = m_MEMCTRL.regs[offset / 4];
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
return value;
|
|
}
|
|
|
|
void Bus::DoWriteMemoryControl(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
|
|
const u32 index = offset / 4;
|
|
const u32 write_mask = (index == 8) ? COMDELAY::WRITE_MASK : MEMDELAY::WRITE_MASK;
|
|
const u32 new_value = (m_MEMCTRL.regs[index] & ~write_mask) | (value & write_mask);
|
|
if (m_MEMCTRL.regs[index] != new_value)
|
|
{
|
|
m_MEMCTRL.regs[index] = new_value;
|
|
RecalculateMemoryTimings();
|
|
}
|
|
}
|
|
|
|
u32 Bus::DoReadMemoryControl2(MemoryAccessSize size, u32 offset)
|
|
{
|
|
if (offset == 0x00)
|
|
return m_ram_size_reg;
|
|
|
|
u32 value = 0;
|
|
DoInvalidAccess(MemoryAccessType::Read, size, MEMCTRL2_BASE | offset, value);
|
|
return value;
|
|
}
|
|
|
|
void Bus::DoWriteMemoryControl2(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
if (offset == 0x00)
|
|
{
|
|
m_ram_size_reg = value;
|
|
return;
|
|
}
|
|
|
|
DoInvalidAccess(MemoryAccessType::Write, size, MEMCTRL2_BASE | offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadPad(MemoryAccessSize size, u32 offset)
|
|
{
|
|
return m_pad->ReadRegister(offset);
|
|
}
|
|
|
|
void Bus::DoWritePad(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
m_pad->WriteRegister(offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadSIO(MemoryAccessSize size, u32 offset)
|
|
{
|
|
return m_sio->ReadRegister(offset);
|
|
}
|
|
|
|
void Bus::DoWriteSIO(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
m_sio->WriteRegister(offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadCDROM(MemoryAccessSize size, u32 offset)
|
|
{
|
|
// TODO: Splitting of half/word reads.
|
|
Assert(size == MemoryAccessSize::Byte);
|
|
return ZeroExtend32(m_cdrom->ReadRegister(offset));
|
|
}
|
|
|
|
void Bus::DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
// TODO: Splitting of half/word reads.
|
|
Assert(size == MemoryAccessSize::Byte);
|
|
m_cdrom->WriteRegister(offset, Truncate8(value));
|
|
}
|
|
|
|
u32 Bus::DoReadGPU(MemoryAccessSize size, u32 offset)
|
|
{
|
|
Assert(size == MemoryAccessSize::Word);
|
|
return m_gpu->ReadRegister(offset);
|
|
}
|
|
|
|
void Bus::DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
Assert(size == MemoryAccessSize::Word);
|
|
m_gpu->WriteRegister(offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadMDEC(MemoryAccessSize size, u32 offset)
|
|
{
|
|
Assert(size == MemoryAccessSize::Word);
|
|
return m_mdec->ReadRegister(offset);
|
|
}
|
|
|
|
void Bus::DoWriteMDEC(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
Assert(size == MemoryAccessSize::Word);
|
|
m_mdec->WriteRegister(offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadInterruptController(MemoryAccessSize size, u32 offset)
|
|
{
|
|
u32 value = m_interrupt_controller->ReadRegister(offset);
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
return value;
|
|
}
|
|
|
|
void Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
m_interrupt_controller->WriteRegister(offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadTimers(MemoryAccessSize size, u32 offset)
|
|
{
|
|
u32 value = m_timers->ReadRegister(offset);
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
return value;
|
|
}
|
|
|
|
void Bus::DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
m_timers->WriteRegister(offset, value);
|
|
}
|
|
|
|
u32 Bus::DoReadSPU(MemoryAccessSize size, u32 offset)
|
|
{
|
|
// 32-bit reads are read as two 16-bit accesses.
|
|
if (size == MemoryAccessSize::Word)
|
|
{
|
|
const u16 lsb = m_spu->ReadRegister(offset);
|
|
const u16 msb = m_spu->ReadRegister(offset + 2);
|
|
return ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16);
|
|
}
|
|
else
|
|
{
|
|
return ZeroExtend32(m_spu->ReadRegister(offset));
|
|
}
|
|
}
|
|
|
|
void Bus::DoWriteSPU(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
// 32-bit writes are written as two 16-bit writes.
|
|
// TODO: Ignore if address is not aligned.
|
|
if (size == MemoryAccessSize::Word)
|
|
{
|
|
Assert(Common::IsAlignedPow2(offset, 2));
|
|
m_spu->WriteRegister(offset, Truncate16(value));
|
|
m_spu->WriteRegister(offset + 2, Truncate16(value >> 16));
|
|
return;
|
|
}
|
|
|
|
Assert(Common::IsAlignedPow2(offset, 2));
|
|
m_spu->WriteRegister(offset, Truncate16(value));
|
|
}
|
|
|
|
void Bus::DoInvalidateCodeCache(u32 page_index)
|
|
{
|
|
m_cpu_code_cache->InvalidateBlocksWithPageIndex(page_index);
|
|
}
|
|
|
|
u32 Bus::DoReadDMA(MemoryAccessSize size, u32 offset)
|
|
{
|
|
return FIXUP_WORD_READ_VALUE(offset, m_dma->ReadRegister(FIXUP_WORD_READ_OFFSET(offset)));
|
|
}
|
|
|
|
void Bus::DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value)
|
|
{
|
|
switch (size)
|
|
{
|
|
case MemoryAccessSize::Byte:
|
|
case MemoryAccessSize::HalfWord:
|
|
{
|
|
// zero extend length register
|
|
if ((offset & u32(0xF0)) < 7 && (offset & u32(0x0F)) == 0x4)
|
|
value = ZeroExtend32(value);
|
|
else
|
|
FixupUnalignedWordAccessW32(offset, value);
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
m_dma->WriteRegister(offset, value);
|
|
}
|