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:
Anthony Pesch 2015-09-04 00:19:20 -07:00
parent bb5b03919d
commit ecc0a6c7ac
45 changed files with 1409 additions and 1487 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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() {

View File

@ -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_));

View File

@ -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);
};

View File

@ -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");
}
}

View File

@ -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_;

View File

@ -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) { //

View File

@ -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_;

View File

@ -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;
}
}
}

View File

@ -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

433
src/emu/dreamcast.cc Normal file
View File

@ -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();
}

173
src/emu/dreamcast.h Normal file
View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

View File

@ -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) {

View File

@ -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);

View File

@ -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 &reg, 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 &reg = 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 &reg, 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 &reg, uint32_t addr) {
return reg.value;
}
void GDROM::WriteRegister(Register &reg, 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 &reg = 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 &reg, 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;

View File

@ -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 &reg, uint32_t addr);
void WriteRegister(Register &reg, 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_;
};
}
}

View File

@ -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 &reg = holly->regs_[addr >> 2];
uint32_t Holly::ReadRegister32(uint32_t addr) {
uint32_t offset = addr >> 2;
Register &reg = 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 &reg = holly->regs_[addr >> 2];
void Holly::WriteRegister32(uint32_t addr, uint32_t value) {
uint32_t offset = addr >> 2;
Register &reg = 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);
}
}
}

View File

@ -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
};
}
}

View File

@ -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 &reg, uint32_t addr) { return reg.value; }
uint32_t Maple::ReadRegister32(uint32_t addr) {
uint32_t offset = (0xc00 + addr) >> 2;
Register &reg = holly_regs_[offset];
void Maple::WriteRegister(Register &reg, 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 &reg = 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);
}

View File

@ -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 &reg, uint32_t addr);
void WriteRegister(Register &reg, 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];
};
}

View File

@ -1,6 +1,7 @@
#include <json11.hpp>
#include <fstream>
#include <sstream>
#include "core/core.h"
#include "holly/maple_controller.h"
using namespace dreavm::holly;

View File

@ -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 &reg = 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 &reg = 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 &reg = 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 &reg = 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>(

View File

@ -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_;
};
}
}

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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_;
};
}
}

View File

@ -1,3 +1,4 @@
#include "core/core.h"
#include "emu/profiler.h"
#include "holly/pixel_convert.h"
#include "holly/tile_accelerator.h"

View File

@ -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();

View File

@ -76,7 +76,6 @@ bool GLBackend::Init() {
InitTextures();
InitShaders();
InitVertexBuffers();
SetupDefaultState();
return true;

View File

@ -60,8 +60,6 @@ struct BackendState {
};
class GLBackend : public Backend {
friend class GLProfilerBackend;
public:
GLBackend(GLContext &ctx);
~GLBackend();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }}})

View File

@ -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) {