mirror of https://github.com/inolen/redream.git
massive refactoring, replaced Emulator class with Dreamcast class. instead of passing around individual components to each system, the new Dreamcast object has public getters for each (holly, pvr, video ram, etc.)
This commit is contained in:
parent
bb5b03919d
commit
ecc0a6c7ac
|
@ -124,10 +124,9 @@ set(DREAVM_SOURCES
|
|||
src/cpu/ir/passes/register_allocation_pass.cc
|
||||
src/cpu/ir/passes/validate_pass.cc
|
||||
src/cpu/sh4.cc
|
||||
src/cpu/sh4_context.cc
|
||||
src/cpu/runtime.cc
|
||||
src/emu/disc.cc
|
||||
src/emu/emulator.cc
|
||||
src/emu/dreamcast.cc
|
||||
src/emu/profiler.cc
|
||||
src/emu/scheduler.cc
|
||||
src/holly/gdrom.cc
|
||||
|
@ -281,36 +280,14 @@ endif()
|
|||
|
||||
# build test binary
|
||||
set(DREAVM_TEST_SOURCES
|
||||
src/core/assert.cc
|
||||
src/core/filesystem.cc
|
||||
src/core/log.cc
|
||||
src/cpu/backend/interpreter/interpreter_backend.cc
|
||||
src/cpu/backend/interpreter/interpreter_block.cc
|
||||
src/cpu/backend/interpreter/interpreter_callbacks.cc
|
||||
src/cpu/backend/x64/x64_backend.cc
|
||||
src/cpu/backend/x64/x64_block.cc
|
||||
src/cpu/backend/x64/x64_emitter.cc
|
||||
src/cpu/frontend/sh4/sh4_builder.cc
|
||||
src/cpu/frontend/sh4/sh4_emit.cc
|
||||
src/cpu/frontend/sh4/sh4_frontend.cc
|
||||
src/cpu/frontend/sh4/sh4_instr.cc
|
||||
src/cpu/ir/ir_builder.cc
|
||||
src/cpu/ir/passes/constant_propagation_pass.cc
|
||||
src/cpu/ir/passes/context_promotion_pass.cc
|
||||
src/cpu/ir/passes/control_flow_analysis_pass.cc
|
||||
src/cpu/ir/passes/pass_runner.cc
|
||||
src/cpu/ir/passes/register_allocation_pass.cc
|
||||
src/cpu/ir/passes/validate_pass.cc
|
||||
src/cpu/sh4.cc
|
||||
src/cpu/sh4_context.cc
|
||||
src/cpu/runtime.cc
|
||||
src/emu/profiler.cc
|
||||
${DREAVM_SOURCES}
|
||||
test/sh4_test.cc
|
||||
test/test_intrusive_list.cc
|
||||
test/test_memory.cc
|
||||
#test/test_memory.cc
|
||||
test/test_ring_buffer.cc
|
||||
test/test_sh4.cc
|
||||
${asm_inc})
|
||||
list(REMOVE_ITEM DREAVM_TEST_SOURCES src/main.cc)
|
||||
|
||||
# assign source groups for visual studio projects
|
||||
source_group_by_dir(DREAVM_TEST_SOURCES)
|
||||
|
|
|
@ -25,7 +25,6 @@ class Backend {
|
|||
virtual const Register *registers() const = 0;
|
||||
virtual int num_registers() const = 0;
|
||||
|
||||
virtual bool Init() = 0;
|
||||
virtual void Reset() = 0;
|
||||
virtual bool AssembleBlock(ir::IRBuilder &builder, RuntimeBlock *block) = 0;
|
||||
|
||||
|
|
|
@ -75,8 +75,6 @@ int InterpreterBackend::num_registers() const {
|
|||
return sizeof(int_registers) / sizeof(Register);
|
||||
}
|
||||
|
||||
bool InterpreterBackend::Init() { return true; }
|
||||
|
||||
void InterpreterBackend::Reset() { codegen_ = codegen_begin_; }
|
||||
|
||||
bool InterpreterBackend::AssembleBlock(IRBuilder &builder,
|
||||
|
|
|
@ -87,7 +87,6 @@ class InterpreterBackend : public Backend {
|
|||
const Register *registers() const;
|
||||
int num_registers() const;
|
||||
|
||||
bool Init();
|
||||
void Reset();
|
||||
bool AssembleBlock(ir::IRBuilder &builder, RuntimeBlock *block);
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ int X64Backend::num_registers() const {
|
|||
return sizeof(x64_registers) / sizeof(Register);
|
||||
}
|
||||
|
||||
bool X64Backend::Init() { return true; }
|
||||
|
||||
void X64Backend::Reset() { codegen_.reset(); }
|
||||
|
||||
bool X64Backend::AssembleBlock(IRBuilder &builder, RuntimeBlock *block) {
|
||||
|
|
|
@ -32,7 +32,6 @@ class X64Backend : public Backend {
|
|||
const Register *registers() const;
|
||||
int num_registers() const;
|
||||
|
||||
bool Init();
|
||||
void Reset();
|
||||
bool AssembleBlock(ir::IRBuilder &builder, RuntimeBlock *block);
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ class Frontend {
|
|||
Frontend(emu::Memory &memory) : memory_(memory) {}
|
||||
virtual ~Frontend() {}
|
||||
|
||||
virtual bool Init() = 0;
|
||||
virtual std::unique_ptr<ir::IRBuilder> BuildBlock(uint32_t addr,
|
||||
const void *guest_ctx) = 0;
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ Value *SH4Builder::LoadSR() {
|
|||
void SH4Builder::StoreSR(Value *v) {
|
||||
CHECK_EQ(v->type(), VALUE_I32);
|
||||
StoreAndPreserveContext(offsetof(SH4Context, sr), v, IF_INVALIDATE_CONTEXT);
|
||||
CallExternal((ExternalFn)&SRUpdated);
|
||||
CallExternal((ExternalFn)&SH4Context::SRUpdated);
|
||||
}
|
||||
|
||||
ir::Value *SH4Builder::LoadT() { return And(LoadSR(), AllocConstant(T)); }
|
||||
|
@ -144,7 +144,7 @@ void SH4Builder::StoreFPSCR(ir::Value *v) {
|
|||
CHECK_EQ(v->type(), VALUE_I32);
|
||||
v = And(v, AllocConstant(0x003fffff));
|
||||
StoreAndPreserveContext(offsetof(SH4Context, fpscr), v);
|
||||
CallExternal((ExternalFn)&FPSCRUpdated);
|
||||
CallExternal((ExternalFn)&SH4Context::FPSCRUpdated);
|
||||
}
|
||||
|
||||
ir::Value *SH4Builder::LoadPR() {
|
||||
|
|
|
@ -7,8 +7,6 @@ using namespace dreavm::emu;
|
|||
|
||||
SH4Frontend::SH4Frontend(Memory &memory) : Frontend(memory) {}
|
||||
|
||||
bool SH4Frontend::Init() { return true; }
|
||||
|
||||
std::unique_ptr<IRBuilder> SH4Frontend::BuildBlock(uint32_t addr,
|
||||
const void *guest_ctx) {
|
||||
std::unique_ptr<SH4Builder> builder(new SH4Builder(memory_));
|
||||
|
|
|
@ -15,7 +15,6 @@ class SH4Frontend : public Frontend {
|
|||
public:
|
||||
SH4Frontend(emu::Memory &memory);
|
||||
|
||||
bool Init();
|
||||
std::unique_ptr<ir::IRBuilder> BuildBlock(uint32_t addr,
|
||||
const void *guest_ctx);
|
||||
};
|
||||
|
|
|
@ -27,38 +27,24 @@ static inline uint32_t BlockOffset(uint32_t addr) {
|
|||
return (addr & BLOCK_ADDR_MASK) >> BLOCK_ADDR_SHIFT;
|
||||
}
|
||||
|
||||
Runtime::Runtime(Memory &memory)
|
||||
Runtime::Runtime(Memory &memory, frontend::Frontend &frontend,
|
||||
backend::Backend &backend)
|
||||
: memory_(memory),
|
||||
frontend_(nullptr),
|
||||
backend_(nullptr),
|
||||
frontend_(frontend),
|
||||
backend_(backend),
|
||||
pending_reset_(false) {
|
||||
blocks_ = new RuntimeBlock[MAX_BLOCKS];
|
||||
}
|
||||
|
||||
Runtime::~Runtime() { delete[] blocks_; }
|
||||
|
||||
bool Runtime::Init(frontend::Frontend *frontend, backend::Backend *backend) {
|
||||
frontend_ = frontend;
|
||||
backend_ = backend;
|
||||
|
||||
if (!frontend_->Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!backend_->Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pass_runner_.AddPass(std::unique_ptr<Pass>(new ValidatePass()));
|
||||
pass_runner_.AddPass(std::unique_ptr<Pass>(new ControlFlowAnalysisPass()));
|
||||
pass_runner_.AddPass(std::unique_ptr<Pass>(new ContextPromotionPass()));
|
||||
pass_runner_.AddPass(std::unique_ptr<Pass>(new ConstantPropagationPass()));
|
||||
pass_runner_.AddPass(
|
||||
std::unique_ptr<Pass>(new RegisterAllocationPass(*backend_)));
|
||||
|
||||
return true;
|
||||
std::unique_ptr<Pass>(new RegisterAllocationPass(backend_)));
|
||||
}
|
||||
|
||||
Runtime::~Runtime() { delete[] blocks_; }
|
||||
|
||||
// TODO should the block caching be part of the frontend?
|
||||
// this way, the SH4Frontend can cache based on FPU state
|
||||
RuntimeBlock *Runtime::GetBlock(uint32_t addr, const void *guest_ctx) {
|
||||
|
@ -86,7 +72,7 @@ RuntimeBlock *Runtime::GetBlock(uint32_t addr, const void *guest_ctx) {
|
|||
void Runtime::QueueResetBlocks() { pending_reset_ = true; }
|
||||
|
||||
void Runtime::ResetBlocks() {
|
||||
backend_->Reset();
|
||||
backend_.Reset();
|
||||
|
||||
memset(blocks_, 0, sizeof(RuntimeBlock) * MAX_BLOCKS);
|
||||
}
|
||||
|
@ -95,12 +81,12 @@ void Runtime::CompileBlock(uint32_t addr, const void *guest_ctx,
|
|||
RuntimeBlock *block) {
|
||||
PROFILER_RUNTIME("Runtime::CompileBlock");
|
||||
|
||||
std::unique_ptr<IRBuilder> builder = frontend_->BuildBlock(addr, guest_ctx);
|
||||
std::unique_ptr<IRBuilder> builder = frontend_.BuildBlock(addr, guest_ctx);
|
||||
|
||||
// run optimization passes
|
||||
pass_runner_.Run(*builder);
|
||||
|
||||
if (!backend_->AssembleBlock(*builder, block)) {
|
||||
if (!backend_.AssembleBlock(*builder, block)) {
|
||||
LOG_INFO("Assembler overflow, resetting block cache");
|
||||
|
||||
// the backend overflowed, reset the block cache
|
||||
|
@ -108,7 +94,7 @@ void Runtime::CompileBlock(uint32_t addr, const void *guest_ctx,
|
|||
|
||||
// if the backend fails to assemble on an empty cache, there's nothing to be
|
||||
// done
|
||||
CHECK(backend_->AssembleBlock(*builder, block),
|
||||
CHECK(backend_.AssembleBlock(*builder, block),
|
||||
"Backend assembler buffer overflow");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ struct RuntimeBlock {
|
|||
|
||||
class Runtime {
|
||||
public:
|
||||
Runtime(emu::Memory &memory);
|
||||
Runtime(emu::Memory &memory, frontend::Frontend &frontend,
|
||||
backend::Backend &backend);
|
||||
~Runtime();
|
||||
|
||||
emu::Memory &memory() { return memory_; }
|
||||
|
||||
bool Init(frontend::Frontend *frontend, backend::Backend *backend);
|
||||
RuntimeBlock *GetBlock(uint32_t addr, const void *guest_ctx);
|
||||
void QueueResetBlocks();
|
||||
|
||||
|
@ -41,8 +41,8 @@ class Runtime {
|
|||
void CompileBlock(uint32_t addr, const void *guest_ctx, RuntimeBlock *block);
|
||||
|
||||
emu::Memory &memory_;
|
||||
frontend::Frontend *frontend_;
|
||||
backend::Backend *backend_;
|
||||
frontend::Frontend &frontend_;
|
||||
backend::Backend &backend_;
|
||||
ir::passes::PassRunner pass_runner_;
|
||||
RuntimeBlock *blocks_;
|
||||
bool pending_reset_;
|
||||
|
|
312
src/cpu/sh4.cc
312
src/cpu/sh4.cc
|
@ -1,6 +1,7 @@
|
|||
#include "core/core.h"
|
||||
#include "cpu/runtime.h"
|
||||
#include "cpu/sh4.h"
|
||||
#include "emu/memory.h"
|
||||
#include "emu/profiler.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
|
@ -15,25 +16,92 @@ InterruptInfo interrupts[NUM_INTERRUPTS] = {
|
|||
#undef SH4_INT
|
||||
};
|
||||
|
||||
SH4::SH4(Memory &memory) : memory_(memory) {}
|
||||
|
||||
SH4::~SH4() {}
|
||||
|
||||
bool SH4::Init(Runtime *runtime) {
|
||||
runtime_ = runtime;
|
||||
|
||||
InitMemory();
|
||||
Reset();
|
||||
|
||||
return true;
|
||||
static void SetRegisterBank(SH4Context *ctx, int bank) {
|
||||
if (bank == 0) {
|
||||
for (int s = 0; s < 8; s++) {
|
||||
ctx->rbnk[1][s] = ctx->r[s];
|
||||
ctx->r[s] = ctx->rbnk[0][s];
|
||||
}
|
||||
} else {
|
||||
for (int s = 0; s < 8; s++) {
|
||||
ctx->rbnk[0][s] = ctx->r[s];
|
||||
ctx->r[s] = ctx->rbnk[1][s];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SH4::Reset(uint32_t pc) {
|
||||
Reset();
|
||||
static void SwapFPRegisters(SH4Context *ctx) {
|
||||
uint32_t z;
|
||||
|
||||
ctx_.pc = pc;
|
||||
for (int s = 0; s <= 15; s++) {
|
||||
z = ctx->fr[s];
|
||||
ctx->fr[s] = ctx->xf[s];
|
||||
ctx->xf[s] = z;
|
||||
}
|
||||
}
|
||||
|
||||
static void SwapFPCouples(SH4Context *ctx) {
|
||||
uint32_t z;
|
||||
|
||||
for (int s = 0; s <= 15; s = s + 2) {
|
||||
z = ctx->fr[s];
|
||||
ctx->fr[s] = ctx->fr[s + 1];
|
||||
ctx->fr[s + 1] = z;
|
||||
|
||||
z = ctx->xf[s];
|
||||
ctx->xf[s] = ctx->xf[s + 1];
|
||||
ctx->xf[s + 1] = z;
|
||||
}
|
||||
}
|
||||
|
||||
void SH4Context::SRUpdated(SH4Context *ctx) {
|
||||
if (ctx->sr.RB != ctx->old_sr.RB) {
|
||||
SetRegisterBank(ctx, ctx->sr.RB ? 1 : 0);
|
||||
}
|
||||
|
||||
ctx->old_sr = ctx->sr;
|
||||
}
|
||||
|
||||
void SH4Context::FPSCRUpdated(SH4Context *ctx) {
|
||||
if (ctx->fpscr.FR != ctx->old_fpscr.FR) {
|
||||
SwapFPRegisters(ctx);
|
||||
}
|
||||
|
||||
if (ctx->fpscr.PR != ctx->old_fpscr.PR) {
|
||||
SwapFPCouples(ctx);
|
||||
}
|
||||
|
||||
ctx->old_fpscr = ctx->fpscr;
|
||||
}
|
||||
|
||||
SH4::SH4(Memory &memory, Runtime &runtime)
|
||||
: memory_(memory),
|
||||
runtime_(runtime),
|
||||
requested_interrupts_(0),
|
||||
pending_interrupts_(0) {}
|
||||
|
||||
void SH4::Init() {
|
||||
memset(&ctx_, 0, sizeof(ctx_));
|
||||
ctx_.pc = 0xa0000000;
|
||||
ctx_.pr = 0xdeadbeef;
|
||||
ctx_.sr.full = ctx_.old_sr.full = 0x700000f0;
|
||||
ctx_.fpscr.full = ctx_.old_fpscr.full = 0x00040001;
|
||||
|
||||
memset(area7_, 0, sizeof(area7_));
|
||||
#define SH4_REG(addr, name, flags, default, reset, sleep, standby, type) \
|
||||
if (default != HELD) { \
|
||||
*(uint32_t *)&area7_[name##_OFFSET] = default; \
|
||||
}
|
||||
#include "cpu/sh4_regs.inc"
|
||||
#undef SH4_REG
|
||||
|
||||
memset(cache_, 0, sizeof(cache_));
|
||||
|
||||
ReprioritizeInterrupts();
|
||||
}
|
||||
|
||||
void SH4::SetPC(uint32_t pc) { ctx_.pc = pc; }
|
||||
|
||||
uint32_t SH4::Execute(uint32_t cycles) {
|
||||
PROFILER_RUNTIME("SH4::Execute");
|
||||
|
||||
|
@ -48,9 +116,8 @@ uint32_t SH4::Execute(uint32_t cycles) {
|
|||
}
|
||||
|
||||
while (ctx_.pc != 0xdeadbeef) {
|
||||
// translate PC to 29-bit physical space
|
||||
uint32_t pc = ctx_.pc & ~MIRROR_MASK;
|
||||
RuntimeBlock *block = runtime_->GetBlock(pc, &ctx_);
|
||||
uint32_t pc = ctx_.pc & ADDR_MASK;
|
||||
RuntimeBlock *block = runtime_.GetBlock(pc, &ctx_);
|
||||
|
||||
// be careful not to wrap around
|
||||
uint32_t next_remaining = remaining - block->guest_cycles;
|
||||
|
@ -105,64 +172,15 @@ void SH4::UnrequestInterrupt(Interrupt intr) {
|
|||
UpdatePendingInterrupts();
|
||||
}
|
||||
|
||||
namespace dreavm {
|
||||
namespace cpu {
|
||||
|
||||
// with OIX, bit 25, rather than bit 13, determines which 4kb bank to use
|
||||
#define CACHE_OFFSET(addr, OIX) \
|
||||
((OIX ? ((addr & 0x2000000) >> 13) : ((addr & 0x2000) >> 1)) | (addr & 0xfff))
|
||||
|
||||
template <typename T>
|
||||
T SH4::ReadCache(void *ctx, uint32_t addr) {
|
||||
SH4 *sh4 = (SH4 *)ctx;
|
||||
CHECK_EQ(sh4->CCR.ORA, 1u);
|
||||
uint32_t offset = CACHE_OFFSET(addr, sh4->CCR.OIX);
|
||||
return *reinterpret_cast<T *>(&sh4->cache_[offset]);
|
||||
uint8_t SH4::ReadRegister8(uint32_t addr) {
|
||||
return static_cast<uint8_t>(ReadRegister32(addr));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SH4::WriteCache(void *ctx, uint32_t addr, T value) {
|
||||
SH4 *sh4 = (SH4 *)ctx;
|
||||
CHECK_EQ(sh4->CCR.ORA, 1u);
|
||||
uint32_t offset = CACHE_OFFSET(addr, sh4->CCR.OIX);
|
||||
*reinterpret_cast<T *>(&sh4->cache_[offset]) = value;
|
||||
uint16_t SH4::ReadRegister16(uint32_t addr) {
|
||||
return static_cast<uint16_t>(ReadRegister32(addr));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T SH4::ReadSQ(void *ctx, uint32_t addr) {
|
||||
return static_cast<T>(ReadSQ<uint32_t>(ctx, addr));
|
||||
}
|
||||
|
||||
template <>
|
||||
uint32_t SH4::ReadSQ(void *ctx, uint32_t addr) {
|
||||
SH4 *sh4 = (SH4 *)ctx;
|
||||
uint32_t sqi = (addr & 0x20) >> 5;
|
||||
unsigned idx = (addr & 0x1c) >> 2;
|
||||
return sh4->ctx_.sq[sqi][idx];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SH4::WriteSQ(void *ctx, uint32_t addr, T value) {
|
||||
WriteSQ<uint32_t>(ctx, addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
void SH4::WriteSQ(void *ctx, uint32_t addr, uint32_t value) {
|
||||
SH4 *sh4 = (SH4 *)ctx;
|
||||
uint32_t sqi = (addr & 0x20) >> 5;
|
||||
unsigned idx = (addr & 0x1c) >> 2;
|
||||
sh4->ctx_.sq[sqi][idx] = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T SH4::ReadArea7(void *ctx, uint32_t addr) {
|
||||
return static_cast<T>(ReadArea7<uint32_t>(ctx, addr));
|
||||
}
|
||||
|
||||
template <>
|
||||
uint32_t SH4::ReadArea7(void *ctx, uint32_t addr) {
|
||||
SH4 *sh4 = (SH4 *)ctx;
|
||||
|
||||
uint32_t SH4::ReadRegister32(uint32_t addr) {
|
||||
// translate from 64mb space to our 16kb space
|
||||
addr = ((addr & 0x1fe0000) >> 11) | ((addr & 0xfc) >> 2);
|
||||
|
||||
|
@ -195,8 +213,8 @@ uint32_t SH4::ReadArea7(void *ctx, uint32_t addr) {
|
|||
// reg[PDTRA] = reg[PDTRA] & 0xfffd;
|
||||
// _8c00b92c(0);
|
||||
// reg[PCTRA] = old_PCTRA;
|
||||
uint32_t pctra = sh4->PCTRA;
|
||||
uint32_t pdtra = sh4->PDTRA;
|
||||
uint32_t pctra = PCTRA;
|
||||
uint32_t pdtra = PDTRA;
|
||||
uint32_t v = 0;
|
||||
if ((pctra & 0xf) == 0x8 ||
|
||||
((pctra & 0xf) == 0xb && (pdtra & 0xf) != 0x2) ||
|
||||
|
@ -236,22 +254,22 @@ uint32_t SH4::ReadArea7(void *ctx, uint32_t addr) {
|
|||
}
|
||||
}
|
||||
|
||||
return sh4->area7_[addr];
|
||||
return area7_[addr];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SH4::WriteArea7(void *ctx, uint32_t addr, T value) {
|
||||
WriteArea7<uint32_t>(ctx, addr, static_cast<uint32_t>(value));
|
||||
void SH4::WriteRegister8(uint32_t addr, uint8_t value) {
|
||||
WriteRegister32(addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
void SH4::WriteArea7(void *ctx, uint32_t addr, uint32_t value) {
|
||||
SH4 *sh4 = (SH4 *)ctx;
|
||||
void SH4::WriteRegister16(uint32_t addr, uint16_t value) {
|
||||
WriteRegister32(addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
void SH4::WriteRegister32(uint32_t addr, uint32_t value) {
|
||||
// translate from 64mb space to our 16kb space
|
||||
addr = ((addr & 0x1fe0000) >> 11) | ((addr & 0xfc) >> 2);
|
||||
|
||||
sh4->area7_[addr] = value;
|
||||
area7_[addr] = value;
|
||||
|
||||
switch (addr) {
|
||||
case MMUCR_OFFSET:
|
||||
|
@ -263,79 +281,111 @@ void SH4::WriteArea7(void *ctx, uint32_t addr, uint32_t value) {
|
|||
// it seems the only aspect of the cache control register that needs to be
|
||||
// emulated is the instruction cache invalidation
|
||||
case CCR_OFFSET:
|
||||
if (sh4->CCR.ICI) {
|
||||
sh4->ResetInstructionCache();
|
||||
if (CCR.ICI) {
|
||||
ResetInstructionCache();
|
||||
}
|
||||
break;
|
||||
|
||||
// when a PREF instruction is encountered, the high order bits of the
|
||||
// address are filled in from the queue address control register
|
||||
case QACR0_OFFSET:
|
||||
sh4->ctx_.sq_ext_addr[0] = (value & 0x1c) << 24;
|
||||
ctx_.sq_ext_addr[0] = (value & 0x1c) << 24;
|
||||
break;
|
||||
case QACR1_OFFSET:
|
||||
sh4->ctx_.sq_ext_addr[1] = (value & 0x1c) << 24;
|
||||
ctx_.sq_ext_addr[1] = (value & 0x1c) << 24;
|
||||
break;
|
||||
|
||||
case IPRA_OFFSET:
|
||||
case IPRB_OFFSET:
|
||||
case IPRC_OFFSET:
|
||||
sh4->ReprioritizeInterrupts();
|
||||
ReprioritizeInterrupts();
|
||||
break;
|
||||
|
||||
// TODO UnrequestInterrupt on TCR write
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// with OIX, bit 25, rather than bit 13, determines which 4kb bank to use
|
||||
#define CACHE_OFFSET(addr, OIX) \
|
||||
((OIX ? ((addr & 0x2000000) >> 13) : ((addr & 0x2000) >> 1)) | (addr & 0xfff))
|
||||
|
||||
uint8_t SH4::ReadCache8(uint32_t addr) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
return *reinterpret_cast<uint8_t *>(&cache_[addr]);
|
||||
}
|
||||
|
||||
void SH4::InitMemory() {
|
||||
// mount internal cpu register area
|
||||
memory_.Handle(SH4_REG_START, SH4_REG_END, MIRROR_MASK, this,
|
||||
&SH4::ReadArea7<uint8_t>, &SH4::ReadArea7<uint16_t>,
|
||||
&SH4::ReadArea7<uint32_t>, nullptr, &SH4::WriteArea7<uint8_t>,
|
||||
&SH4::WriteArea7<uint16_t>, &SH4::WriteArea7<uint32_t>,
|
||||
nullptr);
|
||||
|
||||
// map cache
|
||||
memory_.Handle(0x7c000000, 0x7fffffff, 0x0, this, &SH4::ReadCache<uint8_t>,
|
||||
&SH4::ReadCache<uint16_t>, &SH4::ReadCache<uint32_t>,
|
||||
&SH4::ReadCache<uint64_t>, &SH4::WriteCache<uint8_t>,
|
||||
&SH4::WriteCache<uint16_t>, &SH4::WriteCache<uint32_t>,
|
||||
&SH4::WriteCache<uint64_t>);
|
||||
|
||||
// map store queues
|
||||
memory_.Handle(0xe0000000, 0xe3ffffff, 0x0, this, &SH4::ReadSQ<uint8_t>,
|
||||
&SH4::ReadSQ<uint16_t>, &SH4::ReadSQ<uint32_t>, nullptr,
|
||||
&SH4::WriteSQ<uint8_t>, &SH4::WriteSQ<uint16_t>,
|
||||
&SH4::WriteSQ<uint32_t>, nullptr);
|
||||
uint16_t SH4::ReadCache16(uint32_t addr) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
return *reinterpret_cast<uint16_t *>(&cache_[addr]);
|
||||
}
|
||||
|
||||
void SH4::Reset() {
|
||||
// reset context
|
||||
memset(&ctx_, 0, sizeof(ctx_));
|
||||
ctx_.pc = 0xa0000000;
|
||||
ctx_.pr = 0xdeadbeef;
|
||||
ctx_.sr.full = ctx_.old_sr.full = 0x700000f0;
|
||||
ctx_.fpscr.full = ctx_.old_fpscr.full = 0x00040001;
|
||||
|
||||
#define SH4_REG(addr, name, flags, default, reset, sleep, standby, type) \
|
||||
if (default != HELD) { \
|
||||
*(uint32_t *)&area7_[name##_OFFSET] = default; \
|
||||
}
|
||||
#include "cpu/sh4_regs.inc"
|
||||
#undef SH4_REG
|
||||
|
||||
// reset interrupts
|
||||
requested_interrupts_ = 0;
|
||||
ReprioritizeInterrupts();
|
||||
|
||||
// reset memory
|
||||
memset(area7_, 0, sizeof(area7_));
|
||||
memset(cache_, 0, sizeof(cache_));
|
||||
uint32_t SH4::ReadCache32(uint32_t addr) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
return *reinterpret_cast<uint32_t *>(&cache_[addr]);
|
||||
}
|
||||
|
||||
void SH4::ResetInstructionCache() { runtime_->QueueResetBlocks(); }
|
||||
uint64_t SH4::ReadCache64(uint32_t addr) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
return *reinterpret_cast<uint64_t *>(&cache_[addr]);
|
||||
}
|
||||
|
||||
void SH4::WriteCache8(uint32_t addr, uint8_t value) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
*reinterpret_cast<uint8_t *>(&cache_[addr]) = value;
|
||||
}
|
||||
|
||||
void SH4::WriteCache16(uint32_t addr, uint16_t value) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
*reinterpret_cast<uint16_t *>(&cache_[addr]) = value;
|
||||
}
|
||||
|
||||
void SH4::WriteCache32(uint32_t addr, uint32_t value) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
*reinterpret_cast<uint32_t *>(&cache_[addr]) = value;
|
||||
}
|
||||
|
||||
void SH4::WriteCache64(uint32_t addr, uint64_t value) {
|
||||
CHECK_EQ(CCR.ORA, 1u);
|
||||
addr = CACHE_OFFSET(addr, CCR.OIX);
|
||||
*reinterpret_cast<uint64_t *>(&cache_[addr]) = value;
|
||||
}
|
||||
|
||||
uint8_t SH4::ReadSQ8(uint32_t addr) {
|
||||
return static_cast<uint8_t>(ReadSQ32(addr));
|
||||
}
|
||||
|
||||
uint16_t SH4::ReadSQ16(uint32_t addr) {
|
||||
return static_cast<uint16_t>(ReadSQ32(addr));
|
||||
}
|
||||
|
||||
uint32_t SH4::ReadSQ32(uint32_t addr) {
|
||||
uint32_t sqi = (addr & 0x20) >> 5;
|
||||
unsigned idx = (addr & 0x1c) >> 2;
|
||||
return ctx_.sq[sqi][idx];
|
||||
}
|
||||
|
||||
void SH4::WriteSQ8(uint32_t addr, uint8_t value) {
|
||||
WriteSQ32(addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
void SH4::WriteSQ16(uint32_t addr, uint16_t value) {
|
||||
WriteSQ32(addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
void SH4::WriteSQ32(uint32_t addr, uint32_t value) {
|
||||
uint32_t sqi = (addr & 0x20) >> 5;
|
||||
unsigned idx = (addr & 0x1c) >> 2;
|
||||
ctx_.sq[sqi][idx] = value;
|
||||
}
|
||||
|
||||
void SH4::ResetInstructionCache() { runtime_.QueueResetBlocks(); }
|
||||
|
||||
// Generate a sorted set of interrupts based on their priority. These sorted
|
||||
// ids are used to represent all of the currently requested interrupts as a
|
||||
|
@ -411,7 +461,7 @@ void SH4::CheckPendingInterrupts() {
|
|||
ctx_.sr.RB = 1;
|
||||
ctx_.pc = ctx_.vbr + 0x600;
|
||||
|
||||
SRUpdated(&ctx_);
|
||||
SH4Context::SRUpdated(&ctx_);
|
||||
}
|
||||
|
||||
bool SH4::TimerEnabled(int n) { //
|
||||
|
|
127
src/cpu/sh4.h
127
src/cpu/sh4.h
|
@ -1,31 +1,64 @@
|
|||
#ifndef SH4_H
|
||||
#define SH4_H
|
||||
|
||||
#include "cpu/sh4_context.h"
|
||||
#include "emu/device.h"
|
||||
#include "emu/memory.h"
|
||||
|
||||
struct SH4Test;
|
||||
|
||||
namespace dreavm {
|
||||
namespace emu {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace cpu {
|
||||
|
||||
class Runtime;
|
||||
|
||||
// translate address to 29-bit physical space, ignoring all modifier bits
|
||||
enum { ADDR_MASK = 0x1fffffff };
|
||||
|
||||
// registers
|
||||
enum {
|
||||
UNDEFINED = 0x0,
|
||||
HELD = 0x1,
|
||||
};
|
||||
|
||||
enum {
|
||||
#define SH4_REG(addr, name, flags, default, reset, sleep, standby, type) \
|
||||
name##_OFFSET = ((addr & 0x1fe0000) >> 11) | ((addr & 0xfc) >> 2),
|
||||
#include "cpu/sh4_regs.inc"
|
||||
#undef SH4_REG
|
||||
union SR_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t T : 1;
|
||||
uint32_t S : 1;
|
||||
uint32_t reserved : 2;
|
||||
uint32_t IMASK : 4;
|
||||
uint32_t Q : 1;
|
||||
uint32_t M : 1;
|
||||
uint32_t reserved1 : 5;
|
||||
uint32_t FD : 1;
|
||||
uint32_t reserved2 : 12;
|
||||
uint32_t BL : 1;
|
||||
uint32_t RB : 1;
|
||||
uint32_t MD : 1;
|
||||
uint32_t reserved3 : 1;
|
||||
};
|
||||
};
|
||||
|
||||
union FPSCR_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t RM : 2;
|
||||
uint32_t flag : 5;
|
||||
uint32_t enable : 5;
|
||||
uint32_t cause : 6;
|
||||
uint32_t DN : 1;
|
||||
uint32_t PR : 1;
|
||||
uint32_t SZ : 1;
|
||||
uint32_t FR : 1;
|
||||
uint32_t reserved : 10;
|
||||
};
|
||||
};
|
||||
|
||||
union CCR_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t OCE : 1;
|
||||
uint32_t WT : 1;
|
||||
|
@ -43,10 +76,10 @@ union CCR_T {
|
|||
uint32_t reserved4 : 15;
|
||||
uint32_t EMODE : 1;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union CHCR_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t DE : 1;
|
||||
uint32_t TE : 1;
|
||||
|
@ -67,10 +100,10 @@ union CHCR_T {
|
|||
uint32_t STC : 1;
|
||||
uint32_t SSA : 3;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union DMAOR_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t DME : 1;
|
||||
uint32_t NMIF : 1;
|
||||
|
@ -83,7 +116,13 @@ union DMAOR_T {
|
|||
uint32_t DDT : 1;
|
||||
uint32_t reserved2 : 16;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
enum {
|
||||
#define SH4_REG(addr, name, flags, default, reset, sleep, standby, type) \
|
||||
name##_OFFSET = ((addr & 0x1fe0000) >> 11) | ((addr & 0xfc) >> 2),
|
||||
#include "cpu/sh4_regs.inc"
|
||||
#undef SH4_REG
|
||||
};
|
||||
|
||||
// interrupts
|
||||
|
@ -104,18 +143,36 @@ enum DDTRW { //
|
|||
DDT_W
|
||||
};
|
||||
|
||||
struct SH4Context {
|
||||
static void SRUpdated(SH4Context *ctx);
|
||||
static void FPSCRUpdated(SH4Context *ctx);
|
||||
|
||||
uint32_t pc, spc;
|
||||
uint32_t pr;
|
||||
uint32_t gbr, vbr;
|
||||
uint32_t mach, macl;
|
||||
uint32_t r[16], rbnk[2][8], sgr;
|
||||
uint32_t fr[16], xf[16];
|
||||
uint32_t fpul;
|
||||
uint32_t dbr;
|
||||
uint32_t sq[2][8];
|
||||
uint32_t sq_ext_addr[2];
|
||||
uint32_t preserve;
|
||||
SR_T sr, ssr, old_sr;
|
||||
FPSCR_T fpscr, old_fpscr;
|
||||
};
|
||||
|
||||
class SH4 : public emu::Device {
|
||||
friend void SRUpdated(SH4Context *ctx);
|
||||
friend void RunSH4Test(const SH4Test &);
|
||||
|
||||
public:
|
||||
SH4(emu::Memory &memory);
|
||||
virtual ~SH4();
|
||||
SH4(emu::Memory &memory, cpu::Runtime &runtime);
|
||||
|
||||
uint32_t GetClockFrequency() { return 200000000; }
|
||||
|
||||
bool Init(Runtime *runtime);
|
||||
void Reset(uint32_t pc);
|
||||
void Init();
|
||||
void SetPC(uint32_t pc);
|
||||
uint32_t Execute(uint32_t cycles);
|
||||
|
||||
// DMAC
|
||||
|
@ -125,23 +182,31 @@ class SH4 : public emu::Device {
|
|||
void RequestInterrupt(Interrupt intr);
|
||||
void UnrequestInterrupt(Interrupt intr);
|
||||
|
||||
uint8_t ReadRegister8(uint32_t addr);
|
||||
uint16_t ReadRegister16(uint32_t addr);
|
||||
uint32_t ReadRegister32(uint32_t addr);
|
||||
void WriteRegister8(uint32_t addr, uint8_t value);
|
||||
void WriteRegister16(uint32_t addr, uint16_t value);
|
||||
void WriteRegister32(uint32_t addr, uint32_t value);
|
||||
|
||||
uint8_t ReadCache8(uint32_t addr);
|
||||
uint16_t ReadCache16(uint32_t addr);
|
||||
uint32_t ReadCache32(uint32_t addr);
|
||||
uint64_t ReadCache64(uint32_t addr);
|
||||
void WriteCache8(uint32_t addr, uint8_t value);
|
||||
void WriteCache16(uint32_t addr, uint16_t value);
|
||||
void WriteCache32(uint32_t addr, uint32_t value);
|
||||
void WriteCache64(uint32_t addr, uint64_t value);
|
||||
|
||||
uint8_t ReadSQ8(uint32_t addr);
|
||||
uint16_t ReadSQ16(uint32_t addr);
|
||||
uint32_t ReadSQ32(uint32_t addr);
|
||||
|
||||
void WriteSQ8(uint32_t addr, uint8_t value);
|
||||
void WriteSQ16(uint32_t addr, uint16_t value);
|
||||
void WriteSQ32(uint32_t addr, uint32_t value);
|
||||
|
||||
protected:
|
||||
template <typename T>
|
||||
static T ReadCache(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteCache(void *ctx, uint32_t addr, T value);
|
||||
template <typename T>
|
||||
static T ReadSQ(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteSQ(void *ctx, uint32_t addr, T value);
|
||||
template <typename T>
|
||||
static T ReadArea7(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteArea7(void *ctx, uint32_t addr, T value);
|
||||
|
||||
void InitMemory();
|
||||
void Reset();
|
||||
|
||||
// CCN
|
||||
void ResetInstructionCache();
|
||||
|
||||
|
@ -155,7 +220,7 @@ class SH4 : public emu::Device {
|
|||
void RunTimer(int n, uint32_t cycles);
|
||||
|
||||
emu::Memory &memory_;
|
||||
Runtime *runtime_;
|
||||
Runtime &runtime_;
|
||||
|
||||
SH4Context ctx_;
|
||||
SR_T old_sr_;
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
#include "core/core.h"
|
||||
#include "cpu/sh4.h"
|
||||
#include "cpu/sh4_context.h"
|
||||
|
||||
namespace dreavm {
|
||||
namespace cpu {
|
||||
|
||||
static void SetRegisterBank(SH4Context *ctx, int bank) {
|
||||
if (bank == 0) {
|
||||
for (int s = 0; s < 8; s++) {
|
||||
ctx->rbnk[1][s] = ctx->r[s];
|
||||
ctx->r[s] = ctx->rbnk[0][s];
|
||||
}
|
||||
} else {
|
||||
for (int s = 0; s < 8; s++) {
|
||||
ctx->rbnk[0][s] = ctx->r[s];
|
||||
ctx->r[s] = ctx->rbnk[1][s];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SwapFPRegisters(SH4Context *ctx) {
|
||||
uint32_t z;
|
||||
|
||||
for (int s = 0; s <= 15; s++) {
|
||||
z = ctx->fr[s];
|
||||
ctx->fr[s] = ctx->xf[s];
|
||||
ctx->xf[s] = z;
|
||||
}
|
||||
}
|
||||
|
||||
static void SwapFPCouples(SH4Context *ctx) {
|
||||
uint32_t z;
|
||||
|
||||
for (int s = 0; s <= 15; s = s + 2) {
|
||||
z = ctx->fr[s];
|
||||
ctx->fr[s] = ctx->fr[s + 1];
|
||||
ctx->fr[s + 1] = z;
|
||||
|
||||
z = ctx->xf[s];
|
||||
ctx->xf[s] = ctx->xf[s + 1];
|
||||
ctx->xf[s + 1] = z;
|
||||
}
|
||||
}
|
||||
|
||||
void SRUpdated(SH4Context *ctx) {
|
||||
if (ctx->sr.RB != ctx->old_sr.RB) {
|
||||
SetRegisterBank(ctx, ctx->sr.RB ? 1 : 0);
|
||||
}
|
||||
|
||||
ctx->old_sr = ctx->sr;
|
||||
}
|
||||
|
||||
void FPSCRUpdated(SH4Context *ctx) {
|
||||
if (ctx->fpscr.FR != ctx->old_fpscr.FR) {
|
||||
SwapFPRegisters(ctx);
|
||||
}
|
||||
|
||||
if (ctx->fpscr.PR != ctx->old_fpscr.PR) {
|
||||
SwapFPCouples(ctx);
|
||||
}
|
||||
|
||||
ctx->old_fpscr = ctx->fpscr;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
#ifndef SH4_CONTEXT_H
|
||||
#define SH4_CONTEXT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace dreavm {
|
||||
namespace cpu {
|
||||
|
||||
union SR_T {
|
||||
struct {
|
||||
uint32_t T : 1;
|
||||
uint32_t S : 1;
|
||||
uint32_t reserved : 2;
|
||||
uint32_t IMASK : 4;
|
||||
uint32_t Q : 1;
|
||||
uint32_t M : 1;
|
||||
uint32_t reserved1 : 5;
|
||||
uint32_t FD : 1;
|
||||
uint32_t reserved2 : 12;
|
||||
uint32_t BL : 1;
|
||||
uint32_t RB : 1;
|
||||
uint32_t MD : 1;
|
||||
uint32_t reserved3 : 1;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union FPSCR_T {
|
||||
struct {
|
||||
uint32_t RM : 2;
|
||||
uint32_t flag : 5;
|
||||
uint32_t enable : 5;
|
||||
uint32_t cause : 6;
|
||||
uint32_t DN : 1;
|
||||
uint32_t PR : 1;
|
||||
uint32_t SZ : 1;
|
||||
uint32_t FR : 1;
|
||||
uint32_t reserved : 10;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
struct SH4Context {
|
||||
uint32_t pc, spc;
|
||||
uint32_t pr;
|
||||
uint32_t gbr, vbr;
|
||||
uint32_t mach, macl;
|
||||
uint32_t r[16], rbnk[2][8], sgr;
|
||||
uint32_t fr[16], xf[16];
|
||||
uint32_t fpul;
|
||||
uint32_t dbr;
|
||||
uint32_t sq[2][8];
|
||||
uint32_t sq_ext_addr[2];
|
||||
uint32_t preserve;
|
||||
SR_T sr, ssr, old_sr;
|
||||
FPSCR_T fpscr, old_fpscr;
|
||||
};
|
||||
|
||||
void SRUpdated(SH4Context *ctx);
|
||||
void FPSCRUpdated(SH4Context *ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,433 @@
|
|||
#include "core/core.h"
|
||||
#include "cpu/backend/interpreter/interpreter_backend.h"
|
||||
#include "cpu/backend/x64/x64_backend.h"
|
||||
#include "cpu/frontend/sh4/sh4_frontend.h"
|
||||
#include "emu/dreamcast.h"
|
||||
#include "emu/profiler.h"
|
||||
#include "holly/maple_controller.h"
|
||||
#include "renderer/gl_backend.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
using namespace dreavm::cpu;
|
||||
using namespace dreavm::cpu::backend::interpreter;
|
||||
using namespace dreavm::cpu::backend::x64;
|
||||
using namespace dreavm::cpu::frontend::sh4;
|
||||
using namespace dreavm::emu;
|
||||
using namespace dreavm::holly;
|
||||
using namespace dreavm::renderer;
|
||||
using namespace dreavm::system;
|
||||
using namespace dreavm::trace;
|
||||
|
||||
DEFINE_string(bios, "dc_bios.bin", "Path to BIOS");
|
||||
DEFINE_string(flash, "dc_flash.bin", "Path to flash ROM");
|
||||
|
||||
Dreamcast::Dreamcast() {
|
||||
scheduler_ = std::unique_ptr<Scheduler>(new Scheduler());
|
||||
memory_ = std::unique_ptr<Memory>(new Memory());
|
||||
rb_ = std::unique_ptr<renderer::Backend>(new GLBackend(sys_));
|
||||
rt_frontend_ =
|
||||
std::unique_ptr<frontend::Frontend>(new SH4Frontend(*memory()));
|
||||
rt_backend_ = std::unique_ptr<backend::Backend>(new X64Backend(*memory()));
|
||||
runtime_ = std::unique_ptr<Runtime>(
|
||||
new Runtime(*memory(), *rt_frontend_.get(), *rt_backend_.get()));
|
||||
cpu_ = std::unique_ptr<SH4>(new SH4(*memory(), *runtime()));
|
||||
holly_ = std::unique_ptr<Holly>(new Holly(this));
|
||||
pvr_ = std::unique_ptr<PVR2>(new PVR2(this));
|
||||
ta_ = std::unique_ptr<TileAccelerator>(new TileAccelerator(this));
|
||||
gdrom_ = std::unique_ptr<GDROM>(new GDROM(this));
|
||||
maple_ = std::unique_ptr<Maple>(new Maple(this));
|
||||
}
|
||||
|
||||
void Dreamcast::Run(const char *path) {
|
||||
if (!Init()) {
|
||||
LOG_WARNING("Failed to initialize emulator");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LoadBios(FLAGS_bios.c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LoadFlash(FLAGS_flash.c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
LOG_INFO("Launching %s", path);
|
||||
|
||||
if ((strstr(path, ".bin") && !LaunchBIN(path)) ||
|
||||
(strstr(path, ".gdi") && !LaunchGDI(path))) {
|
||||
LOG_WARNING("Failed to launch %s", path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const std::chrono::nanoseconds step = HZ_TO_NANO(60);
|
||||
std::chrono::nanoseconds time_remaining = std::chrono::nanoseconds(0);
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
auto last_time = current_time;
|
||||
|
||||
while (true) {
|
||||
current_time = std::chrono::high_resolution_clock::now();
|
||||
time_remaining += current_time - last_time;
|
||||
last_time = current_time;
|
||||
|
||||
if (time_remaining < step) {
|
||||
continue;
|
||||
}
|
||||
|
||||
time_remaining -= step;
|
||||
|
||||
PumpEvents();
|
||||
|
||||
scheduler_->Tick(step);
|
||||
|
||||
RenderFrame();
|
||||
}
|
||||
}
|
||||
|
||||
bool Dreamcast::Init() {
|
||||
if (!sys_.Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rb_->Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Profiler::Init();
|
||||
|
||||
InitMemory();
|
||||
InitRegisters();
|
||||
|
||||
cpu_->Init();
|
||||
holly_->Init();
|
||||
pvr_->Init();
|
||||
ta_->Init();
|
||||
gdrom_->Init();
|
||||
maple_->Init();
|
||||
|
||||
scheduler_->AddDevice(cpu());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Dreamcast::InitMemory() {
|
||||
using namespace std::placeholders;
|
||||
|
||||
memset(ram_, 0, sizeof(ram_));
|
||||
memset(unassigned_, 0, sizeof(unassigned_));
|
||||
memset(modem_mem_, 0, sizeof(modem_mem_));
|
||||
memset(aica_mem_, 0, sizeof(aica_mem_));
|
||||
memset(audio_ram_, 0, sizeof(audio_ram_));
|
||||
memset(expdev_mem_, 0, sizeof(expdev_mem_));
|
||||
memset(video_ram_, 0, sizeof(video_ram_));
|
||||
memset(palette_ram_, 0, sizeof(palette_ram_));
|
||||
|
||||
// main ram
|
||||
memory_->Mount(BIOS_START, BIOS_END, MIRROR_MASK, bios_);
|
||||
memory_->Mount(FLASH_START, FLASH_END, MIRROR_MASK, flash_);
|
||||
memory_->Mount(MAIN_RAM_START, MAIN_RAM_END, MAIN_RAM_MIRROR_MASK, ram_);
|
||||
memory_->Mount(UNASSIGNED_START, UNASSIGNED_END, MIRROR_MASK, unassigned_);
|
||||
|
||||
// holly
|
||||
memory_->Handle(HOLLY_REG_START, HOLLY_REG_END, MIRROR_MASK,
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&Holly::ReadRegister32, holly(), _1), //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&Holly::WriteRegister32, holly(), _1, _2), //
|
||||
nullptr);
|
||||
memory_->Mount(MODEM_REG_START, MODEM_REG_END, MIRROR_MASK, modem_mem_);
|
||||
memory_->Mount(AICA_REG_START, AICA_REG_END, MIRROR_MASK, aica_mem_);
|
||||
memory_->Handle(AUDIO_RAM_START, AUDIO_RAM_END, MIRROR_MASK,
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&Holly::ReadAudio32, holly(), _1), //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&Holly::WriteAudio32, holly(), _1, _2), //
|
||||
nullptr);
|
||||
memory_->Mount(EXPDEV_START, EXPDEV_END, MIRROR_MASK, expdev_mem_);
|
||||
|
||||
// gdrom
|
||||
memory_->Handle(GDROM_REG_START, GDROM_REG_END, MIRROR_MASK,
|
||||
std::bind(&GDROM::ReadRegister8, gdrom(), _1), //
|
||||
std::bind(&GDROM::ReadRegister16, gdrom(), _1), //
|
||||
std::bind(&GDROM::ReadRegister32, gdrom(), _1), //
|
||||
nullptr, //
|
||||
std::bind(&GDROM::WriteRegister8, gdrom(), _1, _2), //
|
||||
std::bind(&GDROM::WriteRegister16, gdrom(), _1, _2), //
|
||||
std::bind(&GDROM::WriteRegister32, gdrom(), _1, _2), //
|
||||
nullptr);
|
||||
|
||||
// maple
|
||||
memory_->Handle(MAPLE_REG_START, MAPLE_REG_END, MIRROR_MASK,
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&Maple::ReadRegister32, maple(), _1), //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&Maple::WriteRegister32, maple(), _1, _2), //
|
||||
nullptr);
|
||||
|
||||
// pvr2
|
||||
memory_->Mount(PVR_VRAM32_START, PVR_VRAM32_END, MIRROR_MASK, video_ram_);
|
||||
memory_->Handle(PVR_VRAM64_START, PVR_VRAM64_END, MIRROR_MASK,
|
||||
std::bind(&PVR2::ReadInterleaved8, pvr(), _1), //
|
||||
std::bind(&PVR2::ReadInterleaved16, pvr(), _1), //
|
||||
std::bind(&PVR2::ReadInterleaved32, pvr(), _1), //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&PVR2::WriteInterleaved16, pvr(), _1, _2), //
|
||||
std::bind(&PVR2::WriteInterleaved32, pvr(), _1, _2), //
|
||||
nullptr);
|
||||
memory_->Handle(PVR_REG_START, PVR_REG_END, MIRROR_MASK,
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&PVR2::ReadRegister32, pvr(), _1), //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&PVR2::WriteRegister32, pvr(), _1, _2), //
|
||||
nullptr);
|
||||
memory_->Mount(PVR_PALETTE_START, PVR_PALETTE_END, MIRROR_MASK, palette_ram_);
|
||||
|
||||
// ta
|
||||
// TODO handle YUV transfers from 0x10800000 - 0x10ffffe0
|
||||
memory_->Handle(TA_CMD_START, TA_CMD_END, 0x0,
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&TileAccelerator::WriteCommand32, ta(), _1, _2), //
|
||||
nullptr);
|
||||
memory_->Handle(TA_TEXTURE_START, TA_TEXTURE_END, 0x0,
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
nullptr, //
|
||||
std::bind(&TileAccelerator::WriteTexture32, ta(), _1, _2), //
|
||||
nullptr);
|
||||
|
||||
// cpu
|
||||
memory_->Handle(SH4_REG_START, SH4_REG_END, MIRROR_MASK,
|
||||
std::bind(&SH4::ReadRegister8, cpu(), _1), //
|
||||
std::bind(&SH4::ReadRegister16, cpu(), _1), //
|
||||
std::bind(&SH4::ReadRegister32, cpu(), _1), //
|
||||
nullptr, //
|
||||
std::bind(&SH4::WriteRegister8, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteRegister16, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteRegister32, cpu(), _1, _2), //
|
||||
nullptr);
|
||||
memory_->Handle(SH4_CACHE_START, SH4_CACHE_END, 0x0,
|
||||
std::bind(&SH4::ReadCache8, cpu(), _1), //
|
||||
std::bind(&SH4::ReadCache16, cpu(), _1), //
|
||||
std::bind(&SH4::ReadCache32, cpu(), _1), //
|
||||
std::bind(&SH4::ReadCache64, cpu(), _1), //
|
||||
std::bind(&SH4::WriteCache8, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteCache16, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteCache32, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteCache64, cpu(), _1, _2));
|
||||
memory_->Handle(SH4_SQ_START, SH4_SQ_END, 0x0,
|
||||
std::bind(&SH4::ReadSQ8, cpu(), _1), //
|
||||
std::bind(&SH4::ReadSQ16, cpu(), _1), //
|
||||
std::bind(&SH4::ReadSQ32, cpu(), _1), //
|
||||
nullptr, //
|
||||
std::bind(&SH4::WriteSQ8, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteSQ16, cpu(), _1, _2), //
|
||||
std::bind(&SH4::WriteSQ32, cpu(), _1, _2), //
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void Dreamcast::InitRegisters() {
|
||||
#define HOLLY_REG(addr, name, flags, default, type) \
|
||||
holly_regs_[name##_OFFSET] = {flags, default};
|
||||
#include "holly/holly_regs.inc"
|
||||
#undef HOLLY_REG
|
||||
|
||||
#define PVR_REG(addr, name, flags, default, type) \
|
||||
pvr_regs_[name##_OFFSET] = {flags, default};
|
||||
#include "holly/pvr2_regs.inc"
|
||||
#undef PVR_REG
|
||||
}
|
||||
|
||||
bool Dreamcast::LoadBios(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
LOG_WARNING("Failed to open bios at \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != BIOS_SIZE) {
|
||||
LOG_WARNING("Bios size mismatch, is %d, expected %d", size, BIOS_SIZE);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = static_cast<int>(fread(bios_, sizeof(uint8_t), size, fp));
|
||||
fclose(fp);
|
||||
|
||||
if (n != size) {
|
||||
LOG_WARNING("Bios read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dreamcast::LoadFlash(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
LOG_WARNING("Failed to open flash at \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != FLASH_SIZE) {
|
||||
LOG_WARNING("Flash size mismatch, is %d, expected %d", size, FLASH_SIZE);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = static_cast<int>(fread(flash_, sizeof(uint8_t), size, fp));
|
||||
fclose(fp);
|
||||
|
||||
if (n != size) {
|
||||
LOG_WARNING("Flash read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dreamcast::LaunchBIN(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
uint8_t *data = reinterpret_cast<uint8_t *>(malloc(size));
|
||||
int n = static_cast<int>(fread(data, sizeof(uint8_t), size, fp));
|
||||
fclose(fp);
|
||||
|
||||
if (n != size) {
|
||||
free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
// load to 0x0c010000 (area 3) which is where 1ST_READ.BIN is normally
|
||||
// loaded to
|
||||
memory_->Memcpy(0x0c010000, data, size);
|
||||
free(data);
|
||||
|
||||
cpu_->SetPC(0x0c010000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dreamcast::LaunchGDI(const char *path) {
|
||||
std::unique_ptr<GDI> gdi(new GDI());
|
||||
|
||||
if (!gdi->Load(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gdrom_->SetDisc(std::move(gdi));
|
||||
cpu_->SetPC(0xa0000000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Dreamcast::PumpEvents() {
|
||||
SystemEvent ev;
|
||||
|
||||
sys_.PumpEvents();
|
||||
|
||||
while (sys_.PollEvent(&ev)) {
|
||||
switch (ev.type) {
|
||||
case SE_KEY: {
|
||||
// let the profiler take a stab at the input first
|
||||
if (!Profiler::HandleInput(ev.key.code, ev.key.value)) {
|
||||
// debug tracing
|
||||
if (ev.key.code == K_F2) {
|
||||
if (ev.key.value) {
|
||||
ToggleTracing();
|
||||
}
|
||||
}
|
||||
// else, forward to maple
|
||||
else {
|
||||
maple_->HandleInput(0, ev.key.code, ev.key.value);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case SE_MOUSEMOVE: {
|
||||
Profiler::HandleMouseMove(ev.mousemove.x, ev.mousemove.y);
|
||||
} break;
|
||||
|
||||
case SE_RESIZE: {
|
||||
rb_->ResizeVideo(ev.resize.width, ev.resize.height);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Dreamcast::ToggleTracing() {
|
||||
if (!trace_writer_) {
|
||||
char filename[PATH_MAX];
|
||||
GetNextTraceFilename(filename, sizeof(filename));
|
||||
|
||||
trace_writer_ = std::unique_ptr<TraceWriter>(new TraceWriter());
|
||||
|
||||
if (!trace_writer_->Open(filename)) {
|
||||
trace_writer_ = nullptr;
|
||||
|
||||
LOG_INFO("Failed to start tracing");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Begin tracing to %s", filename);
|
||||
} else {
|
||||
trace_writer_ = nullptr;
|
||||
|
||||
LOG_INFO("End tracing");
|
||||
}
|
||||
}
|
||||
|
||||
void Dreamcast::RenderFrame() {
|
||||
rb_->BeginFrame();
|
||||
|
||||
ta_->RenderLastContext();
|
||||
|
||||
// render stats
|
||||
char stats[512];
|
||||
snprintf(stats, sizeof(stats), "%.2f fps, %.2f vbps", pvr_->fps(),
|
||||
pvr_->vbps());
|
||||
rb_->RenderText2D(0, 0, 12.0f, 0xffffffff, stats);
|
||||
|
||||
// render profiler
|
||||
Profiler::Render(rb());
|
||||
|
||||
rb_->EndFrame();
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
#ifndef DREAMCAST_H
|
||||
#define DREAMCAST_H
|
||||
|
||||
#include <memory>
|
||||
#include "cpu/backend/backend.h"
|
||||
#include "cpu/frontend/frontend.h"
|
||||
#include "cpu/runtime.h"
|
||||
#include "cpu/sh4.h"
|
||||
#include "emu/memory.h"
|
||||
#include "emu/scheduler.h"
|
||||
#include "holly/gdrom.h"
|
||||
#include "holly/holly.h"
|
||||
#include "holly/maple.h"
|
||||
#include "holly/pvr2.h"
|
||||
#include "holly/tile_accelerator.h"
|
||||
#include "renderer/backend.h"
|
||||
#include "system/system.h"
|
||||
#include "trace/trace.h"
|
||||
|
||||
namespace dreavm {
|
||||
namespace emu {
|
||||
|
||||
//
|
||||
// memory layout
|
||||
//
|
||||
#define MEMORY_REGION(name, start, end) \
|
||||
name##_START = start, name##_END = end, name##_SIZE = end - start + 1
|
||||
|
||||
enum {
|
||||
// ignore all access modifier bits
|
||||
MIRROR_MASK = ~cpu::ADDR_MASK,
|
||||
|
||||
// main ram is mirrored an additional four times:
|
||||
// 0x0c000000 - 0x0cffffff
|
||||
// 0x0d000000 - 0x0dffffff
|
||||
// 0x0e000000 - 0x0effffff
|
||||
// 0x0f000000 - 0x0fffffff
|
||||
MAIN_RAM_MIRROR_MASK = MIRROR_MASK | 0x03000000,
|
||||
|
||||
MEMORY_REGION(BIOS, 0x00000000, 0x001fffff),
|
||||
MEMORY_REGION(FLASH, 0x00200000, 0x0021ffff),
|
||||
MEMORY_REGION(HOLLY_REG, 0x005f6000, 0x005f7fff),
|
||||
MEMORY_REGION(MAPLE_REG, 0x005f6c00, 0x005f6fff),
|
||||
MEMORY_REGION(GDROM_REG, 0x005f7000, 0x005f77ff),
|
||||
MEMORY_REGION(PVR_REG, 0x005f8000, 0x005f8fff),
|
||||
MEMORY_REGION(PVR_PALETTE, 0x005f9000, 0x005f9fff),
|
||||
MEMORY_REGION(MODEM_REG, 0x00600000, 0x0067ffff),
|
||||
MEMORY_REGION(AICA_REG, 0x00700000, 0x00710fff),
|
||||
MEMORY_REGION(AUDIO_RAM, 0x00800000, 0x009fffff),
|
||||
MEMORY_REGION(EXPDEV, 0x01000000, 0x01ffffff),
|
||||
MEMORY_REGION(PVR_VRAM32, 0x04000000, 0x047fffff),
|
||||
MEMORY_REGION(PVR_VRAM64, 0x05000000, 0x057fffff),
|
||||
MEMORY_REGION(MAIN_RAM, 0x0c000000, 0x0cffffff),
|
||||
MEMORY_REGION(TA_CMD, 0x10000000, 0x107fffff),
|
||||
MEMORY_REGION(TA_TEXTURE, 0x11000000, 0x11ffffff),
|
||||
MEMORY_REGION(UNASSIGNED, 0x14000000, 0x1bffffff),
|
||||
MEMORY_REGION(SH4_REG, 0x1c000000, 0x1fffffff),
|
||||
MEMORY_REGION(SH4_CACHE, 0x7c000000, 0x7fffffff),
|
||||
MEMORY_REGION(SH4_SQ, 0xe0000000, 0xe3ffffff)
|
||||
};
|
||||
|
||||
//
|
||||
// registers
|
||||
//
|
||||
enum { //
|
||||
R = 0x1,
|
||||
W = 0x2,
|
||||
RW = 0x3,
|
||||
UNDEFINED = 0x0
|
||||
};
|
||||
|
||||
struct Register {
|
||||
Register() : flags(RW), value(0) {}
|
||||
Register(uint8_t flags, uint32_t value) : flags(flags), value(value) {}
|
||||
|
||||
uint8_t flags;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
enum {
|
||||
#define HOLLY_REG(addr, name, flags, default, type) \
|
||||
name##_OFFSET = (addr - emu::HOLLY_REG_START) >> 2,
|
||||
#include "holly/holly_regs.inc"
|
||||
#undef HOLLY_REG
|
||||
|
||||
#define PVR_REG(addr, name, flags, default_value, type) \
|
||||
name##_OFFSET = (addr - emu::PVR_REG_START) >> 2,
|
||||
#include "holly/pvr2_regs.inc"
|
||||
#undef PVR_REG
|
||||
};
|
||||
|
||||
class Dreamcast {
|
||||
public:
|
||||
emu::Scheduler *scheduler() { return scheduler_.get(); }
|
||||
emu::Memory *memory() { return memory_.get(); }
|
||||
renderer::Backend *rb() { return rb_.get(); }
|
||||
cpu::Runtime *runtime() { return runtime_.get(); }
|
||||
cpu::SH4 *cpu() { return cpu_.get(); }
|
||||
holly::Holly *holly() { return holly_.get(); }
|
||||
holly::PVR2 *pvr() { return pvr_.get(); }
|
||||
holly::TileAccelerator *ta() { return ta_.get(); }
|
||||
holly::GDROM *gdrom() { return gdrom_.get(); }
|
||||
holly::Maple *maple() { return maple_.get(); }
|
||||
trace::TraceWriter *trace_writer() { return trace_writer_.get(); }
|
||||
|
||||
Register *holly_regs() { return holly_regs_; }
|
||||
Register *pvr_regs() { return pvr_regs_; }
|
||||
|
||||
uint8_t *audio_ram() { return audio_ram_; }
|
||||
uint8_t *palette_ram() { return palette_ram_; }
|
||||
uint8_t *video_ram() { return video_ram_; }
|
||||
|
||||
Dreamcast();
|
||||
|
||||
void Run(const char *path);
|
||||
|
||||
#define HOLLY_REG(offset, name, flags, default, type) \
|
||||
type &name{reinterpret_cast<type &>(holly_regs_[name##_OFFSET].value)};
|
||||
#include "holly/holly_regs.inc"
|
||||
#undef HOLLY_REG
|
||||
|
||||
#define PVR_REG(offset, name, flags, default, type) \
|
||||
type &name{reinterpret_cast<type &>(pvr_regs_[name##_OFFSET].value)};
|
||||
#include "holly/pvr2_regs.inc"
|
||||
#undef PVR_REG
|
||||
|
||||
private:
|
||||
bool Init();
|
||||
void InitMemory();
|
||||
void InitRegisters();
|
||||
|
||||
bool LoadBios(const char *path);
|
||||
bool LoadFlash(const char *path);
|
||||
bool LaunchBIN(const char *path);
|
||||
bool LaunchGDI(const char *path);
|
||||
|
||||
void PumpEvents();
|
||||
void ToggleTracing();
|
||||
void RenderFrame();
|
||||
|
||||
system::System sys_;
|
||||
std::unique_ptr<emu::Scheduler> scheduler_;
|
||||
std::unique_ptr<emu::Memory> memory_;
|
||||
std::unique_ptr<renderer::Backend> rb_;
|
||||
std::unique_ptr<cpu::frontend::Frontend> rt_frontend_;
|
||||
std::unique_ptr<cpu::backend::Backend> rt_backend_;
|
||||
std::unique_ptr<cpu::Runtime> runtime_;
|
||||
std::unique_ptr<cpu::SH4> cpu_;
|
||||
std::unique_ptr<holly::Holly> holly_;
|
||||
std::unique_ptr<holly::PVR2> pvr_;
|
||||
std::unique_ptr<holly::TileAccelerator> ta_;
|
||||
std::unique_ptr<holly::GDROM> gdrom_;
|
||||
std::unique_ptr<holly::Maple> maple_;
|
||||
std::unique_ptr<trace::TraceWriter> trace_writer_;
|
||||
|
||||
Register holly_regs_[HOLLY_REG_SIZE >> 2];
|
||||
Register pvr_regs_[PVR_REG_SIZE >> 2];
|
||||
|
||||
uint8_t bios_[BIOS_SIZE];
|
||||
uint8_t flash_[FLASH_SIZE];
|
||||
uint8_t ram_[MAIN_RAM_SIZE];
|
||||
uint8_t unassigned_[UNASSIGNED_SIZE];
|
||||
uint8_t modem_mem_[MODEM_REG_SIZE];
|
||||
uint8_t aica_mem_[AICA_REG_SIZE];
|
||||
uint8_t audio_ram_[AUDIO_RAM_SIZE];
|
||||
uint8_t expdev_mem_[EXPDEV_SIZE];
|
||||
uint8_t video_ram_[PVR_VRAM32_SIZE];
|
||||
uint8_t palette_ram_[PVR_PALETTE_SIZE];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,305 +0,0 @@
|
|||
#include "core/core.h"
|
||||
#include "cpu/backend/interpreter/interpreter_backend.h"
|
||||
#include "cpu/backend/x64/x64_backend.h"
|
||||
#include "cpu/frontend/sh4/sh4_frontend.h"
|
||||
#include "emu/emulator.h"
|
||||
#include "emu/profiler.h"
|
||||
#include "holly/maple_controller.h"
|
||||
#include "renderer/gl_backend.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
using namespace dreavm::cpu;
|
||||
using namespace dreavm::cpu::backend::interpreter;
|
||||
using namespace dreavm::cpu::backend::x64;
|
||||
using namespace dreavm::cpu::frontend::sh4;
|
||||
using namespace dreavm::emu;
|
||||
using namespace dreavm::holly;
|
||||
using namespace dreavm::renderer;
|
||||
using namespace dreavm::system;
|
||||
|
||||
DEFINE_string(bios, "dc_bios.bin", "Path to BIOS");
|
||||
DEFINE_string(flash, "dc_flash.bin", "Path to flash ROM");
|
||||
|
||||
Emulator::Emulator() {
|
||||
bios_ = new uint8_t[BIOS_SIZE];
|
||||
flash_ = new uint8_t[FLASH_SIZE];
|
||||
ram_ = new uint8_t[MAIN_RAM_M0_SIZE];
|
||||
unassigned_ = new uint8_t[UNASSIGNED_SIZE];
|
||||
|
||||
scheduler_ = new Scheduler();
|
||||
memory_ = new Memory();
|
||||
runtime_ = new Runtime(*memory_);
|
||||
processor_ = new SH4(*memory_);
|
||||
holly_ = new Holly(*scheduler_, *memory_, *processor_);
|
||||
rt_frontend_ = new SH4Frontend(*memory_);
|
||||
// rt_backend_ = new InterpreterBackend(*memory_);
|
||||
rt_backend_ = new X64Backend(*memory_);
|
||||
rb_ = new GLBackend(sys_);
|
||||
}
|
||||
|
||||
Emulator::~Emulator() {
|
||||
Profiler::Shutdown();
|
||||
|
||||
delete[] bios_;
|
||||
delete[] flash_;
|
||||
delete[] ram_;
|
||||
delete[] unassigned_;
|
||||
|
||||
delete scheduler_;
|
||||
delete memory_;
|
||||
delete runtime_;
|
||||
delete processor_;
|
||||
delete holly_;
|
||||
delete rt_frontend_;
|
||||
delete rt_backend_;
|
||||
delete rb_;
|
||||
}
|
||||
|
||||
void Emulator::Run(const char *path) {
|
||||
if (!Init()) {
|
||||
LOG_WARNING("Failed to initialize emulator");
|
||||
return;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
LOG_INFO("Launching %s", path);
|
||||
|
||||
if ((strstr(path, ".bin") && !LaunchBIN(path)) ||
|
||||
(strstr(path, ".gdi") && !LaunchGDI(path))) {
|
||||
LOG_WARNING("Failed to launch %s", path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const std::chrono::nanoseconds step = HZ_TO_NANO(60);
|
||||
std::chrono::nanoseconds time_remaining = std::chrono::nanoseconds(0);
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
auto last_time = current_time;
|
||||
|
||||
while (true) {
|
||||
current_time = std::chrono::high_resolution_clock::now();
|
||||
time_remaining += current_time - last_time;
|
||||
last_time = current_time;
|
||||
|
||||
if (time_remaining < step) {
|
||||
continue;
|
||||
}
|
||||
|
||||
time_remaining -= step;
|
||||
|
||||
PumpEvents();
|
||||
|
||||
scheduler_->Tick(step);
|
||||
|
||||
RenderFrame();
|
||||
}
|
||||
}
|
||||
|
||||
bool Emulator::Init() {
|
||||
InitMemory();
|
||||
|
||||
if (!sys_.Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rb_->Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Profiler::Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadBios(FLAGS_bios.c_str())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadFlash(FLAGS_flash.c_str())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!runtime_->Init(rt_frontend_, rt_backend_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!holly_->Init(rb_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!processor_->Init(runtime_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
scheduler_->AddDevice(processor_);
|
||||
|
||||
Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Emulator::InitMemory() {
|
||||
memory_->Mount(BIOS_START, BIOS_END, MIRROR_MASK, bios_);
|
||||
memory_->Mount(FLASH_START, FLASH_END, MIRROR_MASK, flash_);
|
||||
memory_->Mount(MAIN_RAM_M0_START, MAIN_RAM_M0_END, MIRROR_MASK, ram_);
|
||||
memory_->Mount(MAIN_RAM_M1_START, MAIN_RAM_M1_END, MIRROR_MASK, ram_);
|
||||
memory_->Mount(MAIN_RAM_M2_START, MAIN_RAM_M2_END, MIRROR_MASK, ram_);
|
||||
memory_->Mount(MAIN_RAM_M3_START, MAIN_RAM_M3_END, MIRROR_MASK, ram_);
|
||||
memory_->Mount(UNASSIGNED_START, UNASSIGNED_END, MIRROR_MASK, unassigned_);
|
||||
}
|
||||
|
||||
bool Emulator::LoadBios(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
LOG_WARNING("Failed to open bios at \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != BIOS_SIZE) {
|
||||
LOG_WARNING("Bios size mismatch, is %d, expected %d", size, BIOS_SIZE);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = static_cast<int>(fread(bios_, sizeof(uint8_t), size, fp));
|
||||
fclose(fp);
|
||||
|
||||
if (n != size) {
|
||||
LOG_WARNING("Bios read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Emulator::LoadFlash(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
LOG_WARNING("Failed to open flash at \"%s\"", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != FLASH_SIZE) {
|
||||
LOG_WARNING("Flash size mismatch, is %d, expected %d", size, FLASH_SIZE);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = static_cast<int>(fread(flash_, sizeof(uint8_t), size, fp));
|
||||
fclose(fp);
|
||||
|
||||
if (n != size) {
|
||||
LOG_WARNING("Flash read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Emulator::Reset() {
|
||||
memset(ram_, 0, MAIN_RAM_M0_SIZE);
|
||||
memset(unassigned_, 0, UNASSIGNED_SIZE);
|
||||
}
|
||||
|
||||
bool Emulator::LaunchBIN(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
uint8_t *data = reinterpret_cast<uint8_t *>(malloc(size));
|
||||
int n = static_cast<int>(fread(data, sizeof(uint8_t), size, fp));
|
||||
fclose(fp);
|
||||
|
||||
if (n != size) {
|
||||
free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
// load to 0x0c010000 (area 3) which is where 1ST_READ.BIN is normally
|
||||
// loaded to
|
||||
memory_->Memcpy(0x0c010000, data, size);
|
||||
free(data);
|
||||
|
||||
// restart to where the bin was loaded
|
||||
processor_->Reset(0x0c010000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Emulator::LaunchGDI(const char *path) {
|
||||
std::unique_ptr<GDI> gdi(new GDI());
|
||||
|
||||
if (!gdi->Load(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
holly_->gdrom().SetDisc(std::move(gdi));
|
||||
|
||||
// restart to bios
|
||||
processor_->Reset(0xa0000000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Emulator::PumpEvents() {
|
||||
SystemEvent ev;
|
||||
|
||||
sys_.PumpEvents();
|
||||
|
||||
while (sys_.PollEvent(&ev)) {
|
||||
switch (ev.type) {
|
||||
case SE_KEY: {
|
||||
// let the profiler take a stab at the input first
|
||||
if (!Profiler::HandleInput(ev.key.code, ev.key.value)) {
|
||||
// debug tracing
|
||||
if (ev.key.code == K_F2) {
|
||||
if (ev.key.value) {
|
||||
holly_->pvr().ToggleTracing();
|
||||
}
|
||||
}
|
||||
// else, forward to maple
|
||||
else {
|
||||
holly_->maple().HandleInput(0, ev.key.code, ev.key.value);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case SE_MOUSEMOVE: {
|
||||
Profiler::HandleMouseMove(ev.mousemove.x, ev.mousemove.y);
|
||||
} break;
|
||||
|
||||
case SE_RESIZE: {
|
||||
rb_->ResizeVideo(ev.resize.width, ev.resize.height);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderFrame() {
|
||||
rb_->BeginFrame();
|
||||
|
||||
holly_->pvr().RenderLastFrame();
|
||||
|
||||
// render stats
|
||||
char stats[512];
|
||||
snprintf(stats, sizeof(stats), "%.2f fps, %.2f vbps", holly_->pvr().fps(),
|
||||
holly_->pvr().vbps());
|
||||
rb_->RenderText2D(0, 0, 12.0f, 0xffffffff, stats);
|
||||
|
||||
// render profiler
|
||||
Profiler::Render(rb_);
|
||||
|
||||
rb_->EndFrame();
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
#ifndef EMULATOR_H
|
||||
#define EMULATOR_H
|
||||
|
||||
#include "cpu/sh4.h"
|
||||
#include "cpu/backend/backend.h"
|
||||
#include "cpu/frontend/frontend.h"
|
||||
#include "cpu/runtime.h"
|
||||
#include "holly/holly.h"
|
||||
#include "renderer/backend.h"
|
||||
#include "system/system.h"
|
||||
|
||||
namespace dreavm {
|
||||
namespace emu {
|
||||
|
||||
class Emulator {
|
||||
public:
|
||||
Emulator();
|
||||
~Emulator();
|
||||
|
||||
void Run(const char *path);
|
||||
|
||||
private:
|
||||
bool Init();
|
||||
void InitMemory();
|
||||
bool LoadBios(const char *path);
|
||||
bool LoadFlash(const char *path);
|
||||
|
||||
void Reset();
|
||||
bool LaunchBIN(const char *path);
|
||||
bool LaunchGDI(const char *path);
|
||||
void PumpEvents();
|
||||
void RenderFrame();
|
||||
|
||||
system::System sys_;
|
||||
emu::Scheduler *scheduler_;
|
||||
emu::Memory *memory_;
|
||||
cpu::Runtime *runtime_;
|
||||
cpu::SH4 *processor_;
|
||||
holly::Holly *holly_;
|
||||
cpu::frontend::Frontend *rt_frontend_;
|
||||
cpu::backend::Backend *rt_backend_;
|
||||
renderer::Backend *rb_;
|
||||
uint8_t *bios_;
|
||||
uint8_t *flash_;
|
||||
uint8_t *ram_;
|
||||
uint8_t *unassigned_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -4,6 +4,7 @@
|
|||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include "core/core.h"
|
||||
|
@ -11,40 +12,6 @@
|
|||
namespace dreavm {
|
||||
namespace emu {
|
||||
|
||||
//
|
||||
// memory map. the dreamcast uses 32-bit logical addresses, but the physical
|
||||
// address range is only 29-bits
|
||||
//
|
||||
#define MEMORY_REGION(name, start, end) \
|
||||
name##_START = start, name##_END = end, name##_SIZE = end - start + 1
|
||||
|
||||
enum {
|
||||
// ignore all modifier bits
|
||||
MIRROR_MASK = 0xe0000000,
|
||||
|
||||
// the Memory class doesn't actually mount these regions, but it's nice
|
||||
// having them listed in a single place to visualize
|
||||
MEMORY_REGION(BIOS, 0x00000000, 0x001fffff),
|
||||
MEMORY_REGION(FLASH, 0x00200000, 0x0021ffff),
|
||||
MEMORY_REGION(HOLLY_REG, 0x005f6000, 0x005f7fff),
|
||||
MEMORY_REGION(PVR_REG, 0x005f8000, 0x005f8fff),
|
||||
MEMORY_REGION(PVR_PALETTE, 0x005f9000, 0x005f9fff),
|
||||
MEMORY_REGION(MODEM_REG, 0x00600000, 0x0067ffff),
|
||||
MEMORY_REGION(AICA_REG, 0x00700000, 0x00710fff),
|
||||
MEMORY_REGION(AUDIO_RAM, 0x00800000, 0x009fffff),
|
||||
MEMORY_REGION(EXPDEV, 0x01000000, 0x01ffffff),
|
||||
MEMORY_REGION(PVR_VRAM32, 0x04000000, 0x047fffff),
|
||||
MEMORY_REGION(PVR_VRAM64, 0x05000000, 0x057fffff),
|
||||
MEMORY_REGION(MAIN_RAM_M0, 0x0c000000, 0x0cffffff),
|
||||
MEMORY_REGION(MAIN_RAM_M1, 0x0d000000, 0x0dffffff),
|
||||
MEMORY_REGION(MAIN_RAM_M2, 0x0e000000, 0x0effffff),
|
||||
MEMORY_REGION(MAIN_RAM_M3, 0x0f000000, 0x0fffffff),
|
||||
MEMORY_REGION(TA_CMD, 0x10000000, 0x107fffff),
|
||||
MEMORY_REGION(TA_TEXTURE, 0x11000000, 0x11ffffff),
|
||||
MEMORY_REGION(UNASSIGNED, 0x14000000, 0x1bffffff),
|
||||
MEMORY_REGION(SH4_REG, 0x1c000000, 0x1fffffff)
|
||||
};
|
||||
|
||||
//
|
||||
// single level page table implementation
|
||||
//
|
||||
|
@ -52,7 +19,7 @@ typedef uint8_t TableHandle;
|
|||
|
||||
enum {
|
||||
UNMAPPED = (TableHandle)0,
|
||||
PAGE_BITS = 20,
|
||||
PAGE_BITS = 22,
|
||||
OFFSET_BITS = 32 - PAGE_BITS,
|
||||
MAX_PAGE_SIZE = 1 << OFFSET_BITS,
|
||||
MAX_ENTRIES = 1 << PAGE_BITS,
|
||||
|
@ -124,14 +91,14 @@ class PageTable {
|
|||
//
|
||||
// physical memory emulation
|
||||
//
|
||||
typedef uint8_t (*R8Handler)(void *, uint32_t);
|
||||
typedef uint16_t (*R16Handler)(void *, uint32_t);
|
||||
typedef uint32_t (*R32Handler)(void *, uint32_t);
|
||||
typedef uint64_t (*R64Handler)(void *, uint32_t);
|
||||
typedef void (*W8Handler)(void *, uint32_t, uint8_t);
|
||||
typedef void (*W16Handler)(void *, uint32_t, uint16_t);
|
||||
typedef void (*W32Handler)(void *, uint32_t, uint32_t);
|
||||
typedef void (*W64Handler)(void *, uint32_t, uint64_t);
|
||||
typedef std::function<uint8_t(uint32_t)> R8Handler;
|
||||
typedef std::function<uint16_t(uint32_t)> R16Handler;
|
||||
typedef std::function<uint32_t(uint32_t)> R32Handler;
|
||||
typedef std::function<uint64_t(uint32_t)> R64Handler;
|
||||
typedef std::function<void(uint32_t, uint8_t)> W8Handler;
|
||||
typedef std::function<void(uint32_t, uint16_t)> W16Handler;
|
||||
typedef std::function<void(uint32_t, uint32_t)> W32Handler;
|
||||
typedef std::function<void(uint32_t, uint64_t)> W64Handler;
|
||||
|
||||
struct MemoryBank {
|
||||
MemoryBank()
|
||||
|
@ -139,7 +106,6 @@ struct MemoryBank {
|
|||
mirror_mask(0),
|
||||
logical_addr(0),
|
||||
physical_addr(nullptr),
|
||||
ctx(nullptr),
|
||||
r8(nullptr),
|
||||
r16(nullptr),
|
||||
r32(nullptr),
|
||||
|
@ -153,7 +119,6 @@ struct MemoryBank {
|
|||
uint32_t mirror_mask;
|
||||
uint32_t logical_addr;
|
||||
uint8_t *physical_addr;
|
||||
void *ctx;
|
||||
R8Handler r8;
|
||||
R16Handler r16;
|
||||
R32Handler r32;
|
||||
|
@ -197,13 +162,12 @@ class Memory {
|
|||
}
|
||||
|
||||
void Handle(uint32_t logical_start, uint32_t logical_end,
|
||||
uint32_t mirror_mask, void *ctx, R8Handler r8, R16Handler r16,
|
||||
uint32_t mirror_mask, R8Handler r8, R16Handler r16,
|
||||
R32Handler r32, R64Handler r64, W8Handler w8, W16Handler w16,
|
||||
W32Handler w32, W64Handler w64) {
|
||||
MemoryBank &bank = AllocBank();
|
||||
bank.mirror_mask = ~mirror_mask;
|
||||
bank.logical_addr = logical_start;
|
||||
bank.ctx = ctx;
|
||||
bank.r8 = r8;
|
||||
bank.r16 = r16;
|
||||
bank.r32 = r32;
|
||||
|
@ -299,7 +263,7 @@ class Memory {
|
|||
return bank;
|
||||
}
|
||||
|
||||
template <typename INT, INT (*MemoryBank::*HANDLER)(void *, uint32_t)>
|
||||
template <typename INT, std::function<INT(uint32_t)> MemoryBank::*HANDLER>
|
||||
inline INT ReadBytes(uint32_t addr) {
|
||||
TableHandle handle = table_.Lookup(addr);
|
||||
MemoryBank &bank = banks_[handle];
|
||||
|
@ -307,7 +271,7 @@ class Memory {
|
|||
if (bank.physical_addr) {
|
||||
return *(INT *)(bank.physical_addr + offset);
|
||||
} else if (bank.*HANDLER) {
|
||||
return (bank.*HANDLER)(bank.ctx, offset);
|
||||
return (bank.*HANDLER)(offset);
|
||||
} else {
|
||||
LOG_FATAL("Attempting to read from unmapped address 0x%x", addr);
|
||||
}
|
||||
|
@ -315,7 +279,8 @@ class Memory {
|
|||
return 0;
|
||||
}
|
||||
|
||||
template <typename INT, void (*MemoryBank::*HANDLER)(void *, uint32_t, INT)>
|
||||
template <typename INT,
|
||||
std::function<void(uint32_t, INT)> MemoryBank::*HANDLER>
|
||||
inline void WriteBytes(uint32_t addr, INT value) {
|
||||
TableHandle handle = table_.Lookup(addr);
|
||||
MemoryBank &bank = banks_[handle];
|
||||
|
@ -323,7 +288,7 @@ class Memory {
|
|||
if (bank.physical_addr) {
|
||||
*(INT *)(bank.physical_addr + offset) = value;
|
||||
} else if (bank.*HANDLER) {
|
||||
(bank.*HANDLER)(bank.ctx, offset, value);
|
||||
(bank.*HANDLER)(offset, value);
|
||||
} else {
|
||||
LOG_FATAL("Attempting to write to unmapped address 0x%x", addr);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ uint32_t Profiler::ScopeColor(const char *name) {
|
|||
return hash(std::string(name)) & 0xffffff;
|
||||
}
|
||||
|
||||
bool Profiler::Init() {
|
||||
void Profiler::Init() {
|
||||
MicroProfileOnThreadCreate("main");
|
||||
|
||||
// register and enable gpu and runtime group by default
|
||||
|
@ -38,12 +38,8 @@ bool Profiler::Init() {
|
|||
|
||||
// render time / average time bars by default
|
||||
g_MicroProfile.nBars |= MP_DRAW_TIMERS | MP_DRAW_AVERAGE | MP_DRAW_CALL_COUNT;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Profiler::Shutdown() {}
|
||||
|
||||
bool Profiler::HandleInput(Keycode key, int16_t value) {
|
||||
if (key == K_F1) {
|
||||
if (value) {
|
||||
|
|
|
@ -21,8 +21,8 @@ class Profiler {
|
|||
public:
|
||||
static uint32_t ScopeColor(const char *name);
|
||||
|
||||
static bool Init();
|
||||
static void Shutdown();
|
||||
static void Init();
|
||||
|
||||
static bool HandleInput(system::Keycode key, int16_t value);
|
||||
static bool HandleMouseMove(int x, int y);
|
||||
static void Render(renderer::Backend *backend);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "core/core.h"
|
||||
#include "holly/holly.h"
|
||||
#include "holly/gdrom.h"
|
||||
#include "emu/dreamcast.h"
|
||||
#include "holly/gdrom_replies.inc"
|
||||
|
||||
using namespace dreavm::core;
|
||||
|
@ -11,29 +10,29 @@ using namespace dreavm::holly;
|
|||
#define SWAP_24(fad) \
|
||||
(((fad & 0xff) << 16) | (fad & 0x00ff00) | ((fad & 0xff0000) >> 16))
|
||||
|
||||
GDROM::GDROM(Memory &memory, Holly &holly)
|
||||
: memory_(memory),
|
||||
holly_(holly),
|
||||
current_disc_(nullptr),
|
||||
GDROM::GDROM(Dreamcast *dc)
|
||||
: dc_(dc),
|
||||
features_{0},
|
||||
intreason_{0},
|
||||
sectnum_{0},
|
||||
byte_count_{0},
|
||||
status_{0},
|
||||
pio_idx_(0),
|
||||
pio_size_(0),
|
||||
dma_size_(0),
|
||||
state_(STATE_STANDBY) {
|
||||
dma_buffer_ = reinterpret_cast<uint8_t *>(malloc(0x1000000));
|
||||
state_(STATE_STANDBY),
|
||||
current_disc_(nullptr) {
|
||||
dma_buffer_ = new uint8_t[0x1000000];
|
||||
}
|
||||
|
||||
GDROM::~GDROM() { free(dma_buffer_); }
|
||||
GDROM::~GDROM() { delete[] dma_buffer_; }
|
||||
|
||||
bool GDROM::Init() {
|
||||
features_.full = 0;
|
||||
intreason_.full = 0;
|
||||
sectnum_.full = 0;
|
||||
byte_count_.full = 0;
|
||||
status_.full = 0;
|
||||
void GDROM::Init() {
|
||||
memory_ = dc_->memory();
|
||||
holly_ = dc_->holly();
|
||||
holly_regs_ = dc_->holly_regs();
|
||||
|
||||
SetDisc(nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GDROM::SetDisc(std::unique_ptr<Disc> disc) {
|
||||
|
@ -47,8 +46,24 @@ void GDROM::SetDisc(std::unique_ptr<Disc> disc) {
|
|||
status_.BSY = 0;
|
||||
}
|
||||
|
||||
uint32_t GDROM::ReadRegister(Register ®, uint32_t addr) {
|
||||
switch (addr) {
|
||||
uint8_t GDROM::ReadRegister8(uint32_t addr) {
|
||||
return static_cast<uint8_t>(ReadRegister32(addr));
|
||||
}
|
||||
|
||||
uint16_t GDROM::ReadRegister16(uint32_t addr) {
|
||||
return static_cast<uint16_t>(ReadRegister32(addr));
|
||||
}
|
||||
|
||||
uint32_t GDROM::ReadRegister32(uint32_t addr) {
|
||||
uint32_t offset = (0x1000 + addr) >> 2;
|
||||
Register ® = holly_regs_[offset];
|
||||
|
||||
if (!(reg.flags & R)) {
|
||||
LOG_WARNING("Invalid read access at 0x%x", addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
// gdrom regs
|
||||
case GD_ALTSTAT_DEVCTRL_OFFSET:
|
||||
// this register is the same as the status register, but it does not
|
||||
|
@ -85,7 +100,7 @@ uint32_t GDROM::ReadRegister(Register ®, uint32_t addr) {
|
|||
return 0;
|
||||
|
||||
case GD_STATUS_COMMAND_OFFSET:
|
||||
holly_.UnrequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
holly_->UnrequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
return status_.full;
|
||||
|
||||
// g1 bus regs
|
||||
|
@ -94,12 +109,27 @@ uint32_t GDROM::ReadRegister(Register ®, uint32_t addr) {
|
|||
return reg.value;
|
||||
}
|
||||
|
||||
void GDROM::WriteRegister(Register ®, uint32_t addr, uint32_t value) {
|
||||
uint32_t old_value = reg.value;
|
||||
void GDROM::WriteRegister8(uint32_t addr, uint8_t value) {
|
||||
WriteRegister32(addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
void GDROM::WriteRegister16(uint32_t addr, uint16_t value) {
|
||||
WriteRegister32(addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
void GDROM::WriteRegister32(uint32_t addr, uint32_t value) {
|
||||
uint32_t offset = (0x1000 + addr) >> 2;
|
||||
Register ® = holly_regs_[offset];
|
||||
|
||||
if (!(reg.flags & W)) {
|
||||
LOG_WARNING("Invalid write access at 0x%x", addr);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t old_value = reg.value;
|
||||
reg.value = value;
|
||||
|
||||
switch (addr) {
|
||||
switch (offset) {
|
||||
// gdrom regs
|
||||
case GD_ALTSTAT_DEVCTRL_OFFSET:
|
||||
// LOG_INFO("GD_DEVCTRL 0x%x", (uint32_t)value);
|
||||
|
@ -159,23 +189,23 @@ void GDROM::WriteRegister(Register ®, uint32_t addr, uint32_t value) {
|
|||
// SB_GDSTAR, SB_GDLEN, or SB_GDDIR register is overwritten while a DMA
|
||||
// operation is in progress, the new setting has no effect on the
|
||||
// current DMA operation.
|
||||
CHECK_EQ(holly_.SB_GDEN, 1); // dma enabled
|
||||
CHECK_EQ(holly_.SB_GDDIR, 1); // gd-rom -> system memory
|
||||
CHECK_EQ(holly_.SB_GDLEN, (uint32_t)dma_size_);
|
||||
CHECK_EQ(dc_->SB_GDEN, 1); // dma enabled
|
||||
CHECK_EQ(dc_->SB_GDDIR, 1); // gd-rom -> system memory
|
||||
CHECK_EQ(dc_->SB_GDLEN, (uint32_t)dma_size_);
|
||||
|
||||
int transfer_size = holly_.SB_GDLEN;
|
||||
uint32_t start = holly_.SB_GDSTAR;
|
||||
int transfer_size = dc_->SB_GDLEN;
|
||||
uint32_t start = dc_->SB_GDSTAR;
|
||||
|
||||
printf("GD DMA START 0x%x -> 0x%x, 0x%x bytes\n", start,
|
||||
start + transfer_size, transfer_size);
|
||||
|
||||
memory_.Memcpy(start, dma_buffer_, transfer_size);
|
||||
memory_->Memcpy(start, dma_buffer_, transfer_size);
|
||||
|
||||
// done
|
||||
holly_.SB_GDSTARD = start + transfer_size;
|
||||
holly_.SB_GDLEND = transfer_size;
|
||||
holly_.SB_GDST = 0;
|
||||
holly_.RequestInterrupt(HOLLY_INTC_G1DEINT);
|
||||
dc_->SB_GDSTARD = start + transfer_size;
|
||||
dc_->SB_GDLEND = transfer_size;
|
||||
dc_->SB_GDST = 0;
|
||||
holly_->RequestInterrupt(HOLLY_INTC_G1DEINT);
|
||||
|
||||
// finish off CD_READ command
|
||||
TriggerEvent(EV_SPI_CMD_DONE);
|
||||
|
@ -194,7 +224,7 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
|
|||
status_.DRDY = 1;
|
||||
status_.BSY = 0;
|
||||
|
||||
holly_.RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
|
||||
state_ = STATE_STANDBY;
|
||||
} break;
|
||||
|
@ -229,7 +259,7 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
|
|||
status_.DRQ = 1;
|
||||
status_.BSY = 0;
|
||||
|
||||
holly_.RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
|
||||
state_ = STATE_SPI_READ_DATA;
|
||||
} break;
|
||||
|
@ -264,7 +294,7 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
|
|||
status_.DRQ = 1;
|
||||
status_.BSY = 0;
|
||||
|
||||
holly_.RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
|
||||
state_ = STATE_SPI_WRITE_DATA;
|
||||
} break;
|
||||
|
@ -285,7 +315,7 @@ void GDROM::TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1) {
|
|||
status_.BSY = 0;
|
||||
status_.DRQ = 0;
|
||||
|
||||
holly_.RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_G1GDINT);
|
||||
|
||||
state_ = STATE_STANDBY;
|
||||
} break;
|
||||
|
|
|
@ -3,11 +3,18 @@
|
|||
|
||||
#include <memory>
|
||||
#include "emu/disc.h"
|
||||
#include "emu/memory.h"
|
||||
|
||||
namespace dreavm {
|
||||
namespace emu {
|
||||
class Dreamcast;
|
||||
class Memory;
|
||||
struct Register;
|
||||
}
|
||||
|
||||
namespace holly {
|
||||
|
||||
class Holly;
|
||||
|
||||
enum GDState { //
|
||||
STATE_STANDBY,
|
||||
STATE_SPI_READ_CMD,
|
||||
|
@ -73,12 +80,12 @@ enum AudioStatus {
|
|||
};
|
||||
|
||||
union TOCEntry {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t adr : 4;
|
||||
uint32_t ctrl : 4;
|
||||
uint32_t fad : 24;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
struct TOC {
|
||||
|
@ -121,34 +128,34 @@ enum DiscType {
|
|||
};
|
||||
|
||||
union GD_FEATURES_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t dma : 1;
|
||||
uint32_t reserved : 31;
|
||||
};
|
||||
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union GD_INTREASON_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t CoD : 1; // "0" indicates data and "1" indicates a command.
|
||||
uint32_t IO : 1; // "1" indicates transfer from device to host, and "0"
|
||||
// from host to device.
|
||||
uint32_t reserved : 30;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union GD_SECTNUM_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t status : 4;
|
||||
uint32_t format : 4;
|
||||
uint32_t reserved : 24;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union GD_STATUS_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t CHECK : 1; // Becomes "1" when an error has occurred during
|
||||
// execution of the command the previous time.
|
||||
|
@ -166,16 +173,15 @@ union GD_STATUS_T {
|
|||
// command block.
|
||||
uint32_t reserved : 24;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union GD_BYTECT_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t lo : 8;
|
||||
uint32_t hi : 8;
|
||||
uint32_t reserved : 16;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
enum SectorFormat {
|
||||
|
@ -195,27 +201,24 @@ enum DataMask {
|
|||
MASK_OTHER = 0x1
|
||||
};
|
||||
|
||||
class Holly;
|
||||
struct Register;
|
||||
|
||||
class GDROM {
|
||||
public:
|
||||
GDROM(emu::Memory &memory, Holly &holly);
|
||||
GDROM(emu::Dreamcast *dc);
|
||||
~GDROM();
|
||||
|
||||
bool Init();
|
||||
void Init();
|
||||
|
||||
void SetDisc(std::unique_ptr<emu::Disc> disc);
|
||||
|
||||
uint32_t ReadRegister(Register ®, uint32_t addr);
|
||||
void WriteRegister(Register ®, uint32_t addr, uint32_t value);
|
||||
uint8_t ReadRegister8(uint32_t addr);
|
||||
uint16_t ReadRegister16(uint32_t addr);
|
||||
uint32_t ReadRegister32(uint32_t addr);
|
||||
|
||||
void WriteRegister8(uint32_t addr, uint8_t value);
|
||||
void WriteRegister16(uint32_t addr, uint16_t value);
|
||||
void WriteRegister32(uint32_t addr, uint32_t value);
|
||||
|
||||
private:
|
||||
GD_FEATURES_T features_;
|
||||
GD_INTREASON_T intreason_;
|
||||
GD_SECTNUM_T sectnum_;
|
||||
GD_BYTECT_T byte_count_;
|
||||
GD_STATUS_T status_;
|
||||
|
||||
void TriggerEvent(GDEvent ev);
|
||||
void TriggerEvent(GDEvent ev, intptr_t arg0, intptr_t arg1);
|
||||
void ProcessATACommand(ATACommand cmd);
|
||||
|
@ -227,18 +230,26 @@ class GDROM {
|
|||
int ReadSectors(int fad, SectorFormat format, DataMask mask, int num_sectors,
|
||||
uint8_t *dst);
|
||||
|
||||
emu::Memory &memory_;
|
||||
Holly &holly_;
|
||||
std::unique_ptr<emu::Disc> current_disc_;
|
||||
emu::Dreamcast *dc_;
|
||||
emu::Memory *memory_;
|
||||
holly::Holly *holly_;
|
||||
emu::Register *holly_regs_;
|
||||
|
||||
GD_FEATURES_T features_;
|
||||
GD_INTREASON_T intreason_;
|
||||
GD_SECTNUM_T sectnum_;
|
||||
GD_BYTECT_T byte_count_;
|
||||
GD_STATUS_T status_;
|
||||
|
||||
uint8_t pio_buffer_[0xfa00];
|
||||
int pio_idx_;
|
||||
int pio_size_;
|
||||
uint8_t *dma_buffer_;
|
||||
int dma_size_;
|
||||
|
||||
GDState state_;
|
||||
int spi_read_offset_;
|
||||
|
||||
std::unique_ptr<emu::Disc> current_disc_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
#include "core/core.h"
|
||||
#include "cpu/sh4.h"
|
||||
#include "holly/gdrom.h"
|
||||
#include "holly/holly.h"
|
||||
#include "holly/maple.h"
|
||||
#include "holly/pvr2.h"
|
||||
#include "emu/dreamcast.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
using namespace dreavm::cpu;
|
||||
|
@ -12,43 +8,12 @@ using namespace dreavm::holly;
|
|||
using namespace dreavm::renderer;
|
||||
using namespace dreavm::system;
|
||||
|
||||
Holly::Holly(Scheduler &scheduler, Memory &memory, SH4 &sh4)
|
||||
: memory_(memory),
|
||||
sh4_(sh4),
|
||||
pvr_(scheduler, memory, *this),
|
||||
gdrom_(memory, *this),
|
||||
maple_(memory, sh4, *this) {
|
||||
modem_mem_ = new uint8_t[MODEM_REG_SIZE];
|
||||
aica_mem_ = new uint8_t[AICA_REG_SIZE];
|
||||
audio_ram_ = new uint8_t[AUDIO_RAM_SIZE];
|
||||
expdev_mem_ = new uint8_t[EXPDEV_SIZE];
|
||||
}
|
||||
Holly::Holly(Dreamcast *dc) : dc_(dc) {}
|
||||
|
||||
Holly::~Holly() {
|
||||
delete[] modem_mem_;
|
||||
delete[] aica_mem_;
|
||||
delete[] audio_ram_;
|
||||
delete[] expdev_mem_;
|
||||
}
|
||||
|
||||
bool Holly::Init(Backend *rb) {
|
||||
InitMemory();
|
||||
|
||||
if (!pvr_.Init(rb)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gdrom_.Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maple_.Init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
return true;
|
||||
void Holly::Init() {
|
||||
cpu_ = dc_->cpu();
|
||||
holly_regs_ = dc_->holly_regs();
|
||||
audio_ram_ = dc_->audio_ram();
|
||||
}
|
||||
|
||||
void Holly::RequestInterrupt(HollyInterrupt intr) {
|
||||
|
@ -57,20 +22,20 @@ void Holly::RequestInterrupt(HollyInterrupt intr) {
|
|||
uint32_t irq = static_cast<uint32_t>(intr & ~HOLLY_INTC_MASK);
|
||||
|
||||
if (intr == HOLLY_INTC_PCVOINT) {
|
||||
maple_.VBlank();
|
||||
dc_->maple()->VBlank();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case HOLLY_INTC_NRM:
|
||||
SB_ISTNRM |= irq;
|
||||
dc_->SB_ISTNRM |= irq;
|
||||
break;
|
||||
|
||||
case HOLLY_INTC_EXT:
|
||||
SB_ISTEXT |= irq;
|
||||
dc_->SB_ISTEXT |= irq;
|
||||
break;
|
||||
|
||||
case HOLLY_INTC_ERR:
|
||||
SB_ISTERR |= irq;
|
||||
dc_->SB_ISTERR |= irq;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -84,54 +49,40 @@ void Holly::UnrequestInterrupt(HollyInterrupt intr) {
|
|||
|
||||
switch (type) {
|
||||
case HOLLY_INTC_NRM:
|
||||
SB_ISTNRM &= ~irq;
|
||||
dc_->SB_ISTNRM &= ~irq;
|
||||
break;
|
||||
|
||||
case HOLLY_INTC_EXT:
|
||||
SB_ISTEXT &= ~irq;
|
||||
dc_->SB_ISTEXT &= ~irq;
|
||||
break;
|
||||
|
||||
case HOLLY_INTC_ERR:
|
||||
SB_ISTERR &= ~irq;
|
||||
dc_->SB_ISTERR &= ~irq;
|
||||
break;
|
||||
}
|
||||
|
||||
ForwardRequestInterrupts();
|
||||
}
|
||||
|
||||
namespace dreavm {
|
||||
namespace holly {
|
||||
|
||||
template <typename T>
|
||||
T Holly::ReadRegister(void *ctx, uint32_t addr) {
|
||||
return static_cast<T>(ReadRegister<uint32_t>(ctx, addr));
|
||||
}
|
||||
|
||||
template <>
|
||||
uint32_t Holly::ReadRegister(void *ctx, uint32_t addr) {
|
||||
Holly *holly = (Holly *)ctx;
|
||||
Register ® = holly->regs_[addr >> 2];
|
||||
uint32_t Holly::ReadRegister32(uint32_t addr) {
|
||||
uint32_t offset = addr >> 2;
|
||||
Register ® = holly_regs_[offset];
|
||||
|
||||
if (!(reg.flags & R)) {
|
||||
LOG_FATAL("Invalid read access at 0x%x", addr);
|
||||
LOG_WARNING("Invalid read access at 0x%x", addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (addr >= SB_MDSTAR_OFFSET && addr <= SB_MRXDBD_OFFSET) {
|
||||
return holly->maple_.ReadRegister(reg, addr);
|
||||
} else if (addr >= GD_ALTSTAT_DEVCTRL_OFFSET && addr <= SB_GDLEND_OFFSET) {
|
||||
return holly->gdrom_.ReadRegister(reg, addr);
|
||||
}
|
||||
|
||||
switch (reg.offset) {
|
||||
switch (offset) {
|
||||
case SB_ISTNRM_OFFSET: {
|
||||
// Note that the two highest bits indicate the OR'ed result of all of the
|
||||
// bits in SB_ISTEXT and SB_ISTERR, respectively, and writes to these two
|
||||
// bits are ignored.
|
||||
uint32_t v = reg.value & 0x3fffffff;
|
||||
if (holly->SB_ISTEXT) {
|
||||
if (dc_->SB_ISTEXT) {
|
||||
v |= 0x40000000;
|
||||
}
|
||||
if (holly->SB_ISTERR) {
|
||||
if (dc_->SB_ISTERR) {
|
||||
v |= 0x80000000;
|
||||
}
|
||||
return v;
|
||||
|
@ -141,38 +92,25 @@ uint32_t Holly::ReadRegister(void *ctx, uint32_t addr) {
|
|||
return reg.value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Holly::WriteRegister(void *ctx, uint32_t addr, T value) {
|
||||
WriteRegister<uint32_t>(ctx, addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
void Holly::WriteRegister(void *ctx, uint32_t addr, uint32_t value) {
|
||||
Holly *holly = (Holly *)ctx;
|
||||
Register ® = holly->regs_[addr >> 2];
|
||||
void Holly::WriteRegister32(uint32_t addr, uint32_t value) {
|
||||
uint32_t offset = addr >> 2;
|
||||
Register ® = holly_regs_[offset];
|
||||
|
||||
if (!(reg.flags & W)) {
|
||||
LOG_FATAL("Invalid write access at 0x%x", addr);
|
||||
}
|
||||
|
||||
if (addr >= SB_MDSTAR_OFFSET && addr <= SB_MRXDBD_OFFSET) {
|
||||
holly->maple_.WriteRegister(reg, addr, value);
|
||||
return;
|
||||
} else if (addr >= GD_ALTSTAT_DEVCTRL_OFFSET && addr <= SB_GDLEND_OFFSET) {
|
||||
holly->gdrom_.WriteRegister(reg, addr, value);
|
||||
LOG_WARNING("Invalid write access at 0x%x", addr);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t old = reg.value;
|
||||
reg.value = value;
|
||||
|
||||
switch (reg.offset) {
|
||||
switch (offset) {
|
||||
case SB_ISTNRM_OFFSET:
|
||||
case SB_ISTEXT_OFFSET:
|
||||
case SB_ISTERR_OFFSET: {
|
||||
// writing a 1 clears the interrupt
|
||||
reg.value = old & ~value;
|
||||
holly->ForwardRequestInterrupts();
|
||||
ForwardRequestInterrupts();
|
||||
} break;
|
||||
|
||||
case SB_IML2NRM_OFFSET:
|
||||
|
@ -184,18 +122,18 @@ void Holly::WriteRegister(void *ctx, uint32_t addr, uint32_t value) {
|
|||
case SB_IML6NRM_OFFSET:
|
||||
case SB_IML6EXT_OFFSET:
|
||||
case SB_IML6ERR_OFFSET:
|
||||
holly->ForwardRequestInterrupts();
|
||||
ForwardRequestInterrupts();
|
||||
break;
|
||||
|
||||
case SB_C2DST_OFFSET:
|
||||
if (value) {
|
||||
holly->CH2DMATransfer();
|
||||
CH2DMATransfer();
|
||||
}
|
||||
break;
|
||||
|
||||
case SB_SDST_OFFSET:
|
||||
if (value) {
|
||||
holly->SortDMATransfer();
|
||||
SortDMATransfer();
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -219,76 +157,30 @@ void Holly::WriteRegister(void *ctx, uint32_t addr, uint32_t value) {
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T Holly::ReadAudio(void *ctx, uint32_t addr) {
|
||||
Holly *holly = (Holly *)ctx;
|
||||
return *reinterpret_cast<T *>(holly->audio_ram_[addr]);
|
||||
}
|
||||
|
||||
template <>
|
||||
uint32_t Holly::ReadAudio(void *ctx, uint32_t addr) {
|
||||
Holly *holly = (Holly *)ctx;
|
||||
|
||||
uint32_t Holly::ReadAudio32(uint32_t addr) {
|
||||
// FIXME temp hack for unsupported audio regs hanging in Crazy Taxi 2
|
||||
if (addr == 0x5c) {
|
||||
return 0x54494e49;
|
||||
}
|
||||
|
||||
return *reinterpret_cast<uint32_t *>(&holly->audio_ram_[addr]);
|
||||
return *reinterpret_cast<uint32_t *>(&audio_ram_[addr]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Holly::WriteAudio(void *ctx, uint32_t addr, T value) {
|
||||
Holly *holly = (Holly *)ctx;
|
||||
*reinterpret_cast<T *>(&holly->audio_ram_[addr]) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Holly::InitMemory() {
|
||||
memory_.Handle(HOLLY_REG_START, HOLLY_REG_END, MIRROR_MASK, this,
|
||||
&Holly::ReadRegister<uint8_t>, &Holly::ReadRegister<uint16_t>,
|
||||
&Holly::ReadRegister<uint32_t>, nullptr,
|
||||
&Holly::WriteRegister<uint8_t>,
|
||||
&Holly::WriteRegister<uint16_t>,
|
||||
&Holly::WriteRegister<uint32_t>, nullptr);
|
||||
memory_.Mount(MODEM_REG_START, MODEM_REG_END, MIRROR_MASK, modem_mem_);
|
||||
memory_.Mount(AICA_REG_START, AICA_REG_END, MIRROR_MASK, aica_mem_);
|
||||
memory_.Handle(AUDIO_RAM_START, AUDIO_RAM_END, MIRROR_MASK, this,
|
||||
&Holly::ReadAudio<uint8_t>, &Holly::ReadAudio<uint16_t>,
|
||||
&Holly::ReadAudio<uint32_t>, nullptr,
|
||||
&Holly::WriteAudio<uint8_t>, &Holly::WriteAudio<uint16_t>,
|
||||
&Holly::WriteAudio<uint32_t>, nullptr);
|
||||
memory_.Mount(EXPDEV_START, EXPDEV_END, MIRROR_MASK, expdev_mem_);
|
||||
}
|
||||
|
||||
void Holly::Reset() {
|
||||
memset(modem_mem_, 0, MODEM_REG_SIZE);
|
||||
memset(aica_mem_, 0, AICA_REG_SIZE);
|
||||
memset(audio_ram_, 0, AUDIO_RAM_SIZE);
|
||||
memset(expdev_mem_, 0, EXPDEV_SIZE);
|
||||
|
||||
// FIXME temp hack for unsupported audio regs hanging in Crazy Taxi
|
||||
*reinterpret_cast<uint32_t *>(&audio_ram_[0x5c]) = 0x54494e49;
|
||||
|
||||
// initialize registers
|
||||
#define HOLLY_REG(addr, name, flags, default, type) \
|
||||
regs_[name##_OFFSET >> 2] = {name##_OFFSET, flags, default};
|
||||
#include "holly/holly_regs.inc"
|
||||
#undef HOLLY_REG
|
||||
void Holly::WriteAudio32(uint32_t addr, uint32_t value) {
|
||||
*reinterpret_cast<uint32_t *>(&audio_ram_[addr]) = value;
|
||||
}
|
||||
|
||||
// FIXME what are SB_LMMODE0 / SB_LMMODE1
|
||||
void Holly::CH2DMATransfer() {
|
||||
sh4_.DDT(2, DDT_W, SB_C2DSTAT);
|
||||
cpu_->DDT(2, DDT_W, dc_->SB_C2DSTAT);
|
||||
|
||||
SB_C2DLEN = 0;
|
||||
SB_C2DST = 0;
|
||||
dc_->SB_C2DLEN = 0;
|
||||
dc_->SB_C2DST = 0;
|
||||
RequestInterrupt(HOLLY_INTC_DTDE2INT);
|
||||
}
|
||||
|
||||
void Holly::SortDMATransfer() {
|
||||
SB_SDST = 0;
|
||||
dc_->SB_SDST = 0;
|
||||
RequestInterrupt(HOLLY_INTC_DTDESINT);
|
||||
}
|
||||
|
||||
|
@ -296,29 +188,32 @@ void Holly::ForwardRequestInterrupts() {
|
|||
// trigger the respective level-encoded interrupt on the sh4 interrupt
|
||||
// controller
|
||||
{
|
||||
if ((SB_ISTNRM & SB_IML6NRM) || (SB_ISTERR & SB_IML6ERR) ||
|
||||
(SB_ISTEXT & SB_IML6EXT)) {
|
||||
sh4_.RequestInterrupt(SH4_INTC_IRL_9);
|
||||
if ((dc_->SB_ISTNRM & dc_->SB_IML6NRM) ||
|
||||
(dc_->SB_ISTERR & dc_->SB_IML6ERR) ||
|
||||
(dc_->SB_ISTEXT & dc_->SB_IML6EXT)) {
|
||||
cpu_->RequestInterrupt(SH4_INTC_IRL_9);
|
||||
} else {
|
||||
sh4_.UnrequestInterrupt(SH4_INTC_IRL_9);
|
||||
cpu_->UnrequestInterrupt(SH4_INTC_IRL_9);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if ((SB_ISTNRM & SB_IML4NRM) || (SB_ISTERR & SB_IML4ERR) ||
|
||||
(SB_ISTEXT & SB_IML4EXT)) {
|
||||
sh4_.RequestInterrupt(SH4_INTC_IRL_11);
|
||||
if ((dc_->SB_ISTNRM & dc_->SB_IML4NRM) ||
|
||||
(dc_->SB_ISTERR & dc_->SB_IML4ERR) ||
|
||||
(dc_->SB_ISTEXT & dc_->SB_IML4EXT)) {
|
||||
cpu_->RequestInterrupt(SH4_INTC_IRL_11);
|
||||
} else {
|
||||
sh4_.UnrequestInterrupt(SH4_INTC_IRL_11);
|
||||
cpu_->UnrequestInterrupt(SH4_INTC_IRL_11);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if ((SB_ISTNRM & SB_IML2NRM) || (SB_ISTERR & SB_IML2ERR) ||
|
||||
(SB_ISTEXT & SB_IML2EXT)) {
|
||||
sh4_.RequestInterrupt(SH4_INTC_IRL_13);
|
||||
if ((dc_->SB_ISTNRM & dc_->SB_IML2NRM) ||
|
||||
(dc_->SB_ISTERR & dc_->SB_IML2ERR) ||
|
||||
(dc_->SB_ISTEXT & dc_->SB_IML2EXT)) {
|
||||
cpu_->RequestInterrupt(SH4_INTC_IRL_13);
|
||||
} else {
|
||||
sh4_.UnrequestInterrupt(SH4_INTC_IRL_13);
|
||||
cpu_->UnrequestInterrupt(SH4_INTC_IRL_13);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,17 @@
|
|||
#ifndef HOLLY_H
|
||||
#define HOLLY_H
|
||||
|
||||
#include "emu/memory.h"
|
||||
#include "emu/scheduler.h"
|
||||
#include "holly/gdrom.h"
|
||||
#include "holly/maple.h"
|
||||
#include "holly/pvr2.h"
|
||||
#include "holly/register.h"
|
||||
#include "renderer/backend.h"
|
||||
#include "system/keys.h"
|
||||
|
||||
namespace dreavm {
|
||||
|
||||
namespace cpu {
|
||||
class SH4;
|
||||
}
|
||||
namespace emu {
|
||||
class Dreamcast;
|
||||
struct Register;
|
||||
}
|
||||
|
||||
namespace holly {
|
||||
|
||||
// registers
|
||||
enum {
|
||||
#define HOLLY_REG(addr, name, flags, default, type) \
|
||||
name##_OFFSET = addr - emu::HOLLY_REG_START,
|
||||
#include "holly/holly_regs.inc"
|
||||
#undef HOLLY_REG
|
||||
};
|
||||
|
||||
// interrupts
|
||||
#define HOLLY_INTC_MASK 0xf00000000
|
||||
|
||||
|
@ -162,53 +148,29 @@ enum HollyInterrupt : uint64_t {
|
|||
};
|
||||
|
||||
class Holly {
|
||||
friend class GDROM;
|
||||
friend class Maple;
|
||||
|
||||
public:
|
||||
Holly(emu::Scheduler &scheduler, emu::Memory &memory, cpu::SH4 &sh4);
|
||||
~Holly();
|
||||
Holly(emu::Dreamcast *dc);
|
||||
|
||||
PVR2 &pvr() { return pvr_; }
|
||||
GDROM &gdrom() { return gdrom_; }
|
||||
Maple &maple() { return maple_; }
|
||||
void Init();
|
||||
|
||||
bool Init(renderer::Backend *rb);
|
||||
void RequestInterrupt(HollyInterrupt intr);
|
||||
void UnrequestInterrupt(HollyInterrupt intr);
|
||||
|
||||
uint32_t ReadRegister32(uint32_t addr);
|
||||
void WriteRegister32(uint32_t addr, uint32_t value);
|
||||
|
||||
uint32_t ReadAudio32(uint32_t addr);
|
||||
void WriteAudio32(uint32_t addr, uint32_t value);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static T ReadRegister(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteRegister(void *ctx, uint32_t addr, T value);
|
||||
template <typename T>
|
||||
static T ReadAudio(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteAudio(void *ctx, uint32_t addr, T value);
|
||||
|
||||
void InitMemory();
|
||||
|
||||
void Reset();
|
||||
void CH2DMATransfer();
|
||||
void SortDMATransfer();
|
||||
void ForwardRequestInterrupts();
|
||||
|
||||
emu::Memory &memory_;
|
||||
cpu::SH4 &sh4_;
|
||||
PVR2 pvr_;
|
||||
GDROM gdrom_;
|
||||
Maple maple_;
|
||||
Register regs_[emu::HOLLY_REG_SIZE >> 2];
|
||||
uint8_t *modem_mem_;
|
||||
uint8_t *aica_mem_;
|
||||
emu::Dreamcast *dc_;
|
||||
cpu::SH4 *cpu_;
|
||||
emu::Register *holly_regs_;
|
||||
uint8_t *audio_ram_;
|
||||
uint8_t *expdev_mem_;
|
||||
|
||||
#define HOLLY_REG(offset, name, flags, default, type) \
|
||||
type &name{reinterpret_cast<type &>(regs_[name##_OFFSET >> 2].value)};
|
||||
#include "holly/holly_regs.inc"
|
||||
#undef HOLLY_REG
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "core/core.h"
|
||||
#include "holly/holly.h"
|
||||
#include "holly/maple.h"
|
||||
#include "emu/dreamcast.h"
|
||||
#include "holly/maple_controller.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
|
@ -9,13 +8,16 @@ using namespace dreavm::emu;
|
|||
using namespace dreavm::holly;
|
||||
using namespace dreavm::system;
|
||||
|
||||
Maple::Maple(Memory &memory, SH4 &sh4, Holly &holly)
|
||||
: memory_(memory), /*sh4_(sh4),*/ holly_(holly), devices_() {
|
||||
Maple::Maple(Dreamcast *dc) : dc_(dc), devices_() {
|
||||
// default controller device
|
||||
devices_[0] = std::unique_ptr<MapleController>(new MapleController());
|
||||
}
|
||||
|
||||
bool Maple::Init() { return true; }
|
||||
void Maple::Init() {
|
||||
memory_ = dc_->memory();
|
||||
holly_ = dc_->holly();
|
||||
holly_regs_ = dc_->holly_regs();
|
||||
}
|
||||
|
||||
bool Maple::HandleInput(int port, Keycode key, int16_t value) {
|
||||
CHECK_LT(port, MAX_PORTS);
|
||||
|
@ -30,8 +32,8 @@ bool Maple::HandleInput(int port, Keycode key, int16_t value) {
|
|||
// in synchronization with the V-BLANK signal. These methods are selected
|
||||
// through the trigger selection register (SB_MDTSEL).
|
||||
void Maple::VBlank() {
|
||||
uint32_t enabled = holly_.SB_MDEN;
|
||||
uint32_t vblank_initiate = holly_.SB_MDTSEL;
|
||||
uint32_t enabled = dc_->SB_MDEN;
|
||||
uint32_t vblank_initiate = dc_->SB_MDTSEL;
|
||||
|
||||
if (enabled && vblank_initiate) {
|
||||
StartDMA();
|
||||
|
@ -40,39 +42,59 @@ void Maple::VBlank() {
|
|||
// TODO maple vblank interrupt?
|
||||
}
|
||||
|
||||
uint32_t Maple::ReadRegister(Register ®, uint32_t addr) { return reg.value; }
|
||||
uint32_t Maple::ReadRegister32(uint32_t addr) {
|
||||
uint32_t offset = (0xc00 + addr) >> 2;
|
||||
Register ® = holly_regs_[offset];
|
||||
|
||||
void Maple::WriteRegister(Register ®, uint32_t addr, uint32_t value) {
|
||||
// T old = reg.value;
|
||||
if (!(reg.flags & R)) {
|
||||
LOG_WARNING("Invalid read access at 0x%x", addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reg.value;
|
||||
}
|
||||
|
||||
void Maple::WriteRegister32(uint32_t addr, uint32_t value) {
|
||||
uint32_t offset = (0xc00 + addr) >> 2;
|
||||
Register ® = holly_regs_[offset];
|
||||
|
||||
if (!(reg.flags & W)) {
|
||||
LOG_WARNING("Invalid write access at 0x%x", addr);
|
||||
return;
|
||||
}
|
||||
|
||||
// uint32_t old = reg.value;
|
||||
reg.value = value;
|
||||
|
||||
if (addr == SB_MDST_OFFSET) {
|
||||
uint32_t enabled = holly_.SB_MDEN;
|
||||
if (enabled) {
|
||||
if (value) {
|
||||
StartDMA();
|
||||
switch (offset) {
|
||||
case SB_MDST_OFFSET: {
|
||||
uint32_t enabled = dc_->SB_MDEN;
|
||||
if (enabled) {
|
||||
if (value) {
|
||||
StartDMA();
|
||||
}
|
||||
} else {
|
||||
reg.value = 0;
|
||||
}
|
||||
} else {
|
||||
reg.value = 0;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void Maple::StartDMA() {
|
||||
uint32_t start_addr = holly_.SB_MDSTAR;
|
||||
uint32_t start_addr = dc_->SB_MDSTAR;
|
||||
MapleTransferDesc desc;
|
||||
MapleFrame frame, res;
|
||||
|
||||
do {
|
||||
desc.full = memory_.R64(start_addr);
|
||||
desc.full = memory_->R64(start_addr);
|
||||
start_addr += 8;
|
||||
|
||||
// read input
|
||||
frame.header.full = memory_.R32(start_addr);
|
||||
frame.header.full = memory_->R32(start_addr);
|
||||
start_addr += 4;
|
||||
|
||||
for (uint32_t i = 0; i < frame.header.num_words; i++) {
|
||||
frame.params[i] = memory_.R32(start_addr);
|
||||
frame.params[i] = memory_->R32(start_addr);
|
||||
start_addr += 4;
|
||||
}
|
||||
|
||||
|
@ -80,18 +102,18 @@ void Maple::StartDMA() {
|
|||
std::unique_ptr<MapleDevice> &dev = devices_[desc.port];
|
||||
|
||||
if (dev && dev->HandleFrame(frame, res)) {
|
||||
memory_.W32(desc.result_addr, res.header.full);
|
||||
memory_->W32(desc.result_addr, res.header.full);
|
||||
desc.result_addr += 4;
|
||||
|
||||
for (uint32_t i = 0; i < res.header.num_words; i++) {
|
||||
memory_.W32(desc.result_addr, res.params[i]);
|
||||
memory_->W32(desc.result_addr, res.params[i]);
|
||||
desc.result_addr += 4;
|
||||
}
|
||||
} else {
|
||||
memory_.W32(desc.result_addr, 0xffffffff);
|
||||
memory_->W32(desc.result_addr, 0xffffffff);
|
||||
}
|
||||
} while (!desc.last);
|
||||
|
||||
holly_.SB_MDST = 0;
|
||||
holly_.RequestInterrupt(HOLLY_INTC_MDEINT);
|
||||
dc_->SB_MDST = 0;
|
||||
holly_->RequestInterrupt(HOLLY_INTC_MDEINT);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
#ifndef MAPLE_H
|
||||
#define MAPLE_H
|
||||
|
||||
#include "emu/memory.h"
|
||||
#include "holly/register.h"
|
||||
#include "system/keys.h"
|
||||
|
||||
namespace dreavm {
|
||||
|
||||
namespace cpu {
|
||||
class SH4;
|
||||
namespace emu {
|
||||
class Dreamcast;
|
||||
class Memory;
|
||||
struct Register;
|
||||
}
|
||||
|
||||
namespace holly {
|
||||
|
||||
class Holly;
|
||||
struct Register;
|
||||
|
||||
enum { MAX_PORTS = 4 };
|
||||
|
||||
|
@ -101,22 +99,25 @@ class MapleDevice {
|
|||
|
||||
class Maple {
|
||||
public:
|
||||
Maple(emu::Memory &memory, cpu::SH4 &sh4, Holly &holly);
|
||||
Maple(emu::Dreamcast *dc);
|
||||
|
||||
void Init();
|
||||
|
||||
bool Init();
|
||||
bool HandleInput(int port, system::Keycode key, int16_t value);
|
||||
void VBlank();
|
||||
|
||||
uint32_t ReadRegister(Register ®, uint32_t addr);
|
||||
void WriteRegister(Register ®, uint32_t addr, uint32_t value);
|
||||
uint32_t ReadRegister32(uint32_t addr);
|
||||
void WriteRegister32(uint32_t addr, uint32_t value);
|
||||
|
||||
private:
|
||||
bool HandleFrame(const MapleFrame &frame, MapleFrame &res);
|
||||
void StartDMA();
|
||||
|
||||
emu::Memory &memory_;
|
||||
// cpu::SH4 &sh4_;
|
||||
Holly &holly_;
|
||||
emu::Dreamcast *dc_;
|
||||
emu::Memory *memory_;
|
||||
holly::Holly *holly_;
|
||||
emu::Register *holly_regs_;
|
||||
|
||||
std::unique_ptr<MapleDevice> devices_[MAX_PORTS];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <json11.hpp>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "core/core.h"
|
||||
#include "holly/maple_controller.h"
|
||||
|
||||
using namespace dreavm::holly;
|
||||
|
|
|
@ -1,48 +1,81 @@
|
|||
#include "holly/holly.h"
|
||||
#include "holly/pvr2.h"
|
||||
#include "core/core.h"
|
||||
#include "emu/dreamcast.h"
|
||||
|
||||
using namespace dreavm::cpu;
|
||||
using namespace dreavm::emu;
|
||||
using namespace dreavm::holly;
|
||||
using namespace dreavm::renderer;
|
||||
|
||||
PVR2::PVR2(Scheduler &scheduler, Memory &memory, Holly &holly)
|
||||
: scheduler_(scheduler),
|
||||
memory_(memory),
|
||||
holly_(holly),
|
||||
ta_(memory, holly, *this),
|
||||
PVR2::PVR2(Dreamcast *dc)
|
||||
: dc_(dc),
|
||||
line_timer_(INVALID_HANDLE),
|
||||
current_scanline_(0),
|
||||
fps_(0),
|
||||
vbps_(0) {
|
||||
vram_ = new uint8_t[PVR_VRAM32_SIZE];
|
||||
pram_ = new uint8_t[PVR_PALETTE_SIZE];
|
||||
vbps_(0) {}
|
||||
|
||||
void PVR2::Init() {
|
||||
scheduler_ = dc_->scheduler();
|
||||
holly_ = dc_->holly();
|
||||
ta_ = dc_->ta();
|
||||
pvr_regs_ = dc_->pvr_regs();
|
||||
video_ram_ = dc_->video_ram();
|
||||
|
||||
ReconfigureSPG();
|
||||
}
|
||||
|
||||
PVR2::~PVR2() {
|
||||
delete[] vram_;
|
||||
delete[] pram_;
|
||||
}
|
||||
uint32_t PVR2::ReadRegister32(uint32_t addr) {
|
||||
uint32_t offset = addr >> 2;
|
||||
Register ® = pvr_regs_[offset];
|
||||
|
||||
bool PVR2::Init(Backend *rb) {
|
||||
InitMemory();
|
||||
|
||||
if (!ta_.Init(rb)) {
|
||||
return false;
|
||||
if (!(reg.flags & R)) {
|
||||
LOG_WARNING("Invalid read access at 0x%x", addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reset();
|
||||
ReconfigureSPG();
|
||||
|
||||
return true;
|
||||
return reg.value;
|
||||
}
|
||||
|
||||
void PVR2::RenderLastFrame() { ta_.RenderLastContext(); }
|
||||
void PVR2::WriteRegister32(uint32_t addr, uint32_t value) {
|
||||
uint32_t offset = addr >> 2;
|
||||
Register ® = pvr_regs_[offset];
|
||||
|
||||
void PVR2::ToggleTracing() { ta_.ToggleTracing(); }
|
||||
if (!(reg.flags & W)) {
|
||||
LOG_WARNING("Invalid write access at 0x%x", addr);
|
||||
return;
|
||||
}
|
||||
|
||||
namespace dreavm {
|
||||
namespace holly {
|
||||
reg.value = value;
|
||||
|
||||
switch (offset) {
|
||||
case SOFTRESET_OFFSET: {
|
||||
bool reset_ta = value & 0x1;
|
||||
if (reset_ta) {
|
||||
ta_->SoftReset();
|
||||
}
|
||||
} break;
|
||||
|
||||
case TA_LIST_INIT_OFFSET: {
|
||||
ta_->InitContext(dc_->TA_ISP_BASE.base_address);
|
||||
} break;
|
||||
|
||||
case STARTRENDER_OFFSET: {
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
now - last_frame_);
|
||||
last_frame_ = now;
|
||||
fps_ = 1000000000.0f / delta.count();
|
||||
}
|
||||
|
||||
ta_->SaveLastContext(dc_->PARAM_BASE.base_address);
|
||||
} break;
|
||||
|
||||
case SPG_LOAD_OFFSET:
|
||||
case FB_R_CTRL_OFFSET: {
|
||||
ReconfigureSPG();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// the dreamcast has 8MB of vram, split into two 4MB banks, with two ways of
|
||||
// accessing it:
|
||||
|
@ -65,104 +98,29 @@ static uint32_t MAP64(uint32_t addr) {
|
|||
(addr & 0x3));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T PVR2::ReadInterleaved(void *ctx, uint32_t addr) {
|
||||
PVR2 *pvr = reinterpret_cast<PVR2 *>(ctx);
|
||||
uint8_t PVR2::ReadInterleaved8(uint32_t addr) {
|
||||
addr = MAP64(addr);
|
||||
return *reinterpret_cast<T *>(&pvr->vram_[addr]);
|
||||
return *reinterpret_cast<uint8_t *>(&video_ram_[addr]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PVR2::WriteInterleaved(void *ctx, uint32_t addr, T value) {
|
||||
PVR2 *pvr = reinterpret_cast<PVR2 *>(ctx);
|
||||
uint16_t PVR2::ReadInterleaved16(uint32_t addr) {
|
||||
addr = MAP64(addr);
|
||||
*reinterpret_cast<T *>(&pvr->vram_[addr]) = value;
|
||||
return *reinterpret_cast<uint16_t *>(&video_ram_[addr]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T PVR2::ReadRegister(void *ctx, uint32_t addr) {
|
||||
return static_cast<T>(ReadRegister<uint32_t>(ctx, addr));
|
||||
uint32_t PVR2::ReadInterleaved32(uint32_t addr) {
|
||||
addr = MAP64(addr);
|
||||
return *reinterpret_cast<uint32_t *>(&video_ram_[addr]);
|
||||
}
|
||||
|
||||
template <>
|
||||
uint32_t PVR2::ReadRegister(void *ctx, uint32_t addr) {
|
||||
PVR2 *pvr = (PVR2 *)ctx;
|
||||
Register ® = pvr->regs_[addr >> 2];
|
||||
|
||||
if (!(reg.flags & R)) {
|
||||
LOG_WARNING("Invalid read access at 0x%x", addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reg.value;
|
||||
void PVR2::WriteInterleaved16(uint32_t addr, uint16_t value) {
|
||||
addr = MAP64(addr);
|
||||
*reinterpret_cast<uint16_t *>(&video_ram_[addr]) = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PVR2::WriteRegister(void *ctx, uint32_t addr, T value) {
|
||||
WriteRegister<uint32_t>(ctx, addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
void PVR2::WriteRegister(void *ctx, uint32_t addr, uint32_t value) {
|
||||
PVR2 *pvr = (PVR2 *)ctx;
|
||||
Register ® = pvr->regs_[addr >> 2];
|
||||
|
||||
if (!(reg.flags & W)) {
|
||||
LOG_WARNING("Invalid write access at 0x%x", addr);
|
||||
return;
|
||||
}
|
||||
|
||||
reg.value = value;
|
||||
|
||||
if (reg.offset == SOFTRESET_OFFSET) {
|
||||
bool reset_ta = value & 0x1;
|
||||
if (reset_ta) {
|
||||
pvr->ta_.SoftReset();
|
||||
}
|
||||
} else if (reg.offset == TA_LIST_INIT_OFFSET) {
|
||||
pvr->ta_.InitContext(pvr->TA_ISP_BASE.base_address);
|
||||
} else if (reg.offset == STARTRENDER_OFFSET) {
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
now - pvr->last_frame_);
|
||||
pvr->last_frame_ = now;
|
||||
pvr->fps_ = 1000000000.0f / delta.count();
|
||||
}
|
||||
|
||||
pvr->ta_.SaveLastContext(pvr->PARAM_BASE.base_address);
|
||||
} else if (reg.offset == SPG_LOAD_OFFSET || reg.offset == FB_R_CTRL_OFFSET) {
|
||||
pvr->ReconfigureSPG();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PVR2::InitMemory() {
|
||||
memory_.Mount(PVR_VRAM32_START, PVR_VRAM32_END, MIRROR_MASK, vram_);
|
||||
memory_.Handle(PVR_VRAM64_START, PVR_VRAM64_END, MIRROR_MASK, this,
|
||||
&PVR2::ReadInterleaved<uint8_t>,
|
||||
&PVR2::ReadInterleaved<uint16_t>,
|
||||
&PVR2::ReadInterleaved<uint32_t>, nullptr, nullptr,
|
||||
&PVR2::WriteInterleaved<uint16_t>,
|
||||
&PVR2::WriteInterleaved<uint32_t>, nullptr);
|
||||
memory_.Handle(PVR_REG_START, PVR_REG_END, MIRROR_MASK, this,
|
||||
&PVR2::ReadRegister<uint8_t>, &PVR2::ReadRegister<uint16_t>,
|
||||
&PVR2::ReadRegister<uint32_t>, nullptr,
|
||||
&PVR2::WriteRegister<uint8_t>, &PVR2::WriteRegister<uint16_t>,
|
||||
&PVR2::WriteRegister<uint32_t>, nullptr);
|
||||
memory_.Mount(PVR_PALETTE_START, PVR_PALETTE_END, MIRROR_MASK, pram_);
|
||||
}
|
||||
|
||||
void PVR2::Reset() {
|
||||
memset(vram_, 0, PVR_VRAM32_SIZE);
|
||||
memset(pram_, 0, PVR_PALETTE_SIZE);
|
||||
|
||||
// initialize registers
|
||||
#define PVR_REG(addr, name, flags, default, type) \
|
||||
regs_[name##_OFFSET >> 2] = {name##_OFFSET, flags, default};
|
||||
#include "holly/pvr2_regs.inc"
|
||||
#undef PVR_REG
|
||||
void PVR2::WriteInterleaved32(uint32_t addr, uint32_t value) {
|
||||
addr = MAP64(addr);
|
||||
*reinterpret_cast<uint32_t *>(&video_ram_[addr]) = value;
|
||||
}
|
||||
|
||||
void PVR2::ReconfigureSPG() {
|
||||
|
@ -174,57 +132,58 @@ void PVR2::ReconfigureSPG() {
|
|||
// specify "number of lines per field/2 - 1." (default = 0x106)
|
||||
// PAL interlaced = vcount 624, vbstart 620, vbend 44. why isn't vcount ~200?
|
||||
// VGA non-interlaced = vcount 524, vbstart 520, vbend 40
|
||||
int pixel_clock = FB_R_CTRL.vclk_div ? PIXEL_CLOCK : (PIXEL_CLOCK / 2);
|
||||
int line_clock = pixel_clock / (SPG_LOAD.hcount + 1);
|
||||
int pixel_clock = dc_->FB_R_CTRL.vclk_div ? PIXEL_CLOCK : (PIXEL_CLOCK / 2);
|
||||
int line_clock = pixel_clock / (dc_->SPG_LOAD.hcount + 1);
|
||||
|
||||
// HACK seems to get interlaced mode to vsync reasonably
|
||||
if (SPG_CONTROL.interlace) {
|
||||
if (dc_->SPG_CONTROL.interlace) {
|
||||
line_clock *= 2;
|
||||
}
|
||||
|
||||
LOG_INFO(
|
||||
"ReconfigureSPG: pixel_clock %d, line_clock %d, vcount %d, hcount %d, "
|
||||
"interlace %d, vbstart %d, vbend %d",
|
||||
pixel_clock, line_clock, SPG_LOAD.vcount, SPG_LOAD.hcount,
|
||||
SPG_CONTROL.interlace, SPG_VBLANK.vbstart, SPG_VBLANK.vbend);
|
||||
pixel_clock, line_clock, dc_->SPG_LOAD.vcount, dc_->SPG_LOAD.hcount,
|
||||
dc_->SPG_CONTROL.interlace, dc_->SPG_VBLANK.vbstart,
|
||||
dc_->SPG_VBLANK.vbend);
|
||||
|
||||
if (line_timer_ != INVALID_HANDLE) {
|
||||
scheduler_.RemoveTimer(line_timer_);
|
||||
scheduler_->RemoveTimer(line_timer_);
|
||||
line_timer_ = INVALID_HANDLE;
|
||||
}
|
||||
|
||||
line_timer_ = scheduler_.AddTimer(HZ_TO_NANO(line_clock),
|
||||
std::bind(&PVR2::LineClockUpdate, this));
|
||||
line_timer_ = scheduler_->AddTimer(HZ_TO_NANO(line_clock),
|
||||
std::bind(&PVR2::LineClockUpdate, this));
|
||||
}
|
||||
|
||||
void PVR2::LineClockUpdate() {
|
||||
uint32_t num_scanlines = SPG_LOAD.vcount + 1;
|
||||
uint32_t num_scanlines = dc_->SPG_LOAD.vcount + 1;
|
||||
if (current_scanline_ > num_scanlines) {
|
||||
current_scanline_ = 0;
|
||||
}
|
||||
|
||||
// vblank in
|
||||
if (current_scanline_ == SPG_VBLANK_INT.vblank_in_line_number) {
|
||||
holly_.RequestInterrupt(HOLLY_INTC_PCVIINT);
|
||||
if (current_scanline_ == dc_->SPG_VBLANK_INT.vblank_in_line_number) {
|
||||
holly_->RequestInterrupt(HOLLY_INTC_PCVIINT);
|
||||
}
|
||||
|
||||
// vblank out
|
||||
if (current_scanline_ == SPG_VBLANK_INT.vblank_out_line_number) {
|
||||
holly_.RequestInterrupt(HOLLY_INTC_PCVOINT);
|
||||
if (current_scanline_ == dc_->SPG_VBLANK_INT.vblank_out_line_number) {
|
||||
holly_->RequestInterrupt(HOLLY_INTC_PCVOINT);
|
||||
}
|
||||
|
||||
// hblank in
|
||||
holly_.RequestInterrupt(HOLLY_INTC_PCHIINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_PCHIINT);
|
||||
|
||||
bool was_vsync = SPG_STATUS.vsync;
|
||||
SPG_STATUS.vsync = SPG_VBLANK.vbstart < SPG_VBLANK.vbend
|
||||
? (current_scanline_ >= SPG_VBLANK.vbstart &&
|
||||
current_scanline_ < SPG_VBLANK.vbend)
|
||||
: (current_scanline_ >= SPG_VBLANK.vbstart ||
|
||||
current_scanline_ < SPG_VBLANK.vbend);
|
||||
SPG_STATUS.scanline = current_scanline_++;
|
||||
bool was_vsync = dc_->SPG_STATUS.vsync;
|
||||
dc_->SPG_STATUS.vsync = dc_->SPG_VBLANK.vbstart < dc_->SPG_VBLANK.vbend
|
||||
? (current_scanline_ >= dc_->SPG_VBLANK.vbstart &&
|
||||
current_scanline_ < dc_->SPG_VBLANK.vbend)
|
||||
: (current_scanline_ >= dc_->SPG_VBLANK.vbstart ||
|
||||
current_scanline_ < dc_->SPG_VBLANK.vbend);
|
||||
dc_->SPG_STATUS.scanline = current_scanline_++;
|
||||
|
||||
if (!was_vsync && SPG_STATUS.vsync) {
|
||||
if (!was_vsync && dc_->SPG_STATUS.vsync) {
|
||||
// track vblank stats
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
|
|
107
src/holly/pvr2.h
107
src/holly/pvr2.h
|
@ -1,34 +1,29 @@
|
|||
#ifndef PVR_CLX2_H
|
||||
#define PVR_CLX2_H
|
||||
|
||||
#include <chrono>
|
||||
#include "emu/memory.h"
|
||||
#include "emu/scheduler.h"
|
||||
#include "holly/register.h"
|
||||
#include "holly/tile_accelerator.h"
|
||||
#include "renderer/backend.h"
|
||||
|
||||
namespace dreavm {
|
||||
namespace emu {
|
||||
class Dreamcast;
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace holly {
|
||||
|
||||
class Holly;
|
||||
|
||||
enum {
|
||||
#define PVR_REG(addr, name, flags, default_value, type) \
|
||||
name##_OFFSET = addr - emu::PVR_REG_START,
|
||||
#include "holly/pvr2_regs.inc"
|
||||
#undef PVR_REG
|
||||
};
|
||||
class TileAccelerator;
|
||||
|
||||
union PARAM_BASE_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t base_address : 24;
|
||||
uint32_t reserved : 8;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union FB_R_CTRL_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t fb_enable : 1;
|
||||
uint32_t fb_line_double : 1;
|
||||
|
@ -41,10 +36,10 @@ union FB_R_CTRL_T {
|
|||
uint32_t vclk_div : 1;
|
||||
uint32_t reserved1 : 8;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union FB_W_CTRL_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t fb_packmode : 3;
|
||||
uint32_t fb_dither : 1;
|
||||
|
@ -53,19 +48,19 @@ union FB_W_CTRL_T {
|
|||
uint32_t fb_alpha_threshhold : 8;
|
||||
uint32_t reserved1 : 8;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union FPU_SHAD_SCALE_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t scale_factor : 8;
|
||||
uint32_t intensity_volume_mode : 1;
|
||||
uint32_t reserved : 23;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union FPU_PARAM_CFG_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t first_ptr_burst_size : 4;
|
||||
uint32_t ptr_burst_size : 4;
|
||||
|
@ -75,10 +70,10 @@ union FPU_PARAM_CFG_T {
|
|||
uint32_t region_header_type : 1;
|
||||
uint32_t reserved1 : 10;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union ISP_BACKGND_T_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t tag_offset : 3;
|
||||
uint32_t tag_address : 21;
|
||||
|
@ -86,10 +81,10 @@ union ISP_BACKGND_T_T {
|
|||
uint32_t shadow : 1;
|
||||
uint32_t cache_bypass : 1;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union ISP_FEED_CFG_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t presort : 1;
|
||||
uint32_t reserved : 2;
|
||||
|
@ -98,10 +93,10 @@ union ISP_FEED_CFG_T {
|
|||
uint32_t cache_size : 10;
|
||||
uint32_t reserved1 : 8;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union SPG_HBLANK_INT_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t line_comp_val : 10;
|
||||
uint32_t reserved : 2;
|
||||
|
@ -110,20 +105,20 @@ union SPG_HBLANK_INT_T {
|
|||
uint32_t hblank_in_interrupt : 10;
|
||||
uint32_t reserved3 : 6;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union SPG_VBLANK_INT_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t vblank_in_line_number : 10;
|
||||
uint32_t reserved : 6;
|
||||
uint32_t vblank_out_line_number : 10;
|
||||
uint32_t reserved2 : 6;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union SPG_CONTROL_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t mhsync_pol : 1;
|
||||
uint32_t mvsync_pol : 1;
|
||||
|
@ -137,30 +132,30 @@ union SPG_CONTROL_T {
|
|||
uint32_t csync_on_h : 1;
|
||||
uint32_t reserved : 22;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union SPG_LOAD_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t hcount : 10;
|
||||
uint32_t reserved : 6;
|
||||
uint32_t vcount : 10;
|
||||
uint32_t reserved2 : 6;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union SPG_VBLANK_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t vbstart : 10;
|
||||
uint32_t reserved : 6;
|
||||
uint32_t vbend : 10;
|
||||
uint32_t reserved2 : 6;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union TEXT_CONTROL_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t stride : 5;
|
||||
uint32_t reserved : 3;
|
||||
|
@ -170,18 +165,18 @@ union TEXT_CONTROL_T {
|
|||
uint32_t codebook_endian : 1;
|
||||
uint32_t reserved3 : 14;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union PAL_RAM_CTRL_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t pixel_format : 2;
|
||||
uint32_t reserved0 : 30;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union SPG_STATUS_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t scanline : 10;
|
||||
uint32_t fieldnum : 1;
|
||||
|
@ -190,67 +185,51 @@ union SPG_STATUS_T {
|
|||
uint32_t vsync : 1;
|
||||
uint32_t reserved : 18;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
union TA_ISP_BASE_T {
|
||||
uint32_t full;
|
||||
struct {
|
||||
uint32_t base_address : 24;
|
||||
uint32_t reserved : 8;
|
||||
};
|
||||
uint32_t full;
|
||||
};
|
||||
|
||||
class PVR2 {
|
||||
friend class TileAccelerator;
|
||||
friend class TileTextureCache;
|
||||
|
||||
public:
|
||||
PVR2(emu::Scheduler &scheduler, emu::Memory &memory, Holly &holly);
|
||||
~PVR2();
|
||||
PVR2(emu::Dreamcast *dc);
|
||||
|
||||
float fps() { return fps_; }
|
||||
float vbps() { return vbps_; }
|
||||
|
||||
bool Init(renderer::Backend *rb);
|
||||
void RenderLastFrame();
|
||||
void ToggleTracing();
|
||||
void Init();
|
||||
|
||||
uint32_t ReadRegister32(uint32_t addr);
|
||||
void WriteRegister32(uint32_t addr, uint32_t value);
|
||||
|
||||
uint8_t ReadInterleaved8(uint32_t addr);
|
||||
uint16_t ReadInterleaved16(uint32_t addr);
|
||||
uint32_t ReadInterleaved32(uint32_t addr);
|
||||
void WriteInterleaved16(uint32_t addr, uint16_t value);
|
||||
void WriteInterleaved32(uint32_t addr, uint32_t value);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static T ReadInterleaved(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteInterleaved(void *ctx, uint32_t addr, T value);
|
||||
template <typename T>
|
||||
static T ReadRegister(void *ctx, uint32_t addr);
|
||||
template <typename T>
|
||||
static void WriteRegister(void *ctx, uint32_t addr, T value);
|
||||
|
||||
void InitMemory();
|
||||
void Reset();
|
||||
void ReconfigureSPG();
|
||||
void LineClockUpdate();
|
||||
|
||||
emu::Scheduler &scheduler_;
|
||||
emu::Memory &memory_;
|
||||
Holly &holly_;
|
||||
TileAccelerator ta_;
|
||||
emu::Dreamcast *dc_;
|
||||
emu::Scheduler *scheduler_;
|
||||
holly::Holly *holly_;
|
||||
holly::TileAccelerator *ta_;
|
||||
emu::Register *pvr_regs_;
|
||||
uint8_t *video_ram_;
|
||||
|
||||
emu::TimerHandle line_timer_;
|
||||
uint32_t current_scanline_;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point last_frame_, last_vblank_;
|
||||
float fps_, vbps_;
|
||||
|
||||
uint8_t *vram_;
|
||||
uint8_t *pram_;
|
||||
|
||||
Register regs_[emu::PVR_REG_SIZE >> 2];
|
||||
|
||||
#define PVR_REG(offset, name, flags, default, type) \
|
||||
type &name{reinterpret_cast<type &>(regs_[name##_OFFSET >> 2].value)};
|
||||
#include "holly/pvr2_regs.inc"
|
||||
#undef PVR_REG
|
||||
std::chrono::high_resolution_clock::time_point last_frame_;
|
||||
float fps_;
|
||||
std::chrono::high_resolution_clock::time_point last_vblank_;
|
||||
float vbps_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ PVR_REG(0x005f8004, REVISION, R, 0x00000011, uint32_t)
|
|||
PVR_REG(0x005f8008, SOFTRESET, RW, 0x00000007, uint32_t)
|
||||
PVR_REG(0x005f8014, STARTRENDER, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8018, TEST_SELECT, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8020, PARAM_BASE, RW, 0x00000000, PARAM_BASE_T)
|
||||
PVR_REG(0x005f8020, PARAM_BASE, RW, 0x00000000, holly::PARAM_BASE_T)
|
||||
PVR_REG(0x005f802c, REGION_BASE, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8030, SPAN_SORT_CFG, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8040, VO_BORDER_COL, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8044, FB_R_CTRL, RW, 0x00000000, FB_R_CTRL_T)
|
||||
PVR_REG(0x005f8048, FB_W_CTRL, RW, 0x00000000, FB_W_CTRL_T)
|
||||
PVR_REG(0x005f8044, FB_R_CTRL, RW, 0x00000000, holly::FB_R_CTRL_T)
|
||||
PVR_REG(0x005f8048, FB_W_CTRL, RW, 0x00000000, holly::FB_W_CTRL_T)
|
||||
PVR_REG(0x005f804c, FB_W_LINESTRIDE, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8050, FB_R_SOF1, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8054, FB_R_SOF2, RW, 0x00000000, uint32_t)
|
||||
|
@ -17,14 +17,14 @@ PVR_REG(0x005f8060, FB_W_SOF1, RW, 0x00000000, uint32_t)
|
|||
PVR_REG(0x005f8064, FB_W_SOF2, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8068, FB_X_CLIP, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f806c, FB_Y_CLIP, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8074, FPU_SHAD_SCALE, RW, 0x00000000, FPU_SHAD_SCALE_T)
|
||||
PVR_REG(0x005f8074, FPU_SHAD_SCALE, RW, 0x00000000, holly::FPU_SHAD_SCALE_T)
|
||||
PVR_REG(0x005f8078, FPU_CULL_VAL, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f807c, FPU_PARAM_CFG, RW, 0x0007df77, FPU_PARAM_CFG_T)
|
||||
PVR_REG(0x005f807c, FPU_PARAM_CFG, RW, 0x0007df77, holly::FPU_PARAM_CFG_T)
|
||||
PVR_REG(0x005f8080, HALF_OFFSET, RW, 0x00000007, uint32_t)
|
||||
PVR_REG(0x005f8084, FPU_PERP_VAL, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8088, ISP_BACKGND_D, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f808c, ISP_BACKGND_T, RW, 0x00000000, ISP_BACKGND_T_T)
|
||||
PVR_REG(0x005f8098, ISP_FEED_CFG, RW, 0x00402000, ISP_FEED_CFG_T)
|
||||
PVR_REG(0x005f808c, ISP_BACKGND_T, RW, 0x00000000, holly::ISP_BACKGND_T_T)
|
||||
PVR_REG(0x005f8098, ISP_FEED_CFG, RW, 0x00402000, holly::ISP_FEED_CFG_T)
|
||||
PVR_REG(0x005f80a0, SDRAM_REFRESH, RW, 0x00000020, uint32_t)
|
||||
PVR_REG(0x005f80a4, SDRAM_ARB_CFG, RW, 0x0000001f, uint32_t)
|
||||
PVR_REG(0x005f80a8, SDRAM_CFG, RW, 0x15f28997, uint32_t)
|
||||
|
@ -34,26 +34,26 @@ PVR_REG(0x005f80b8, FOG_DENSITY, RW, 0x00000000, uint32_t)
|
|||
PVR_REG(0x005f80bc, FOG_CLAMP_MAX, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f80c0, FOG_CLAMP_MIN, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f80c4, SPG_TRIGGER_POS, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f80c8, SPG_HBLANK_INT, RW, 0x031d0000, SPG_HBLANK_INT_T)
|
||||
PVR_REG(0x005f80cc, SPG_VBLANK_INT, RW, 0x01500104, SPG_VBLANK_INT_T)
|
||||
PVR_REG(0x005f80d0, SPG_CONTROL, RW, 0x00000000, SPG_CONTROL_T)
|
||||
PVR_REG(0x005f80c8, SPG_HBLANK_INT, RW, 0x031d0000, holly::SPG_HBLANK_INT_T)
|
||||
PVR_REG(0x005f80cc, SPG_VBLANK_INT, RW, 0x01500104, holly::SPG_VBLANK_INT_T)
|
||||
PVR_REG(0x005f80d0, SPG_CONTROL, RW, 0x00000000, holly::SPG_CONTROL_T)
|
||||
PVR_REG(0x005f80d4, SPG_HBLANK, RW, 0x007e0345, uint32_t)
|
||||
PVR_REG(0x005f80d8, SPG_LOAD, RW, 0x01060359, SPG_LOAD_T)
|
||||
PVR_REG(0x005f80dc, SPG_VBLANK, RW, 0x01500104, SPG_VBLANK_T)
|
||||
PVR_REG(0x005f80d8, SPG_LOAD, RW, 0x01060359, holly::SPG_LOAD_T)
|
||||
PVR_REG(0x005f80dc, SPG_VBLANK, RW, 0x01500104, holly::SPG_VBLANK_T)
|
||||
PVR_REG(0x005f80e0, SPG_WIDTH, RW, 0x07f1933f, uint32_t)
|
||||
PVR_REG(0x005f80e4, TEXT_CONTROL, RW, 0x00000000, TEXT_CONTROL_T)
|
||||
PVR_REG(0x005f80e4, TEXT_CONTROL, RW, 0x00000000, holly::TEXT_CONTROL_T)
|
||||
PVR_REG(0x005f80e8, VO_CONTROL, RW, 0x00000108, uint32_t)
|
||||
PVR_REG(0x005f80ec, VO_STARTX, RW, 0x0000009d, uint32_t)
|
||||
PVR_REG(0x005f80f0, VO_STARTY, RW, 0x00000015, uint32_t)
|
||||
PVR_REG(0x005f80f4, SCALER_CTL, RW, 0x00000400, uint32_t)
|
||||
PVR_REG(0x005f8108, PAL_RAM_CTRL, RW, 0x00000000, PAL_RAM_CTRL_T)
|
||||
PVR_REG(0x005f810c, SPG_STATUS, R, 0x00000000, SPG_STATUS_T)
|
||||
PVR_REG(0x005f8108, PAL_RAM_CTRL, RW, 0x00000000, holly::PAL_RAM_CTRL_T)
|
||||
PVR_REG(0x005f810c, SPG_STATUS, R, 0x00000000, holly::SPG_STATUS_T)
|
||||
PVR_REG(0x005f8110, FB_BURSTCTRL, RW, 0x00090639, uint32_t)
|
||||
PVR_REG(0x005f8114, FB_C_SOF, R, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8118, Y_COEFF, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f811c, PT_ALPHA_REF, RW, 0x000000ff, uint32_t)
|
||||
PVR_REG(0x005f8124, TA_OL_BASE, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8128, TA_ISP_BASE, RW, 0x00000000, TA_ISP_BASE_T)
|
||||
PVR_REG(0x005f8128, TA_ISP_BASE, RW, 0x00000000, holly::TA_ISP_BASE_T)
|
||||
PVR_REG(0x005f812c, TA_OL_LIMIT, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8130, TA_ISP_LIMIT, RW, 0x00000000, uint32_t)
|
||||
PVR_REG(0x005f8134, TA_NEXT_OPB, R, 0x00000000, uint32_t)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
#ifndef REGISTER_H
|
||||
#define REGISTER_H
|
||||
|
||||
namespace dreavm {
|
||||
namespace holly {
|
||||
|
||||
enum { R = 0x1, W = 0x2, RW = 0x3, UNDEFINED = 0x0 };
|
||||
|
||||
struct Register {
|
||||
Register() : offset(-1), flags(RW), value(0) {}
|
||||
Register(uint32_t offset, uint8_t flags, uint32_t value)
|
||||
: offset(offset), flags(flags), value(value) {}
|
||||
|
||||
uint32_t offset;
|
||||
uint8_t flags;
|
||||
uint32_t value;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,9 +1,6 @@
|
|||
#include "core/core.h"
|
||||
#include "holly/holly.h"
|
||||
#include "emu/dreamcast.h"
|
||||
#include "holly/pixel_convert.h"
|
||||
#include "holly/pvr2.h"
|
||||
#include "holly/tile_accelerator.h"
|
||||
#include "trace/trace.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
using namespace dreavm::emu;
|
||||
|
@ -167,7 +164,8 @@ static int GetVertexType_raw(const PCW &pcw) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
TileTextureCache::TileTextureCache(TileAccelerator &ta) : ta_(ta) {}
|
||||
TileTextureCache::TileTextureCache(Dreamcast *dc)
|
||||
: dc_(dc), trace_writer_(nullptr) {}
|
||||
|
||||
void TileTextureCache::Clear() {
|
||||
for (auto it : textures_) {
|
||||
|
@ -175,7 +173,7 @@ void TileTextureCache::Clear() {
|
|||
continue;
|
||||
}
|
||||
|
||||
ta_.rb_->FreeTexture(it.second);
|
||||
dc_->rb()->FreeTexture(it.second);
|
||||
}
|
||||
|
||||
textures_.clear();
|
||||
|
@ -188,7 +186,7 @@ void TileTextureCache::RemoveTexture(uint32_t addr) {
|
|||
}
|
||||
|
||||
TextureHandle handle = it->second;
|
||||
ta_.rb_->FreeTexture(handle);
|
||||
dc_->rb()->FreeTexture(handle);
|
||||
textures_.erase(it);
|
||||
}
|
||||
|
||||
|
@ -196,6 +194,12 @@ TextureHandle TileTextureCache::GetTexture(
|
|||
const TSP &tsp, const TCW &tcw, RegisterTextureCallback register_cb) {
|
||||
uint32_t texture_key = TextureCache::GetTextureKey(tsp, tcw);
|
||||
|
||||
// if the trace writer has changed, clear the cache to force insert events
|
||||
if (dc_->trace_writer() != trace_writer_) {
|
||||
Clear();
|
||||
trace_writer_ = dc_->trace_writer();
|
||||
}
|
||||
|
||||
// see if we already have an entry
|
||||
auto it = textures_.find(texture_key);
|
||||
if (it != textures_.end()) {
|
||||
|
@ -212,7 +216,7 @@ TextureHandle TileTextureCache::GetTexture(
|
|||
? 8
|
||||
: tcw.pixel_format == TA_PIXEL_4BPP ? 4 : 16;
|
||||
int texture_size = (width * height * element_size_bits) >> 3;
|
||||
const uint8_t *texture = &ta_.pvr_.vram_[texture_addr];
|
||||
const uint8_t *texture = &dc_->video_ram()[texture_addr];
|
||||
|
||||
// get the palette data
|
||||
int palette_size = 0;
|
||||
|
@ -229,7 +233,7 @@ TextureHandle TileTextureCache::GetTexture(
|
|||
}
|
||||
|
||||
palette_size = 0x1000;
|
||||
palette = &ta_.pvr_.pram_[palette_addr];
|
||||
palette = &dc_->palette_ram()[palette_addr];
|
||||
}
|
||||
|
||||
// register and insert into the cache
|
||||
|
@ -238,9 +242,9 @@ TextureHandle TileTextureCache::GetTexture(
|
|||
CHECK(result.second, "Texture already in the map?");
|
||||
|
||||
// add insert to trace
|
||||
if (ta_.trace_writer_) {
|
||||
ta_.trace_writer_->WriteInsertTexture(tsp, tcw, texture, texture_size,
|
||||
palette, palette_size);
|
||||
if (trace_writer_) {
|
||||
trace_writer_->WriteInsertTexture(tsp, tcw, texture, texture_size, palette,
|
||||
palette_size);
|
||||
}
|
||||
|
||||
return result.first->second;
|
||||
|
@ -264,11 +268,9 @@ int TileAccelerator::GetVertexType(const PCW &pcw) {
|
|||
pcw.para_type * TA_NUM_LISTS + pcw.list_type];
|
||||
}
|
||||
|
||||
TileAccelerator::TileAccelerator(Memory &memory, Holly &holly, PVR2 &pvr)
|
||||
: memory_(memory),
|
||||
holly_(holly),
|
||||
pvr_(pvr),
|
||||
texcache_(*this),
|
||||
TileAccelerator::TileAccelerator(Dreamcast *dc)
|
||||
: dc_(dc),
|
||||
texcache_(dc_),
|
||||
tile_renderer_(texcache_),
|
||||
last_context_(&scratch_context_) {}
|
||||
|
||||
|
@ -283,12 +285,10 @@ TileAccelerator::~TileAccelerator() {
|
|||
}
|
||||
}
|
||||
|
||||
bool TileAccelerator::Init(Backend *rb) {
|
||||
rb_ = rb;
|
||||
|
||||
InitMemory();
|
||||
|
||||
return true;
|
||||
void TileAccelerator::Init() {
|
||||
memory_ = dc_->memory();
|
||||
holly_ = dc_->holly();
|
||||
video_ram_ = dc_->video_ram();
|
||||
}
|
||||
|
||||
void TileAccelerator::SoftReset() {
|
||||
|
@ -329,7 +329,7 @@ void TileAccelerator::WriteContext(uint32_t addr, uint32_t value) {
|
|||
}
|
||||
|
||||
if (pcw.para_type == TA_PARAM_END_OF_LIST) {
|
||||
holly_.RequestInterrupt(list_interrupts[tactx->list_type]);
|
||||
holly_->RequestInterrupt(list_interrupts[tactx->list_type]);
|
||||
|
||||
tactx->last_poly = nullptr;
|
||||
tactx->last_vertex = nullptr;
|
||||
|
@ -365,88 +365,31 @@ void TileAccelerator::SaveLastContext(uint32_t addr) {
|
|||
WriteBackgroundState(last_context_);
|
||||
|
||||
// tell holly that rendering is complete
|
||||
holly_.RequestInterrupt(HOLLY_INTC_PCEOVINT);
|
||||
holly_.RequestInterrupt(HOLLY_INTC_PCEOIINT);
|
||||
holly_.RequestInterrupt(HOLLY_INTC_PCEOTINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_PCEOVINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_PCEOIINT);
|
||||
holly_->RequestInterrupt(HOLLY_INTC_PCEOTINT);
|
||||
}
|
||||
|
||||
void TileAccelerator::RenderLastContext() {
|
||||
tile_renderer_.RenderContext(last_context_, rb_);
|
||||
tile_renderer_.RenderContext(last_context_, dc_->rb());
|
||||
|
||||
// add render to trace
|
||||
if (trace_writer_) {
|
||||
trace_writer_->WriteRenderContext(last_context_);
|
||||
if (dc_->trace_writer()) {
|
||||
dc_->trace_writer()->WriteRenderContext(last_context_);
|
||||
}
|
||||
}
|
||||
|
||||
void TileAccelerator::ToggleTracing() {
|
||||
if (!trace_writer_) {
|
||||
char filename[PATH_MAX];
|
||||
GetNextTraceFilename(filename, sizeof(filename));
|
||||
|
||||
trace_writer_ = std::unique_ptr<TraceWriter>(new TraceWriter());
|
||||
if (!trace_writer_->Open(filename)) {
|
||||
LOG_INFO("Failed to start tracing");
|
||||
trace_writer_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Begin tracing to %s", filename);
|
||||
|
||||
// clear the texture cache, so the next render will write out insert
|
||||
// texture commands for any textures in use
|
||||
texcache_.Clear();
|
||||
} else {
|
||||
trace_writer_ = nullptr;
|
||||
|
||||
LOG_INFO("End tracing");
|
||||
}
|
||||
void TileAccelerator::WriteCommand32(uint32_t addr, uint32_t value) {
|
||||
WriteContext(dc_->TA_ISP_BASE.base_address, value);
|
||||
}
|
||||
|
||||
namespace dreavm {
|
||||
namespace holly {
|
||||
|
||||
template <typename T>
|
||||
void TileAccelerator::WriteCommand(void *ctx, uint32_t addr, T value) {
|
||||
WriteCommand<uint32_t>(ctx, addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
void TileAccelerator::WriteCommand(void *ctx, uint32_t addr, uint32_t value) {
|
||||
TileAccelerator *ta = (TileAccelerator *)ctx;
|
||||
|
||||
ta->WriteContext(ta->pvr_.TA_ISP_BASE.base_address, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TileAccelerator::WriteTexture(void *ctx, uint32_t addr, T value) {
|
||||
WriteTexture<uint32_t>(ctx, addr, static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
void TileAccelerator::WriteTexture(void *ctx, uint32_t addr, uint32_t value) {
|
||||
TileAccelerator *ta = (TileAccelerator *)ctx;
|
||||
|
||||
void TileAccelerator::WriteTexture32(uint32_t addr, uint32_t value) {
|
||||
addr &= 0xeeffffff;
|
||||
|
||||
// FIXME this is terrible
|
||||
ta->texcache_.RemoveTexture(addr);
|
||||
texcache_.RemoveTexture(addr);
|
||||
|
||||
*reinterpret_cast<uint32_t *>(&ta->pvr_.vram_[addr]) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TileAccelerator::InitMemory() {
|
||||
// TODO handle YUV transfers from 0x10800000 - 0x10ffffe0
|
||||
memory_.Handle(TA_CMD_START, TA_CMD_END, 0x0, this, nullptr, nullptr, nullptr,
|
||||
nullptr, &TileAccelerator::WriteCommand<uint8_t>,
|
||||
&TileAccelerator::WriteCommand<uint16_t>,
|
||||
&TileAccelerator::WriteCommand<uint32_t>, nullptr);
|
||||
memory_.Handle(TA_TEXTURE_START, TA_TEXTURE_END, 0x0, this, nullptr, nullptr,
|
||||
nullptr, nullptr, &TileAccelerator::WriteTexture<uint8_t>,
|
||||
&TileAccelerator::WriteTexture<uint16_t>,
|
||||
&TileAccelerator::WriteTexture<uint32_t>, nullptr);
|
||||
*reinterpret_cast<uint32_t *>(&video_ram_[addr]) = value;
|
||||
}
|
||||
|
||||
TileContextIterator TileAccelerator::FindContext(uint32_t addr) {
|
||||
|
@ -472,23 +415,23 @@ TileContext *TileAccelerator::GetContext(uint32_t addr) {
|
|||
|
||||
void TileAccelerator::WritePVRState(TileContext *tactx) {
|
||||
// autosort
|
||||
if (!pvr_.FPU_PARAM_CFG.region_header_type) {
|
||||
tactx->autosort = !pvr_.ISP_FEED_CFG.presort;
|
||||
if (!dc_->FPU_PARAM_CFG.region_header_type) {
|
||||
tactx->autosort = !dc_->ISP_FEED_CFG.presort;
|
||||
} else {
|
||||
uint32_t region_data = memory_.R32(PVR_VRAM64_START + pvr_.REGION_BASE);
|
||||
uint32_t region_data = memory_->R32(PVR_VRAM64_START + dc_->REGION_BASE);
|
||||
tactx->autosort = !(region_data & 0x20000000);
|
||||
}
|
||||
|
||||
// texture stride
|
||||
tactx->stride = pvr_.TEXT_CONTROL.stride * 32;
|
||||
tactx->stride = dc_->TEXT_CONTROL.stride * 32;
|
||||
|
||||
// texture palette pixel format
|
||||
tactx->pal_pxl_format = pvr_.PAL_RAM_CTRL.pixel_format;
|
||||
tactx->pal_pxl_format = dc_->PAL_RAM_CTRL.pixel_format;
|
||||
|
||||
// write out video width to help with unprojecting the screen space
|
||||
// coordinates
|
||||
if (pvr_.SPG_CONTROL.interlace ||
|
||||
(!pvr_.SPG_CONTROL.NTSC && !pvr_.SPG_CONTROL.PAL)) {
|
||||
if (dc_->SPG_CONTROL.interlace ||
|
||||
(!dc_->SPG_CONTROL.NTSC && !dc_->SPG_CONTROL.PAL)) {
|
||||
// interlaced and VGA mode both render at full resolution
|
||||
tactx->video_width = 640;
|
||||
tactx->video_height = 480;
|
||||
|
@ -507,35 +450,35 @@ void TileAccelerator::WriteBackgroundState(TileContext *tactx) {
|
|||
// be the correct solution
|
||||
uint32_t vram_offset =
|
||||
PVR_VRAM64_START +
|
||||
((tactx->addr + pvr_.ISP_BACKGND_T.tag_address * 4) & 0x7fffff);
|
||||
((tactx->addr + dc_->ISP_BACKGND_T.tag_address * 4) & 0x7fffff);
|
||||
|
||||
// get surface parameters
|
||||
tactx->bg_isp.full = memory_.R32(vram_offset);
|
||||
tactx->bg_tsp.full = memory_.R32(vram_offset + 4);
|
||||
tactx->bg_tcw.full = memory_.R32(vram_offset + 8);
|
||||
tactx->bg_isp.full = memory_->R32(vram_offset);
|
||||
tactx->bg_tsp.full = memory_->R32(vram_offset + 4);
|
||||
tactx->bg_tcw.full = memory_->R32(vram_offset + 8);
|
||||
vram_offset += 12;
|
||||
|
||||
// get the background depth
|
||||
tactx->bg_depth = *reinterpret_cast<float *>(&pvr_.ISP_BACKGND_D);
|
||||
tactx->bg_depth = *reinterpret_cast<float *>(&dc_->ISP_BACKGND_D);
|
||||
|
||||
// get the byte size for each vertex. normally, the byte size is
|
||||
// ISP_BACKGND_T.skip + 3, but if parameter selection volume mode is in
|
||||
// effect and the shadow bit is 1, then the byte size is
|
||||
// ISP_BACKGND_T.skip * 2 + 3
|
||||
int vertex_size = pvr_.ISP_BACKGND_T.skip;
|
||||
if (!pvr_.FPU_SHAD_SCALE.intensity_volume_mode && pvr_.ISP_BACKGND_T.shadow) {
|
||||
int vertex_size = dc_->ISP_BACKGND_T.skip;
|
||||
if (!dc_->FPU_SHAD_SCALE.intensity_volume_mode && dc_->ISP_BACKGND_T.shadow) {
|
||||
vertex_size *= 2;
|
||||
}
|
||||
vertex_size = (vertex_size + 3) * 4;
|
||||
|
||||
// skip to the first vertex
|
||||
vram_offset += pvr_.ISP_BACKGND_T.tag_offset * vertex_size;
|
||||
vram_offset += dc_->ISP_BACKGND_T.tag_offset * vertex_size;
|
||||
|
||||
// copy vertex data to context
|
||||
for (int i = 0, bg_offset = 0; i < 3; i++) {
|
||||
CHECK_LE(bg_offset + vertex_size, (int)sizeof(tactx->bg_vertices));
|
||||
|
||||
memory_.Memcpy(&tactx->bg_vertices[bg_offset], vram_offset, vertex_size);
|
||||
memory_->Memcpy(&tactx->bg_vertices[bg_offset], vram_offset, vertex_size);
|
||||
|
||||
bg_offset += vertex_size;
|
||||
vram_offset += vertex_size;
|
||||
|
|
|
@ -3,11 +3,15 @@
|
|||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "emu/memory.h"
|
||||
#include "holly/tile_renderer.h"
|
||||
#include "renderer/backend.h"
|
||||
|
||||
namespace dreavm {
|
||||
namespace emu {
|
||||
class Dreamcast;
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace trace {
|
||||
class TraceWriter;
|
||||
}
|
||||
|
@ -15,7 +19,6 @@ class TraceWriter;
|
|||
namespace holly {
|
||||
|
||||
class Holly;
|
||||
class PVR2;
|
||||
|
||||
enum {
|
||||
// control params
|
||||
|
@ -474,11 +477,9 @@ struct TileContext {
|
|||
int vertex_type;
|
||||
};
|
||||
|
||||
class TileAccelerator;
|
||||
|
||||
class TileTextureCache : public TextureCache {
|
||||
public:
|
||||
TileTextureCache(TileAccelerator &ta);
|
||||
TileTextureCache(emu::Dreamcast *dc);
|
||||
|
||||
void Clear();
|
||||
void RemoveTexture(uint32_t addr);
|
||||
|
@ -486,7 +487,9 @@ class TileTextureCache : public TextureCache {
|
|||
RegisterTextureCallback register_cb);
|
||||
|
||||
private:
|
||||
TileAccelerator &ta_;
|
||||
emu::Dreamcast *dc_;
|
||||
|
||||
trace::TraceWriter *trace_writer_;
|
||||
std::unordered_map<uint32_t, renderer::TextureHandle> textures_;
|
||||
};
|
||||
|
||||
|
@ -494,17 +497,15 @@ typedef std::unordered_map<uint32_t, TileContext *> TileContextMap;
|
|||
typedef TileContextMap::iterator TileContextIterator;
|
||||
|
||||
class TileAccelerator {
|
||||
friend class TileTextureCache;
|
||||
|
||||
public:
|
||||
static int GetParamSize(const PCW &pcw, int vertex_type);
|
||||
static int GetPolyType(const PCW &pcw);
|
||||
static int GetVertexType(const PCW &pcw);
|
||||
|
||||
TileAccelerator(emu::Memory &memory, Holly &holly, PVR2 &pvr);
|
||||
TileAccelerator(emu::Dreamcast *dc);
|
||||
~TileAccelerator();
|
||||
|
||||
bool Init(renderer::Backend *rb);
|
||||
void Init();
|
||||
|
||||
void SoftReset();
|
||||
void InitContext(uint32_t addr);
|
||||
|
@ -512,32 +513,25 @@ class TileAccelerator {
|
|||
void SaveLastContext(uint32_t addr);
|
||||
void RenderLastContext();
|
||||
|
||||
void ToggleTracing();
|
||||
void WriteCommand32(uint32_t addr, uint32_t value);
|
||||
void WriteTexture32(uint32_t addr, uint32_t value);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static void WriteCommand(void *ctx, uint32_t addr, T value);
|
||||
template <typename T>
|
||||
static void WriteTexture(void *ctx, uint32_t addr, T value);
|
||||
|
||||
void InitMemory();
|
||||
TileContextIterator FindContext(uint32_t addr);
|
||||
TileContext *GetContext(uint32_t addr);
|
||||
void WritePVRState(TileContext *tactx);
|
||||
void WriteBackgroundState(TileContext *tactx);
|
||||
|
||||
emu::Memory &memory_;
|
||||
Holly &holly_;
|
||||
PVR2 &pvr_;
|
||||
renderer::Backend *rb_;
|
||||
emu::Dreamcast *dc_;
|
||||
emu::Memory *memory_;
|
||||
holly::Holly *holly_;
|
||||
uint8_t *video_ram_;
|
||||
|
||||
TileTextureCache texcache_;
|
||||
TileRenderer tile_renderer_;
|
||||
TileContextMap contexts_;
|
||||
TileContext scratch_context_;
|
||||
TileContext *last_context_;
|
||||
|
||||
std::unique_ptr<trace::TraceWriter> trace_writer_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "core/core.h"
|
||||
#include "emu/profiler.h"
|
||||
#include "holly/pixel_convert.h"
|
||||
#include "holly/tile_accelerator.h"
|
||||
|
|
13
src/main.cc
13
src/main.cc
|
@ -1,5 +1,7 @@
|
|||
#include <memory>
|
||||
#include <stdio.h>
|
||||
#include "emu/emulator.h"
|
||||
#include "core/core.h"
|
||||
#include "emu/dreamcast.h"
|
||||
#include "trace/trace_viewer.h"
|
||||
|
||||
using namespace dreavm::core;
|
||||
|
@ -34,13 +36,12 @@ int main(int argc, char **argv) {
|
|||
InitFlags(&argc, &argv);
|
||||
|
||||
const char *load = argc > 1 ? argv[1] : nullptr;
|
||||
|
||||
if (load && strstr(load, ".trace")) {
|
||||
TraceViewer tracer;
|
||||
tracer.Run(load);
|
||||
std::unique_ptr<TraceViewer> tracer(new TraceViewer());
|
||||
tracer->Run(load);
|
||||
} else {
|
||||
Emulator emu;
|
||||
emu.Run(load);
|
||||
std::unique_ptr<Dreamcast> emu(new Dreamcast());
|
||||
emu->Run(load);
|
||||
}
|
||||
|
||||
ShutdownFlags();
|
||||
|
|
|
@ -76,7 +76,6 @@ bool GLBackend::Init() {
|
|||
InitTextures();
|
||||
InitShaders();
|
||||
InitVertexBuffers();
|
||||
|
||||
SetupDefaultState();
|
||||
|
||||
return true;
|
||||
|
|
|
@ -60,8 +60,6 @@ struct BackendState {
|
|||
};
|
||||
|
||||
class GLBackend : public Backend {
|
||||
friend class GLProfilerBackend;
|
||||
|
||||
public:
|
||||
GLBackend(GLContext &ctx);
|
||||
~GLBackend();
|
||||
|
|
|
@ -7,8 +7,8 @@ start:
|
|||
mov.l .L2, r0
|
||||
mov.b @r0, r2
|
||||
add r2, r2
|
||||
mov.b r2, @r15
|
||||
mov.b @r15, r2
|
||||
mov.b r2, @r0
|
||||
mov.b @r0, r2
|
||||
# MOV.B Rm,@-Rn
|
||||
# MOV.B @Rm+,Rn
|
||||
mov.l .L2, r0
|
||||
|
|
|
@ -10,8 +10,8 @@ start:
|
|||
add #4, r0
|
||||
mov.l @r0, r3
|
||||
add r3, r3
|
||||
mov.l r3, @r15
|
||||
mov.l @r15, r3
|
||||
mov.l r3, @r0
|
||||
mov.l @r0, r3
|
||||
# MOV.L Rm,@-Rn
|
||||
# MOV.L @Rm+,Rn
|
||||
mov.l .L2, r0
|
||||
|
|
|
@ -10,8 +10,8 @@ start:
|
|||
add #2, r0
|
||||
mov.w @r0, r3
|
||||
add r3, r3
|
||||
mov.w r3, @r15
|
||||
mov.w @r15, r3
|
||||
mov.w r3, @r0
|
||||
mov.w @r0, r3
|
||||
# MOV.W Rm,@-Rn
|
||||
# MOV.W @Rm+,Rn
|
||||
mov.l .L2, r0
|
||||
|
|
|
@ -56,10 +56,10 @@ SH4_TEST(ldcl, SH4Test {(uint8_t *)"\x01\xe2\x3b\xd0\x07\x40\x63\xe2\x31\xd1\x03
|
|||
SH4_TEST(lds, SH4Test {(uint8_t *)"\x0a\x40\x0a\x02\x1a\x40\x1a\x03\x2a\x05\x2a\x40\x2a\x04\x2a\x45\x6a\x41\x6a\x05\x5a\x40\x5a\x06\x0b\x00\x09\x00", 28, {{ SH4CTX_R0, 13 }, { SH4CTX_R1, 0xffd40001 }}, {{ SH4CTX_R2, 13 }, { SH4CTX_R3, 13 }, { SH4CTX_R4, 13 }, { SH4CTX_R5, 0x00140001 }, { SH4CTX_R6, 13 }}})
|
||||
SH4_TEST(ldsl, SH4Test {(uint8_t *)"\x1f\xd0\x23\xd1\x0a\xe2\x22\x20\x06\x40\x02\x41\x12\x63\x00\x31\x29\x00\x0c\x33\x1a\xd0\x1e\xd1\x0b\xe2\x22\x20\x16\x40\x12\x41\x12\x64\x00\x31\x29\x00\x0c\x34\x2a\x06\x15\xd0\x18\xd1\x0c\xe2\x22\x20\x26\x40\x22\x41\x12\x65\x00\x31\x29\x00\x0c\x35\x2a\x46\x0f\xd0\x13\xd1\x16\xd2\x22\x20\x66\x40\x62\x41\x12\x66\x00\x31\x29\x00\x0c\x36\x0a\xd0\x0e\xd1\x0d\xe2\x22\x20\x56\x40\x52\x41\x12\x67\x00\x31\x29\x00\x0c\x37\x0b\x00\x09\x00\x09\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x09\x00\x09\x00\x09\x00\x70\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x78\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x01\x00\xd4\xff\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 176, {}, {{ SH4CTX_R3, 0xb }, { SH4CTX_R4, 0xc }, { SH4CTX_R5, 0xd }, { SH4CTX_R6, 0x00140002 }, { SH4CTX_R7, 0xe }}})
|
||||
SH4_TEST(mova, SH4Test {(uint8_t *)"\x03\xc7\x02\x62\x0b\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\xe8\xff\xff\xff\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 32, {}, {{ SH4CTX_R2, -24 }}})
|
||||
SH4_TEST(movb, SH4Test {(uint8_t *)"\x13\xd0\x00\x62\x2c\x32\x20\x2f\xf0\x62\x11\xd0\x01\x70\x04\x63\x3c\x33\x34\x20\x00\x63\x0e\xd4\x42\x84\x0c\x30\x42\x80\x42\x84\x03\x64\x0b\xd0\x03\xe1\x1c\x05\x5c\x35\x54\x01\x1c\x05\x08\xd0\x1e\x40\x04\xc4\x0c\x30\x04\xc0\x04\xc4\x03\x66\x0b\x00\x09\x00\xf4\xf3\xf2\xf1\xf0\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x40\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 96, {}, {{ SH4CTX_R2, -24 }, { SH4CTX_R3, -26 }, { SH4CTX_R4, -28 }, { SH4CTX_R5, -30 }, { SH4CTX_R6, -32 }}})
|
||||
SH4_TEST(movl, SH4Test {(uint8_t *)"\x13\xd2\x1b\xd0\x04\x70\x02\x63\x3c\x33\x32\x2f\xf2\x63\x18\xd0\x08\x70\x06\x64\x4c\x34\x46\x20\x02\x64\x15\xd5\x53\x50\x0c\x30\x03\x15\x53\x50\x03\x65\x12\xd0\x10\xe1\x1e\x06\x6c\x36\x66\x01\x1e\x06\x0f\xd0\x1e\x40\x05\xc6\x0c\x30\x05\xc2\x05\xc6\x03\x67\x0b\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\xe8\xff\xff\xff\xf3\xff\xff\xff\xf2\xff\xff\xff\xf1\xff\xff\xff\xf0\xff\xff\xff\xef\xff\xff\xff\x09\x00\x09\x00\x09\x00\x09\x00\x50\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 128, {}, {{ SH4CTX_R2, -24 }, { SH4CTX_R3, -26 }, { SH4CTX_R4, -28 }, { SH4CTX_R5, -30 }, { SH4CTX_R6, -32 }, { SH4CTX_R7, -34 }}})
|
||||
SH4_TEST(movb, SH4Test {(uint8_t *)"\x13\xd0\x00\x62\x2c\x32\x20\x20\x00\x62\x11\xd0\x01\x70\x04\x63\x3c\x33\x34\x20\x00\x63\x0e\xd4\x42\x84\x0c\x30\x42\x80\x42\x84\x03\x64\x0b\xd0\x03\xe1\x1c\x05\x5c\x35\x54\x01\x1c\x05\x08\xd0\x1e\x40\x04\xc4\x0c\x30\x04\xc0\x04\xc4\x03\x66\x0b\x00\x09\x00\xf4\xf3\xf2\xf1\xf0\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x40\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 96, {}, {{ SH4CTX_R2, -24 }, { SH4CTX_R3, -26 }, { SH4CTX_R4, -28 }, { SH4CTX_R5, -30 }, { SH4CTX_R6, -32 }}})
|
||||
SH4_TEST(movl, SH4Test {(uint8_t *)"\x13\xd2\x1b\xd0\x04\x70\x02\x63\x3c\x33\x32\x20\x02\x63\x18\xd0\x08\x70\x06\x64\x4c\x34\x46\x20\x02\x64\x15\xd5\x53\x50\x0c\x30\x03\x15\x53\x50\x03\x65\x12\xd0\x10\xe1\x1e\x06\x6c\x36\x66\x01\x1e\x06\x0f\xd0\x1e\x40\x05\xc6\x0c\x30\x05\xc2\x05\xc6\x03\x67\x0b\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\xe8\xff\xff\xff\xf3\xff\xff\xff\xf2\xff\xff\xff\xf1\xff\xff\xff\xf0\xff\xff\xff\xef\xff\xff\xff\x09\x00\x09\x00\x09\x00\x09\x00\x50\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 128, {}, {{ SH4CTX_R2, -24 }, { SH4CTX_R3, -26 }, { SH4CTX_R4, -28 }, { SH4CTX_R5, -30 }, { SH4CTX_R6, -32 }, { SH4CTX_R7, -34 }}})
|
||||
SH4_TEST(movt, SH4Test {(uint8_t *)"\x03\x88\x29\x01\x05\x88\x29\x02\x0b\x00\x09\x00", 12, {{ SH4CTX_R0, 3 }}, {{ SH4CTX_R1, 1 }, { SH4CTX_R2, 0 }}})
|
||||
SH4_TEST(movw, SH4Test {(uint8_t *)"\x26\x92\x17\xd0\x02\x70\x01\x63\x3c\x33\x31\x2f\xf1\x63\x14\xd0\x04\x70\x05\x64\x4c\x34\x45\x20\x01\x64\x11\xd5\x53\x85\x0c\x30\x53\x81\x53\x85\x03\x65\x0e\xd0\x08\xe1\x1d\x06\x6c\x36\x65\x01\x1d\x06\x0b\xd0\x1e\x40\x05\xc5\x0c\x30\x05\xc1\x05\xc5\x03\x67\x0b\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\xe8\xff\xf3\xff\xf2\xff\xf1\xff\xf0\xff\xef\xff\x09\x00\x09\x00\x50\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 112, {}, {{ SH4CTX_R2, -24 }, { SH4CTX_R3, -26 }, { SH4CTX_R4, -28 }, { SH4CTX_R5, -30 }, { SH4CTX_R6, -32 }, { SH4CTX_R7, -34 }}})
|
||||
SH4_TEST(movw, SH4Test {(uint8_t *)"\x26\x92\x17\xd0\x02\x70\x01\x63\x3c\x33\x31\x20\x01\x63\x14\xd0\x04\x70\x05\x64\x4c\x34\x45\x20\x01\x64\x11\xd5\x53\x85\x0c\x30\x53\x81\x53\x85\x03\x65\x0e\xd0\x08\xe1\x1d\x06\x6c\x36\x65\x01\x1d\x06\x0b\xd0\x1e\x40\x05\xc5\x0c\x30\x05\xc1\x05\xc5\x03\x67\x0b\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\xe8\xff\xf3\xff\xf2\xff\xf1\xff\xf0\xff\xef\xff\x09\x00\x09\x00\x50\x00\x01\x8c\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00\x09\x00", 112, {}, {{ SH4CTX_R2, -24 }, { SH4CTX_R3, -26 }, { SH4CTX_R4, -28 }, { SH4CTX_R5, -30 }, { SH4CTX_R6, -32 }, { SH4CTX_R7, -34 }}})
|
||||
SH4_TEST(mul, SH4Test {(uint8_t *)"\x07\x01\x1a\x01\x2f\x23\x1a\x03\x0b\x00\x09\x00", 12, {{ SH4CTX_R0, 0xfffffffe }, { SH4CTX_R1, 0x00005555 }, { SH4CTX_R2, 0xfffffffe }, { SH4CTX_R3, 0x00005555 }, { SH4CTX_R4, 0x00000002 }, { SH4CTX_R5, 0xffffaaaa }}, {{ SH4CTX_R1, 0xffff5556 }, { SH4CTX_R3, 0xffff5556 }}})
|
||||
SH4_TEST(neg, SH4Test {(uint8_t *)"\x2b\x64\x0e\x40\x2a\x65\x02\x06\x0e\x40\x3a\x67\x02\x08\x0e\x41\x2a\x69\x02\x0a\x0e\x41\x3a\x6b\x02\x0c\x0b\x00\x09\x00", 30, {{ SH4CTX_R0, 0x700000f0 }, { SH4CTX_R1, 0x700000f1 }, { SH4CTX_R2, -4 }, { SH4CTX_R3, 0 }}, {{ SH4CTX_R4, 4 }, { SH4CTX_R5, 4 }, { SH4CTX_R6, 0x700000f1 }, { SH4CTX_R7, 0 }, { SH4CTX_R8, 0x700000f0 }, { SH4CTX_R9, 3 }, { SH4CTX_R10, 0x700000f1 }, { SH4CTX_R11, 0xffffffff }, { SH4CTX_R12, 0x700000f1 }}})
|
||||
SH4_TEST(not, SH4Test {(uint8_t *)"\x07\x61\x0b\x00\x09\x00", 6, {{ SH4CTX_R0, 0xf0f0f0f0 }}, {{ SH4CTX_R1, 0x0f0f0f0f }}})
|
||||
|
|
|
@ -18,30 +18,29 @@ namespace dreavm {
|
|||
namespace cpu {
|
||||
|
||||
void RunSH4Test(const SH4Test &test) {
|
||||
Memory memory;
|
||||
static uint32_t pc = 0x8c010000;
|
||||
|
||||
// initialize runtime
|
||||
Memory memory;
|
||||
frontend::sh4::SH4Frontend rt_frontend(memory);
|
||||
backend::x64::X64Backend rt_backend(memory);
|
||||
// backend::interpreter::InterpreterBackend rt_backend(memory);
|
||||
Runtime runtime(memory);
|
||||
ASSERT_TRUE(runtime.Init(&rt_frontend, &rt_backend));
|
||||
Runtime runtime(memory, rt_frontend, rt_backend);
|
||||
|
||||
// initialize device
|
||||
SH4 sh4(memory);
|
||||
ASSERT_TRUE(sh4.Init(&runtime));
|
||||
sh4.ctx_.pc = 0x8c010000;
|
||||
SH4 sh4(memory, runtime);
|
||||
sh4.Init();
|
||||
sh4.SetPC(pc);
|
||||
|
||||
// mount the test binary and a small stack
|
||||
// mount a small stack (stack grows down)
|
||||
uint8_t stack[MAX_PAGE_SIZE];
|
||||
sh4.ctx_.r[15] = sizeof(stack);
|
||||
memory.Mount(0x0, sizeof(stack) - 1, ~ADDR_MASK, stack);
|
||||
|
||||
// mount the test binary
|
||||
uint32_t binary_size = core::align(static_cast<uint32_t>(test.buffer_size),
|
||||
static_cast<uint32_t>(MAX_PAGE_SIZE));
|
||||
uint8_t *binary = new uint8_t[binary_size];
|
||||
memcpy(binary, test.buffer, test.buffer_size);
|
||||
|
||||
memory.Mount(0x0, sizeof(stack) - 1, MIRROR_MASK, stack);
|
||||
memory.Mount(sh4.ctx_.pc, sh4.ctx_.pc + binary_size - 1, MIRROR_MASK, binary);
|
||||
memory.Mount(pc, pc + binary_size - 1, ~ADDR_MASK, binary);
|
||||
|
||||
// setup in registers
|
||||
for (auto it : test.r_in) {
|
||||
|
|
Loading…
Reference in New Issue