mirror of https://github.com/Treeki/WindEmu.git
broken draft of a fully custom ARM emulator core
This commit is contained in:
parent
868112e9fc
commit
c93b268061
|
@ -5,10 +5,11 @@
|
||||||
#-------------------------------------------------
|
#-------------------------------------------------
|
||||||
|
|
||||||
QT -= core gui
|
QT -= core gui
|
||||||
|
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
|
||||||
|
|
||||||
TARGET = WindCore
|
TARGET = WindCore
|
||||||
TEMPLATE = lib
|
TEMPLATE = lib
|
||||||
CONFIG += staticlib
|
CONFIG += staticlib c++17
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
# any feature of Qt which has been marked as deprecated (the exact warnings
|
||||||
|
@ -22,6 +23,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
arm710a.cpp \
|
||||||
etna.cpp \
|
etna.cpp \
|
||||||
wind.cpp \
|
wind.cpp \
|
||||||
isa-arm.c \
|
isa-arm.c \
|
||||||
|
@ -31,6 +33,7 @@ SOURCES += \
|
||||||
emu.cpp
|
emu.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
arm710a.h \
|
||||||
etna.h \
|
etna.h \
|
||||||
wind_hw.h \
|
wind_hw.h \
|
||||||
wind.h \
|
wind.h \
|
||||||
|
|
|
@ -0,0 +1,976 @@
|
||||||
|
#include "arm710a.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
// this will need changing if this code ever compiles on big-endian procs
|
||||||
|
inline uint32_t read32LE(uint8_t *p) {
|
||||||
|
return *((uint32_t *)p);
|
||||||
|
}
|
||||||
|
inline void write32LE(uint8_t *p, uint32_t v) {
|
||||||
|
*((uint32_t *)p) = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ARM710a::switchBank(BankIndex newBank) {
|
||||||
|
if (newBank != bank) {
|
||||||
|
// R13 and R14 need saving/loading for all banks
|
||||||
|
allModesBankedRegisters[bank][0] = GPRs[13];
|
||||||
|
allModesBankedRegisters[bank][1] = GPRs[14];
|
||||||
|
GPRs[13] = allModesBankedRegisters[newBank][0];
|
||||||
|
GPRs[14] = allModesBankedRegisters[newBank][1];
|
||||||
|
|
||||||
|
// R8 to R12 are only banked in FIQ mode
|
||||||
|
auto oldBankR8to12 = (bank == FiqBank) ? FiqBank : MainBank;
|
||||||
|
auto newBankR8to12 = (newBank == FiqBank) ? FiqBank : MainBank;
|
||||||
|
if (oldBankR8to12 != newBankR8to12) {
|
||||||
|
// swap these sets around
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
fiqBankedRegisters[oldBankR8to12][i] = GPRs[8 + i];
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
GPRs[8 + i] = fiqBankedRegisters[newBankR8to12][i];
|
||||||
|
}
|
||||||
|
|
||||||
|
bank = newBank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ARM710a::switchMode(Mode newMode) {
|
||||||
|
auto oldMode = currentMode();
|
||||||
|
if (newMode != oldMode) {
|
||||||
|
switchBank(modeToBank[newMode & 0xF]);
|
||||||
|
|
||||||
|
CPSR &= ~CPSR_ModeMask;
|
||||||
|
CPSR |= newMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARM710a::raiseException(Mode mode, uint32_t savedPC, uint32_t newPC) {
|
||||||
|
auto bankIndex = modeToBank[mode & 0xF];
|
||||||
|
SPSRs[bankIndex] = CPSR;
|
||||||
|
|
||||||
|
switchMode(mode);
|
||||||
|
|
||||||
|
prefetchCount = 0;
|
||||||
|
GPRs[14] = savedPC;
|
||||||
|
GPRs[15] = newPC;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARM710a::requestFIQ() {
|
||||||
|
raiseException(FIQ32, GPRs[15], 0x1C);
|
||||||
|
CPSR |= CPSR_FIQDisable;
|
||||||
|
CPSR |= CPSR_IRQDisable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARM710a::requestIRQ() {
|
||||||
|
raiseException(FIQ32, GPRs[15], 0x18);
|
||||||
|
CPSR |= CPSR_IRQDisable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARM710a::reset() {
|
||||||
|
clearCache();
|
||||||
|
raiseException(Supervisor32, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t ARM710a::tick() {
|
||||||
|
// pop an instruction off the end of the pipeline
|
||||||
|
bool haveInsn = false;
|
||||||
|
uint32_t insn;
|
||||||
|
MMUFault insnFault;
|
||||||
|
if (prefetchCount == 2) {
|
||||||
|
haveInsn = true;
|
||||||
|
insn = prefetch[1];
|
||||||
|
insnFault = prefetchFaults[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the instruction we fetched last tick once along
|
||||||
|
if (prefetchCount >= 1) {
|
||||||
|
prefetch[1] = prefetch[0];
|
||||||
|
prefetchFaults[1] = prefetchFaults[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch a new instruction
|
||||||
|
auto newInsn = readVirtual(GPRs[15], V32);
|
||||||
|
GPRs[15] += 4;
|
||||||
|
prefetch[0] = newInsn.first.value_or(0);
|
||||||
|
prefetchFaults[0] = newInsn.second;
|
||||||
|
if (prefetchCount < 2)
|
||||||
|
prefetchCount++;
|
||||||
|
|
||||||
|
// now deal with the one we popped
|
||||||
|
uint32_t clocks = 1;
|
||||||
|
if (haveInsn) {
|
||||||
|
if (insnFault != NoFault) {
|
||||||
|
// Raise a prefetch error
|
||||||
|
// These do not set FSR or FAR
|
||||||
|
raiseException(Abort32, GPRs[15] - 8, 0xC);
|
||||||
|
} else {
|
||||||
|
clocks += executeInstruction(insn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faultTriggeredThisCycle) {
|
||||||
|
// data abort time!
|
||||||
|
faultTriggeredThisCycle = false;
|
||||||
|
raiseException(Abort32, GPRs[15] - 4, 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline uint32_t extract(uint32_t value, uint32_t hiBit, uint32_t loBit) {
|
||||||
|
return (value >> loBit) & ((1 << (hiBit - loBit + 1)) - 1);
|
||||||
|
// return (value >> (32 - offset - length)) & ((1 << length) - 1);
|
||||||
|
}
|
||||||
|
static inline bool extract1(uint32_t value, uint32_t bit) {
|
||||||
|
return (value >> bit) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t ARM710a::executeInstruction(uint32_t i) {
|
||||||
|
uint32_t cycles = 1;
|
||||||
|
|
||||||
|
// a big old dispatch thing here
|
||||||
|
// but first, conditions!
|
||||||
|
if (!checkCondition(extract(i, 31, 28)))
|
||||||
|
return cycles;
|
||||||
|
|
||||||
|
if ((i & 0x0F000000) == 0x0F000000)
|
||||||
|
raiseException(Supervisor32, GPRs[15] - 8, 0x08);
|
||||||
|
else if ((i & 0x0F000F10) == 0x0E000F10)
|
||||||
|
cycles += execCP15RegisterTransfer(extract(i,23,21), extract1(i,20), extract(i,19,16), extract(i,15,12), extract(i,7,5), extract(i,3,0));
|
||||||
|
else if ((i & 0x0E000000) == 0x0A000000)
|
||||||
|
cycles += execBranch(extract1(i,24), extract(i,23,0));
|
||||||
|
else if ((i & 0x0E000000) == 0x08000000)
|
||||||
|
cycles += execBlockDataTransfer(extract(i,24,20), extract(i,19,16), extract(i,15,0));
|
||||||
|
else if ((i & 0x0C000000) == 0x04000000)
|
||||||
|
cycles += execSingleDataTransfer(extract(i,25,20), extract(i,19,16), extract(i,15,12), extract(i,11,0));
|
||||||
|
else if ((i & 0x0FB00FF0) == 0x01000090)
|
||||||
|
cycles += execSingleDataSwap(extract1(i,22), extract(i,19,16), extract(i,15,12), extract(i,3,0));
|
||||||
|
else if ((i & 0x0F8000F0) == 0x00000090)
|
||||||
|
cycles += execMultiply(extract(i,21,20), extract(i,19,16), extract(i,15,12), extract(i,11,8), extract(i,3,0));
|
||||||
|
else if ((i & 0x0C000000) == 0x00000000)
|
||||||
|
cycles += execDataProcessing(extract1(i,25), extract(i,24,21), extract1(i,20), extract(i,19,16), extract(i,15,12), extract(i,11,0));
|
||||||
|
else
|
||||||
|
raiseException(Undefined32, GPRs[15] - 8, 0x04);
|
||||||
|
|
||||||
|
return cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execDataProcessing(bool I, uint32_t Opcode, bool S, uint32_t Rn, uint32_t Rd, uint32_t Operand2)
|
||||||
|
{
|
||||||
|
uint32_t cycles = 0; // TODO increment me semi-accurately
|
||||||
|
bool shifterCarryOutput;
|
||||||
|
|
||||||
|
// compute our Op1 (may be unnecessary but that's ok)
|
||||||
|
uint32_t op1 = GPRs[Rn];
|
||||||
|
|
||||||
|
// compute our Op2
|
||||||
|
uint32_t op2;
|
||||||
|
if (!I) {
|
||||||
|
// REGISTER
|
||||||
|
uint32_t Rm = extract(Operand2, 3, 0);
|
||||||
|
op2 = GPRs[Rm];
|
||||||
|
|
||||||
|
uint8_t shiftBy;
|
||||||
|
|
||||||
|
// this is the real painful one, honestly
|
||||||
|
if (extract(Operand2, 4, 4)) {
|
||||||
|
// Shift by Register
|
||||||
|
uint32_t Rs = extract(Operand2, 11, 8);
|
||||||
|
shiftBy = GPRs[Rs] & 0xFF;
|
||||||
|
} else {
|
||||||
|
// Shift by Immediate
|
||||||
|
shiftBy = extract(Operand2, 11, 7);
|
||||||
|
|
||||||
|
if (Rn == 15) // if PC is fetched...
|
||||||
|
op1 -= 4; // compensate for prefetching
|
||||||
|
if (Rm == 15)
|
||||||
|
op2 -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (extract(Operand2, 6, 5)) {
|
||||||
|
case 0: // Logical Left (LSL)
|
||||||
|
if (shiftBy == 0) {
|
||||||
|
shifterCarryOutput = flagC();
|
||||||
|
// no change to op2!
|
||||||
|
} else if (shiftBy <= 31) {
|
||||||
|
shifterCarryOutput = extract1(op2, 31 - shiftBy);
|
||||||
|
op2 <<= shiftBy;
|
||||||
|
} else if (shiftBy == 32) {
|
||||||
|
shifterCarryOutput = extract1(op2, 0);
|
||||||
|
op2 = 0;
|
||||||
|
} else /*if (shiftBy >= 33)*/ {
|
||||||
|
shifterCarryOutput = false;
|
||||||
|
op2 = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1: // Logical Right (LSR)
|
||||||
|
if (shiftBy == 0 || shiftBy == 32) {
|
||||||
|
shifterCarryOutput = extract1(op2, 31);
|
||||||
|
op2 = 0;
|
||||||
|
} else if (shiftBy <= 31) {
|
||||||
|
shifterCarryOutput = extract1(op2, shiftBy - 1);
|
||||||
|
op2 >>= shiftBy;
|
||||||
|
} else /*if (shiftBy >= 33)*/ {
|
||||||
|
shifterCarryOutput = false;
|
||||||
|
op2 = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // Arithmetic Right (ASR)
|
||||||
|
if (shiftBy == 0 || shiftBy >= 32) {
|
||||||
|
shifterCarryOutput = extract1(op2, 31);
|
||||||
|
op2 = (int32_t)op2 >> 31;
|
||||||
|
} else /*if (shiftBy <= 31)*/ {
|
||||||
|
shifterCarryOutput = extract1(op2, shiftBy - 1);
|
||||||
|
op2 = (int32_t)op2 >> shiftBy;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // Rotate Right (ROR)
|
||||||
|
if (shiftBy == 0) { // treated as RRX
|
||||||
|
shifterCarryOutput = op2 & 1;
|
||||||
|
op2 >>= 1;
|
||||||
|
op2 |= flagC() ? 0x80000000 : 0;
|
||||||
|
} else {
|
||||||
|
shiftBy %= 32;
|
||||||
|
if (shiftBy == 0) { // like 32
|
||||||
|
shifterCarryOutput = extract1(op2, 31);
|
||||||
|
// no change to op2
|
||||||
|
} else {
|
||||||
|
shifterCarryOutput = extract1(op2, shiftBy - 1);
|
||||||
|
op2 = ROR(op2, shiftBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// IMMEDIATE
|
||||||
|
if (Rn == 15) // if PC is fetched...
|
||||||
|
op1 -= 4; // compensate for prefetching
|
||||||
|
|
||||||
|
uint32_t Rotate = extract(Operand2, 11, 8);
|
||||||
|
uint32_t Imm = extract(Operand2, 7, 0);
|
||||||
|
Imm = (uint32_t)(int8_t)Imm;
|
||||||
|
op2 = ROR(Imm, Rotate * 2);
|
||||||
|
shifterCarryOutput = flagC(); // correct? unsure...
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have our operands, what next
|
||||||
|
uint64_t result = 0;
|
||||||
|
uint32_t flags = 0;
|
||||||
|
|
||||||
|
#define LOGICAL_OP(v) \
|
||||||
|
result = v; \
|
||||||
|
flags |= (result & 0xFFFFFFFF) ? 0 : CPSR_Z; \
|
||||||
|
flags |= (result & 0x80000000) ? CPSR_N : 0; \
|
||||||
|
flags |= shifterCarryOutput ? CPSR_C : 0; \
|
||||||
|
flags |= (CPSR & CPSR_V);
|
||||||
|
|
||||||
|
#define ADD_OP(a, b, c) \
|
||||||
|
result = a + b + (uint32_t)(c); \
|
||||||
|
flags |= (result & 0xFFFFFFFF) ? 0 : CPSR_Z; \
|
||||||
|
flags |= (result & 0x80000000) ? CPSR_N : 0; \
|
||||||
|
flags |= (result & 0x100000000) ? CPSR_C : 0; \
|
||||||
|
flags |= (result >= 0x100000000) ? CPSR_V : 0;
|
||||||
|
|
||||||
|
#define SUB_OP(a, b, c) ADD_OP(a, ~b, c)
|
||||||
|
|
||||||
|
|
||||||
|
switch (Opcode) {
|
||||||
|
case 0: LOGICAL_OP(op1 & op2); break; // AND
|
||||||
|
case 1: LOGICAL_OP(op1 ^ op2); break; // EOR
|
||||||
|
case 2: SUB_OP(op1, op2, 1); break; // SUB
|
||||||
|
case 3: SUB_OP(op2, op1, 1); break; // RSB
|
||||||
|
case 4: ADD_OP(op1, op2, 0); break; // ADD
|
||||||
|
case 5: ADD_OP(op1, op2, flagC()); break; // ADC
|
||||||
|
case 6: SUB_OP(op1, op2, flagC()); break; // SBC
|
||||||
|
case 7: SUB_OP(op2, op1, flagC()); break; // RSC
|
||||||
|
case 8: LOGICAL_OP(op1 & op2); break; // TST
|
||||||
|
case 9: LOGICAL_OP(op1 ^ op2); break; // TEQ
|
||||||
|
case 0xA: SUB_OP(op1, op2, 1); break; // CMP
|
||||||
|
case 0xB: ADD_OP(op1, op2, 0); break; // CMN
|
||||||
|
case 0xC: LOGICAL_OP(op1 | op2); break; // ORR
|
||||||
|
case 0xD: LOGICAL_OP(op2); break; // MOV
|
||||||
|
case 0xE: LOGICAL_OP(op1 & ~op2); break; // BIC
|
||||||
|
case 0xF: LOGICAL_OP(~op2); break; // MVN
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Opcode >= 8 && Opcode <= 0xB) {
|
||||||
|
// Output-less opcodes: special behaviour
|
||||||
|
if (S) {
|
||||||
|
CPSR = (CPSR & ~CPSR_FlagMask) | flags;
|
||||||
|
} else if (Opcode == 8) {
|
||||||
|
// MRS, CPSR -> Reg
|
||||||
|
GPRs[Rd] = CPSR;
|
||||||
|
} else if (Opcode == 9) {
|
||||||
|
// MSR, Reg -> CPSR
|
||||||
|
bool canChangeMode = extract1(Rn, 0);
|
||||||
|
if (canChangeMode && isPrivileged()) {
|
||||||
|
auto newCPSR = GPRs[extract(Operand2, 3, 0)];
|
||||||
|
switchMode(modeFromCPSR(newCPSR));
|
||||||
|
CPSR = newCPSR;
|
||||||
|
} else {
|
||||||
|
// for the flag-only version, immediates are allowed
|
||||||
|
// so we just re-use what was calculated earlier...
|
||||||
|
auto newFlag = I ? op2 : GPRs[extract(Operand2, 3, 0)];
|
||||||
|
CPSR &= ~CPSR_FlagMask;
|
||||||
|
CPSR |= (newFlag & CPSR_FlagMask);
|
||||||
|
}
|
||||||
|
} else if (Opcode == 0xA) {
|
||||||
|
// MRS, SPSR -> Reg
|
||||||
|
if (isPrivileged())
|
||||||
|
GPRs[Rd] = SPSRs[currentBank()];
|
||||||
|
} else /*if (Opcode == 0xB)*/ {
|
||||||
|
bool canChangeMode = extract1(Rn, 0);
|
||||||
|
if (isPrivileged()) {
|
||||||
|
if (canChangeMode) {
|
||||||
|
SPSRs[currentBank()] = GPRs[extract(Operand2, 3, 0)];
|
||||||
|
} else {
|
||||||
|
// same hat
|
||||||
|
auto newFlag = I ? op2 : GPRs[extract(Operand2, 3, 0)];
|
||||||
|
SPSRs[currentBank()] &= ~CPSR_FlagMask;
|
||||||
|
SPSRs[currentBank()] |= (newFlag & CPSR_FlagMask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GPRs[Rd] = result & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
if (Rd == 15) {
|
||||||
|
// Writing to PC
|
||||||
|
// Special things occur here!
|
||||||
|
prefetchCount = 0;
|
||||||
|
if (S && isPrivileged()) {
|
||||||
|
// We SHOULD be privileged
|
||||||
|
// (Raise an error otherwise...?)
|
||||||
|
auto saved = SPSRs[currentBank()];
|
||||||
|
switchMode(modeFromCPSR(saved));
|
||||||
|
CPSR = saved;
|
||||||
|
}
|
||||||
|
} else if (S) {
|
||||||
|
CPSR = (CPSR & ~CPSR_FlagMask) | flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execMultiply(uint32_t AS, uint32_t Rd, uint32_t Rn, uint32_t Rs, uint32_t Rm)
|
||||||
|
{
|
||||||
|
// no need for R15 fuckery
|
||||||
|
// datasheet says it's not allowed here
|
||||||
|
if (AS & 2)
|
||||||
|
GPRs[Rd] = GPRs[Rm] * GPRs[Rs] + GPRs[Rn];
|
||||||
|
else
|
||||||
|
GPRs[Rd] = GPRs[Rm] * GPRs[Rs];
|
||||||
|
|
||||||
|
if (AS & 1) {
|
||||||
|
CPSR &= ~(CPSR_N | CPSR_Z);
|
||||||
|
CPSR |= GPRs[Rd] ? 0 : CPSR_Z;
|
||||||
|
CPSR |= (GPRs[Rd] & 0x80000000) ? CPSR_N : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execSingleDataSwap(bool B, uint32_t Rn, uint32_t Rd, uint32_t Rm)
|
||||||
|
{
|
||||||
|
auto valueSize = B ? V8 : V32;
|
||||||
|
auto readResult = readVirtual(GPRs[Rn], valueSize);
|
||||||
|
auto fault = readResult.second;
|
||||||
|
|
||||||
|
if (fault == NoFault) {
|
||||||
|
fault = writeVirtual(GPRs[Rm], GPRs[Rn], valueSize);
|
||||||
|
if (fault == NoFault)
|
||||||
|
GPRs[Rd] = readResult.first.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fault != NoFault)
|
||||||
|
reportFault(fault);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execSingleDataTransfer(uint32_t IPUBWL, uint32_t Rn, uint32_t Rd, uint32_t offset)
|
||||||
|
{
|
||||||
|
bool load = extract1(IPUBWL, 0);
|
||||||
|
bool writeback = extract1(IPUBWL, 1);
|
||||||
|
auto valueSize = extract1(IPUBWL, 2) ? V8 : V32;
|
||||||
|
bool up = extract1(IPUBWL, 3);
|
||||||
|
bool preIndex = extract1(IPUBWL, 4);
|
||||||
|
bool immediate = extract1(IPUBWL, 5);
|
||||||
|
|
||||||
|
// calculate the offset
|
||||||
|
uint32_t calcOffset;
|
||||||
|
if (!immediate) {
|
||||||
|
// REGISTER
|
||||||
|
uint32_t Rm = extract(offset, 3, 0);
|
||||||
|
calcOffset = GPRs[Rm];
|
||||||
|
|
||||||
|
uint8_t shiftBy = extract(offset, 11, 7);
|
||||||
|
|
||||||
|
switch (extract(offset, 6, 5)) {
|
||||||
|
case 0: // Logical Left (LSL)
|
||||||
|
if (shiftBy > 0)
|
||||||
|
calcOffset <<= shiftBy;
|
||||||
|
break;
|
||||||
|
case 1: // Logical Right (LSR)
|
||||||
|
if (shiftBy == 0)
|
||||||
|
calcOffset = 0;
|
||||||
|
else
|
||||||
|
calcOffset >>= shiftBy;
|
||||||
|
break;
|
||||||
|
case 2: // Arithmetic Right (ASR)
|
||||||
|
if (shiftBy == 0)
|
||||||
|
calcOffset = (int32_t)calcOffset >> 31;
|
||||||
|
else
|
||||||
|
calcOffset = (int32_t)calcOffset >> shiftBy;
|
||||||
|
break;
|
||||||
|
case 3: // Rotate Right (ROR)
|
||||||
|
if (shiftBy == 0) { // treated as RRX
|
||||||
|
calcOffset >>= 1;
|
||||||
|
calcOffset |= flagC() ? 0x80000000 : 0;
|
||||||
|
} else
|
||||||
|
calcOffset = ROR(calcOffset, shiftBy);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// IMMEDIATE
|
||||||
|
uint32_t Rotate = extract(offset, 11, 8);
|
||||||
|
uint32_t Imm = extract(offset, 7, 0);
|
||||||
|
Imm = (uint32_t)(int8_t)Imm;
|
||||||
|
calcOffset = ROR(Imm, Rotate * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t base = GPRs[Rn];
|
||||||
|
if (Rn == 15) base -= 4; // prefetch adjustment
|
||||||
|
uint32_t modifiedBase = up ? (base + calcOffset) : (base - calcOffset);
|
||||||
|
uint32_t transferAddr = preIndex ? modifiedBase : base;
|
||||||
|
|
||||||
|
bool changeModes = !preIndex && writeback && isPrivileged();
|
||||||
|
auto saveMode = currentMode();
|
||||||
|
|
||||||
|
MMUFault fault;
|
||||||
|
|
||||||
|
if (load) {
|
||||||
|
if (changeModes) switchMode(User32);
|
||||||
|
auto readResult = readVirtual(transferAddr, valueSize);
|
||||||
|
if (changeModes) switchMode(saveMode);
|
||||||
|
if (readResult.first.has_value())
|
||||||
|
GPRs[Rd] = readResult.first.value();
|
||||||
|
fault = readResult.second;
|
||||||
|
} else {
|
||||||
|
uint32_t value = GPRs[Rd];
|
||||||
|
if (changeModes) switchMode(User32);
|
||||||
|
fault = writeVirtual(value, transferAddr, valueSize);
|
||||||
|
if (changeModes) switchMode(saveMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preIndex && writeback)
|
||||||
|
GPRs[Rn] = modifiedBase;
|
||||||
|
|
||||||
|
if (fault != NoFault)
|
||||||
|
reportFault(fault);
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execBlockDataTransfer(uint32_t PUSWL, uint32_t Rn, uint32_t registerList)
|
||||||
|
{
|
||||||
|
bool load = extract1(PUSWL, 0);
|
||||||
|
bool store = !load;
|
||||||
|
bool writeback = extract1(PUSWL, 1);
|
||||||
|
bool psrForceUser = extract1(PUSWL, 2);
|
||||||
|
bool up = extract1(PUSWL, 3);
|
||||||
|
bool preIndex = extract1(PUSWL, 4);
|
||||||
|
|
||||||
|
MMUFault fault = NoFault;
|
||||||
|
uint32_t base = GPRs[Rn] & ~3;
|
||||||
|
uint32_t blockSize = popcount32(registerList) * 4;
|
||||||
|
|
||||||
|
uint32_t lowAddr, updatedBase;
|
||||||
|
if (up) {
|
||||||
|
updatedBase = base + blockSize;
|
||||||
|
lowAddr = base + (preIndex ? 4 : 0);
|
||||||
|
} else {
|
||||||
|
updatedBase = base - blockSize;
|
||||||
|
lowAddr = updatedBase + (preIndex ? 0 : 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto saveBank = bank;
|
||||||
|
if (psrForceUser && (store || !(registerList & 0x8000)))
|
||||||
|
switchBank(MainBank);
|
||||||
|
|
||||||
|
bool doneWriteback = false;
|
||||||
|
if (load && writeback) {
|
||||||
|
doneWriteback = true;
|
||||||
|
GPRs[Rn] = updatedBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t addr = lowAddr;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (registerList & (1 << i)) {
|
||||||
|
// work on this one
|
||||||
|
if (load) {
|
||||||
|
// handling for LDM faults may be kinda iffy...
|
||||||
|
// wording on datasheet is a bit unclear
|
||||||
|
auto readResult = readVirtual(addr, V32);
|
||||||
|
if (readResult.first.has_value())
|
||||||
|
GPRs[i] = readResult.first.value();
|
||||||
|
if (readResult.second != NoFault) {
|
||||||
|
fault = readResult.second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto newFault = writeVirtual(GPRs[i], addr, V32);
|
||||||
|
if (newFault != NoFault)
|
||||||
|
fault = newFault;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr += 4;
|
||||||
|
|
||||||
|
if (writeback && !doneWriteback) {
|
||||||
|
doneWriteback = true;
|
||||||
|
GPRs[Rn] = updatedBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// datasheet specifies that base register must be
|
||||||
|
// restored if an error occurs during LDM
|
||||||
|
if (load && fault != NoFault)
|
||||||
|
GPRs[Rn] = writeback ? updatedBase : base;
|
||||||
|
|
||||||
|
if (psrForceUser && (!load || !(registerList & 0x8000)))
|
||||||
|
switchBank(saveBank);
|
||||||
|
|
||||||
|
if (fault != NoFault)
|
||||||
|
reportFault(fault);
|
||||||
|
|
||||||
|
return 0; // fixme
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execBranch(bool L, uint32_t offset)
|
||||||
|
{
|
||||||
|
if (L)
|
||||||
|
GPRs[14] = GPRs[15] - 8;
|
||||||
|
|
||||||
|
// start with 24 bits, shift left 2, sign extend to 32
|
||||||
|
int32_t sextOffset = (int32_t)(offset << 8) >> 6;
|
||||||
|
|
||||||
|
prefetchCount = 0;
|
||||||
|
GPRs[15] -= 4; // account for our prefetch being +4 too much
|
||||||
|
GPRs[15] += sextOffset;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ARM710a::execCP15RegisterTransfer(uint32_t CPOpc, bool L, uint32_t CRn, uint32_t Rd, uint32_t CP, uint32_t CRm)
|
||||||
|
{
|
||||||
|
(void)CPOpc; // not used by ARM CP15
|
||||||
|
(void)CP;
|
||||||
|
(void)CRm;
|
||||||
|
|
||||||
|
if (!isPrivileged())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (L) {
|
||||||
|
// read a value
|
||||||
|
uint32_t what = 0;
|
||||||
|
|
||||||
|
switch (CRn) {
|
||||||
|
case 0: what = cp15_id; break;
|
||||||
|
case 5: what = cp15_faultStatus; break;
|
||||||
|
case 6: what = cp15_faultAddress; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Rd == 15)
|
||||||
|
CPSR = (CPSR & ~CPSR_FlagMask) | (what & CPSR_FlagMask);
|
||||||
|
else
|
||||||
|
GPRs[Rd] = what;
|
||||||
|
} else {
|
||||||
|
// store a value
|
||||||
|
uint32_t what = GPRs[Rd];
|
||||||
|
|
||||||
|
switch (CRn) {
|
||||||
|
case 1: cp15_control = what; break;
|
||||||
|
case 2: cp15_translationTableBase = what; break;
|
||||||
|
case 3: cp15_domainAccessControl = what; break;
|
||||||
|
case 5: flushTlb(); break;
|
||||||
|
case 6: flushTlb(what); break;
|
||||||
|
case 7: clearCache(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ARM710a::clearCache() {
|
||||||
|
for (uint32_t i = 0; i < CacheSets; i++) {
|
||||||
|
for (uint32_t j = 0; j < CacheBlocksPerSet; j++) {
|
||||||
|
cacheBlockTags[i][j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *ARM710a::findCacheLine(uint32_t virtAddr) {
|
||||||
|
uint32_t set = virtAddr & CacheAddressSetMask;
|
||||||
|
uint32_t tag = virtAddr & CacheAddressTagMask;
|
||||||
|
set >>= CacheAddressSetShift;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < CacheBlocksPerSet; i++) {
|
||||||
|
if (cacheBlockTags[set][i] & CacheBlockEnabled) {
|
||||||
|
if ((cacheBlockTags[set][i] & ~CacheBlockEnabled) == tag)
|
||||||
|
return &cacheBlocks[set][i][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pair<MaybeU32, ARM710a::MMUFault> ARM710a::addCacheLineAndRead(uint32_t physAddr, uint32_t virtAddr, ValueSize valueSize, int domain, bool isPage) {
|
||||||
|
uint32_t set = virtAddr & CacheAddressSetMask;
|
||||||
|
uint32_t tag = virtAddr & CacheAddressTagMask;
|
||||||
|
set >>= CacheAddressSetShift;
|
||||||
|
|
||||||
|
// "it will be randomly placed in a cache bank"
|
||||||
|
// - the ARM710a data sheet, 6-2 (p90)
|
||||||
|
uint32_t i = rand() % CacheBlocksPerSet;
|
||||||
|
uint8_t *block = &cacheBlocks[set][i][0];
|
||||||
|
MaybeU32 result;
|
||||||
|
MMUFault fault = NoFault;
|
||||||
|
|
||||||
|
for (uint32_t j = 0; j < CacheBlockSize; j += 4) {
|
||||||
|
auto word = readPhysical(physAddr + j, V32);
|
||||||
|
if (word.has_value()) {
|
||||||
|
write32LE(&block[j], word.value());
|
||||||
|
if (valueSize == V8 && j == (virtAddr & CacheAddressLineMask & ~3))
|
||||||
|
result = (word.value() >> ((virtAddr & 3) * 8)) & 0xFF;
|
||||||
|
else if (valueSize == V32 && j == (virtAddr & CacheAddressLineMask))
|
||||||
|
result = word.value();
|
||||||
|
} else {
|
||||||
|
// read error, great
|
||||||
|
// TODO: should probably prioritise specific kinds of faults over others
|
||||||
|
fault = encodeFaultSorP(SorPLinefetchError, isPage, domain, virtAddr & ~CacheAddressLineMask);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the cache block is only stored if it's complete
|
||||||
|
if (fault == NoFault)
|
||||||
|
cacheBlockTags[set][i] = tag | CacheBlockEnabled;
|
||||||
|
|
||||||
|
return make_pair(result, fault);
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeU32 ARM710a::readCached(uint32_t virtAddr, ValueSize valueSize) {
|
||||||
|
uint8_t *line = findCacheLine(virtAddr);
|
||||||
|
if (line) {
|
||||||
|
if (valueSize == V8)
|
||||||
|
return line[virtAddr & CacheAddressLineMask];
|
||||||
|
else /*if (valueSize == V32)*/
|
||||||
|
return read32LE(&line[virtAddr & CacheAddressLineMask]);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ARM710a::writeCached(uint32_t value, uint32_t virtAddr, ValueSize valueSize) {
|
||||||
|
uint8_t *line = findCacheLine(virtAddr);
|
||||||
|
if (line) {
|
||||||
|
if (valueSize == V8)
|
||||||
|
line[virtAddr & CacheAddressLineMask] = value & 0xFF;
|
||||||
|
else /*if (valueSize == V32)*/
|
||||||
|
write32LE(&line[virtAddr & CacheAddressLineMask], value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t ARM710a::physAddrFromTlbEntry(TlbEntry *tlbEntry, uint32_t virtAddr) {
|
||||||
|
if ((tlbEntry->lv2Entry & 3) == 2) {
|
||||||
|
// Smøl page
|
||||||
|
return (tlbEntry->lv2Entry & 0xFFFFF000) | (virtAddr & 0xFFF);
|
||||||
|
} else if ((tlbEntry->lv2Entry & 3) == 1) {
|
||||||
|
// Lørge page
|
||||||
|
return (tlbEntry->lv2Entry & 0xFFFF0000) | (virtAddr & 0xFFFF);
|
||||||
|
} else {
|
||||||
|
// Section
|
||||||
|
return (tlbEntry->lv1Entry & 0xFFF00000) | (virtAddr & 0xFFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MaybeU32 ARM710a::virtToPhys(uint32_t virtAddr) {
|
||||||
|
if (!isMMUEnabled())
|
||||||
|
return virtAddr;
|
||||||
|
|
||||||
|
TlbEntry tempEntry;
|
||||||
|
auto translated = translateAddressUsingTlb(virtAddr, &tempEntry);
|
||||||
|
if (holds_alternative<TlbEntry *>(translated)) {
|
||||||
|
auto tlbEntry = get<TlbEntry *>(translated);
|
||||||
|
return physAddrFromTlbEntry(tlbEntry, virtAddr);
|
||||||
|
} else {
|
||||||
|
return MaybeU32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MaybeU32 ARM710a::readVirtualDebug(uint32_t virtAddr, ValueSize valueSize) {
|
||||||
|
if (auto v = virtToPhys(virtAddr); v.has_value())
|
||||||
|
return readPhysical(v.value(), valueSize);
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pair<MaybeU32, ARM710a::MMUFault> ARM710a::readVirtual(uint32_t virtAddr, ValueSize valueSize) {
|
||||||
|
if (isAlignmentFaultEnabled() && valueSize == V32 && virtAddr & 3)
|
||||||
|
return make_pair(MaybeU32(), encodeFault(AlignmentFault, 0, virtAddr));
|
||||||
|
|
||||||
|
// fast path: cache
|
||||||
|
if (auto v = readCached(virtAddr, valueSize); v.has_value())
|
||||||
|
return make_pair(v.value(), NoFault);
|
||||||
|
|
||||||
|
if (!isMMUEnabled()) {
|
||||||
|
// things are very simple without a MMU
|
||||||
|
if (auto v = readPhysical(virtAddr, valueSize); v.has_value())
|
||||||
|
return make_pair(v.value(), NoFault);
|
||||||
|
else
|
||||||
|
return make_pair(MaybeU32(), NonMMUError);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto translated = translateAddressUsingTlb(virtAddr);
|
||||||
|
if (holds_alternative<MMUFault>(translated))
|
||||||
|
return make_pair(MaybeU32(), get<MMUFault>(translated));
|
||||||
|
|
||||||
|
// resolve this boy
|
||||||
|
auto tlbEntry = get<TlbEntry *>(translated);
|
||||||
|
|
||||||
|
if (auto f = checkAccessPermissions(tlbEntry, virtAddr, false); f != NoFault)
|
||||||
|
return make_pair(MaybeU32(), f);
|
||||||
|
|
||||||
|
int domain = (tlbEntry->lv1Entry >> 5) & 0xF;
|
||||||
|
bool isPage = (tlbEntry->lv2Entry != 0);
|
||||||
|
|
||||||
|
uint32_t physAddr = physAddrFromTlbEntry(tlbEntry, virtAddr);
|
||||||
|
bool cacheable = tlbEntry->lv2Entry ? (tlbEntry->lv2Entry & 8) : (tlbEntry->lv1Entry & 8);
|
||||||
|
|
||||||
|
if (cacheable && isCacheEnabled())
|
||||||
|
return addCacheLineAndRead(physAddr, virtAddr, valueSize, domain, isPage);
|
||||||
|
else if (auto result = readPhysical(physAddr, valueSize); result.has_value())
|
||||||
|
return make_pair(result, NoFault);
|
||||||
|
else
|
||||||
|
return make_pair(result, encodeFaultSorP(SorPOtherBusError, isPage, domain, virtAddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
ARM710a::MMUFault ARM710a::writeVirtual(uint32_t value, uint32_t virtAddr, ValueSize valueSize) {
|
||||||
|
if (isAlignmentFaultEnabled() && valueSize == V32 && virtAddr & 3)
|
||||||
|
return encodeFault(AlignmentFault, 0, virtAddr);
|
||||||
|
|
||||||
|
if (!isMMUEnabled()) {
|
||||||
|
// direct virtual -> physical mapping, sans MMU
|
||||||
|
if (!writePhysical(value, virtAddr, valueSize))
|
||||||
|
return NonMMUError;
|
||||||
|
} else {
|
||||||
|
auto translated = translateAddressUsingTlb(virtAddr);
|
||||||
|
if (holds_alternative<MMUFault>(translated))
|
||||||
|
return get<MMUFault>(translated);
|
||||||
|
|
||||||
|
// resolve this boy
|
||||||
|
auto tlbEntry = get<TlbEntry *>(translated);
|
||||||
|
|
||||||
|
if (auto f = checkAccessPermissions(tlbEntry, virtAddr, true); f != NoFault)
|
||||||
|
return f;
|
||||||
|
|
||||||
|
uint32_t physAddr = physAddrFromTlbEntry(tlbEntry, virtAddr);
|
||||||
|
int domain = (tlbEntry->lv1Entry >> 5) & 0xF;
|
||||||
|
bool isPage = (tlbEntry->lv2Entry != 0);
|
||||||
|
|
||||||
|
if (!writePhysical(value, physAddr, valueSize))
|
||||||
|
return encodeFaultSorP(SorPOtherBusError, isPage, domain, virtAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit to cache if all was good
|
||||||
|
writeCached(value, virtAddr, valueSize);
|
||||||
|
return NoFault;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TLB
|
||||||
|
void ARM710a::flushTlb() {
|
||||||
|
for (TlbEntry &e : tlb)
|
||||||
|
e = {0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
void ARM710a::flushTlb(uint32_t virtAddr) {
|
||||||
|
for (TlbEntry &e : tlb) {
|
||||||
|
if (e.addrMask && (virtAddr & e.addrMask) == e.addr) {
|
||||||
|
e = {0, 0, 0, 0};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARM710a::TlbEntry *ARM710a::_allocateTlbEntry(uint32_t addrMask, uint32_t addr) {
|
||||||
|
TlbEntry *entry = &tlb[nextTlbIndex];
|
||||||
|
entry->addrMask = addrMask;
|
||||||
|
entry->addr = addr & addrMask;
|
||||||
|
nextTlbIndex = (nextTlbIndex + 1) % TlbSize;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
variant<ARM710a::TlbEntry *, ARM710a::MMUFault> ARM710a::translateAddressUsingTlb(uint32_t virtAddr, TlbEntry *useMe) {
|
||||||
|
// first things first, do we have a matching entry in the TLB?
|
||||||
|
for (TlbEntry &e : tlb) {
|
||||||
|
if (e.addrMask && (virtAddr & e.addrMask) == e.addr)
|
||||||
|
return &e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no, so do a page table walk
|
||||||
|
TlbEntry *entry;
|
||||||
|
uint32_t tableIndex = virtAddr >> 20;
|
||||||
|
|
||||||
|
// fetch the Level 1 entry
|
||||||
|
auto lv1EntryOpt = readPhysical(cp15_translationTableBase | (tableIndex << 2), V32);
|
||||||
|
if (!lv1EntryOpt.has_value())
|
||||||
|
return Lv1TranslationError;
|
||||||
|
auto lv1Entry = lv1EntryOpt.value();
|
||||||
|
int domain = (lv1Entry >> 5) & 0xF;
|
||||||
|
|
||||||
|
switch (lv1Entry & 3) {
|
||||||
|
case 0:
|
||||||
|
case 3:
|
||||||
|
// invalid!
|
||||||
|
return encodeFault(SectionTranslationFault, domain, virtAddr);
|
||||||
|
case 2:
|
||||||
|
// a Section entry is straightforward
|
||||||
|
// we just throw that immediately into the TLB
|
||||||
|
entry = useMe ? useMe : _allocateTlbEntry(0xFFF00000, virtAddr);
|
||||||
|
entry->lv1Entry = lv1Entry;
|
||||||
|
entry->lv2Entry = 0;
|
||||||
|
return entry;
|
||||||
|
case 1:
|
||||||
|
// a Page requires a Level 2 read
|
||||||
|
uint32_t pageTableAddr = lv1Entry & 0xFFFFFC00;
|
||||||
|
uint32_t lv2TableIndex = (virtAddr >> 12) & 0xFF;
|
||||||
|
|
||||||
|
auto lv2EntryOpt = readPhysical(pageTableAddr | (lv2TableIndex << 2), V32);
|
||||||
|
if (!lv2EntryOpt.has_value())
|
||||||
|
return encodeFault(Lv2TranslationError, domain, virtAddr);
|
||||||
|
auto lv2Entry = lv2EntryOpt.value();
|
||||||
|
|
||||||
|
switch (lv2Entry & 3) {
|
||||||
|
case 0:
|
||||||
|
case 3:
|
||||||
|
// invalid!
|
||||||
|
return encodeFault(PageTranslationFault, domain, virtAddr);
|
||||||
|
case 1:
|
||||||
|
// Large 64kb page
|
||||||
|
entry = useMe ? useMe : _allocateTlbEntry(0xFFFF0000, virtAddr);
|
||||||
|
entry->lv1Entry = lv1Entry;
|
||||||
|
entry->lv2Entry = lv2Entry;
|
||||||
|
return entry;
|
||||||
|
case 2:
|
||||||
|
// Small 4kb page
|
||||||
|
entry = useMe ? useMe : _allocateTlbEntry(0xFFFFF000, virtAddr);
|
||||||
|
entry->lv1Entry = lv1Entry;
|
||||||
|
entry->lv2Entry = lv2Entry;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should never get here as the switch covers 0, 1, 2, 3
|
||||||
|
// but this satisfies a compiler warning
|
||||||
|
return SectionTranslationFault;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ARM710a::MMUFault ARM710a::checkAccessPermissions(ARM710a::TlbEntry *entry, uint32_t virtAddr, bool isWrite) const {
|
||||||
|
int domain;
|
||||||
|
int accessPerms;
|
||||||
|
bool isPage;
|
||||||
|
|
||||||
|
// extract info from the entries
|
||||||
|
domain = (entry->lv1Entry >> 5) & 0xF;
|
||||||
|
if (entry->lv2Entry) {
|
||||||
|
// Page
|
||||||
|
accessPerms = (entry->lv2Entry >> 4) & 0xFF;
|
||||||
|
|
||||||
|
int permIndex;
|
||||||
|
if ((entry->lv2Entry & 3) == 1) // Large 64kb
|
||||||
|
permIndex = (virtAddr >> 14) & 3;
|
||||||
|
else // Small 4kb
|
||||||
|
permIndex = (virtAddr >> 10) & 3;
|
||||||
|
|
||||||
|
accessPerms >>= (permIndex * 2);
|
||||||
|
accessPerms &= 3;
|
||||||
|
isPage = true;
|
||||||
|
} else {
|
||||||
|
// Section
|
||||||
|
accessPerms = (entry->lv1Entry >> 10) & 3;
|
||||||
|
isPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, do our checks
|
||||||
|
int primaryAccessControls = (cp15_domainAccessControl >> (domain * 2)) & 3;
|
||||||
|
|
||||||
|
// Manager: always allowed
|
||||||
|
if (primaryAccessControls == 3)
|
||||||
|
return NoFault;
|
||||||
|
|
||||||
|
// Client: enforce checks!
|
||||||
|
if (primaryAccessControls == 1) {
|
||||||
|
#define OK_IF_TRUE(b) return ((b) ? NoFault : encodeFaultSorP(SorPPermissionFault, isPage, domain, virtAddr))
|
||||||
|
bool System = cp15_control & 0x100;
|
||||||
|
bool ROM = cp15_control & 0x200;
|
||||||
|
|
||||||
|
if (accessPerms == 0) {
|
||||||
|
if (!System && !ROM) {
|
||||||
|
// 00/0/0: Any access generates a permission fault
|
||||||
|
OK_IF_TRUE(false);
|
||||||
|
} else if (System && !ROM) {
|
||||||
|
// 00/1/0: Supervisor read only permitted
|
||||||
|
OK_IF_TRUE(!isWrite && isPrivileged());
|
||||||
|
} else if (!System && ROM) {
|
||||||
|
// 00/0/1: Any write generates a permission fault
|
||||||
|
OK_IF_TRUE(!isWrite);
|
||||||
|
} else /*if (System && ROM)*/ {
|
||||||
|
// Reserved
|
||||||
|
OK_IF_TRUE(false);
|
||||||
|
}
|
||||||
|
} else if (accessPerms == 1) {
|
||||||
|
// 01/x/x: Access allowed only in Supervisor mode
|
||||||
|
OK_IF_TRUE(isPrivileged());
|
||||||
|
} else if (accessPerms == 2) {
|
||||||
|
// 10/x/x: Writes in User mode cause permission fault
|
||||||
|
OK_IF_TRUE(!isWrite || isPrivileged());
|
||||||
|
} else /*if (accessPerms == 3)*/ {
|
||||||
|
// 11/x/x: All access types permitted in both modes
|
||||||
|
OK_IF_TRUE(true);
|
||||||
|
}
|
||||||
|
#undef OK_IF_TRUE
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Access or Reserved: never allowed (Domain Fault)
|
||||||
|
return encodeFaultSorP(SorPDomainFault, isPage, domain, virtAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ARM710a::reportFault(MMUFault fault) {
|
||||||
|
if (fault != NoFault) {
|
||||||
|
if ((fault & 0xF) != NonMMUError) {
|
||||||
|
cp15_faultStatus = fault & 0xFFFF;
|
||||||
|
cp15_faultAddress = fault >> 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this signals a branch to DataAbort after the
|
||||||
|
// instruction is done executing
|
||||||
|
faultTriggeredThisCycle = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// ASSUMPTIONS:
|
||||||
|
// - Little-endian will be used
|
||||||
|
// - 26-bit address spaces will not be used
|
||||||
|
// - Alignment faults will always be on
|
||||||
|
|
||||||
|
// Write buffer is 4 address FIFO, 8 data FIFO
|
||||||
|
// TLB is 64 entries
|
||||||
|
|
||||||
|
typedef optional<uint32_t> MaybeU32;
|
||||||
|
|
||||||
|
class ARM710a
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum ValueSize { V8 = 0, V32 = 1 };
|
||||||
|
|
||||||
|
enum MMUFault : uint64_t {
|
||||||
|
// ref: datasheet 9-13 (p111)
|
||||||
|
NoFault = 0,
|
||||||
|
AlignmentFault = 1,
|
||||||
|
// the ARM gods say there is to be no fault 2 or 3
|
||||||
|
SectionLinefetchError = 4,
|
||||||
|
SectionTranslationFault = 5,
|
||||||
|
PageLinefetchError = 6,
|
||||||
|
PageTranslationFault = 7,
|
||||||
|
SectionOtherBusError = 8,
|
||||||
|
SectionDomainFault = 9,
|
||||||
|
PageOtherBusError = 0xA,
|
||||||
|
PageDomainFault = 0xB,
|
||||||
|
Lv1TranslationError = 0xC,
|
||||||
|
SectionPermissionFault = 0xD,
|
||||||
|
Lv2TranslationError = 0xE,
|
||||||
|
PagePermissionFault = 0xF,
|
||||||
|
|
||||||
|
// not actually in the ARM datasheet
|
||||||
|
// so we are reusing it for nefarious purposes
|
||||||
|
NonMMUError = 3,
|
||||||
|
|
||||||
|
MMUFaultDomainMask = 0xF0,
|
||||||
|
MMUFaultAddressMask = 0xFFFFFFFF00000000
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ARM710a() {
|
||||||
|
cp15_id = 0x41047100;
|
||||||
|
clearAllValues();
|
||||||
|
}
|
||||||
|
virtual ~ARM710a() { }
|
||||||
|
|
||||||
|
void clearAllValues() {
|
||||||
|
bank = MainBank;
|
||||||
|
CPSR = 0;
|
||||||
|
for (int i = 0; i < 16; i++) GPRs[i] = 0;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
fiqBankedRegisters[0][i] = 0;
|
||||||
|
fiqBankedRegisters[1][i] = 0;
|
||||||
|
SPSRs[i] = 0;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
allModesBankedRegisters[i][0] = 0;
|
||||||
|
allModesBankedRegisters[i][1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp15_control = 0;
|
||||||
|
cp15_translationTableBase = 0;
|
||||||
|
cp15_domainAccessControl = 0;
|
||||||
|
cp15_faultStatus = 0;
|
||||||
|
cp15_faultAddress = 0;
|
||||||
|
prefetchCount = 0;
|
||||||
|
clearCache();
|
||||||
|
flushTlb();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setProcessorID(uint32_t v) { cp15_id = v; }
|
||||||
|
void requestFIQ(); // pull nFIQ low
|
||||||
|
void requestIRQ(); // pull nIRQ low
|
||||||
|
void reset(); // pull nRESET low
|
||||||
|
|
||||||
|
uint32_t tick(); // run the chip for at least 1 clock cycle
|
||||||
|
|
||||||
|
MaybeU32 readVirtualDebug(uint32_t virtAddr, ValueSize valueSize);
|
||||||
|
MaybeU32 virtToPhys(uint32_t virtAddr);
|
||||||
|
|
||||||
|
pair<MaybeU32, MMUFault> readVirtual(uint32_t virtAddr, ValueSize valueSize);
|
||||||
|
virtual MaybeU32 readPhysical(uint32_t physAddr, ValueSize valueSize) = 0;
|
||||||
|
MMUFault writeVirtual(uint32_t value, uint32_t virtAddr, ARM710a::ValueSize valueSize);
|
||||||
|
virtual bool writePhysical(uint32_t value, uint32_t physAddr, ARM710a::ValueSize valueSize) = 0;
|
||||||
|
|
||||||
|
uint32_t getGPR(int index) const { return GPRs[index]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum { Nop = 0xE1A00000 };
|
||||||
|
|
||||||
|
enum Mode : uint8_t {
|
||||||
|
User32 = 0x10,
|
||||||
|
FIQ32 = 0x11,
|
||||||
|
IRQ32 = 0x12,
|
||||||
|
Supervisor32 = 0x13,
|
||||||
|
Abort32 = 0x17,
|
||||||
|
Undefined32 = 0x1B
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BankIndex : uint8_t {
|
||||||
|
FiqBank,
|
||||||
|
IrqBank,
|
||||||
|
SvcBank,
|
||||||
|
AbtBank,
|
||||||
|
UndBank,
|
||||||
|
MainBank
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr static const BankIndex modeToBank[16] = {
|
||||||
|
MainBank, FiqBank, IrqBank, SvcBank,
|
||||||
|
MainBank, MainBank, MainBank, AbtBank,
|
||||||
|
MainBank, MainBank, MainBank, UndBank,
|
||||||
|
MainBank, MainBank, MainBank, MainBank
|
||||||
|
};
|
||||||
|
|
||||||
|
enum : uint32_t {
|
||||||
|
CPSR_ModeMask = 0x0000001F,
|
||||||
|
CPSR_FIQDisable = 0x00000040,
|
||||||
|
CPSR_IRQDisable = 0x00000080,
|
||||||
|
CPSR_V = 0x10000000,
|
||||||
|
CPSR_C = 0x20000000,
|
||||||
|
CPSR_Z = 0x40000000,
|
||||||
|
CPSR_N = 0x80000000,
|
||||||
|
CPSR_FlagMask = 0xF0000000
|
||||||
|
};
|
||||||
|
|
||||||
|
// active state
|
||||||
|
BankIndex bank;
|
||||||
|
uint32_t CPSR;
|
||||||
|
uint32_t GPRs[16];
|
||||||
|
|
||||||
|
// saved state
|
||||||
|
uint32_t fiqBankedRegisters[2][5]; // R8..R12 inclusive
|
||||||
|
uint32_t allModesBankedRegisters[6][2]; // R13, R14
|
||||||
|
uint32_t SPSRs[5];
|
||||||
|
|
||||||
|
// coprocessor 15
|
||||||
|
uint32_t cp15_id; // 0: read-only
|
||||||
|
uint32_t cp15_control; // 1: write-only
|
||||||
|
uint32_t cp15_translationTableBase; // 2: write-only
|
||||||
|
uint32_t cp15_domainAccessControl; // 3: write-only
|
||||||
|
uint8_t cp15_faultStatus; // 5: read-only (writing has unrelated effects)
|
||||||
|
uint32_t cp15_faultAddress; // 6: read-only (writing has unrelated effects)
|
||||||
|
|
||||||
|
bool flagV() const { return CPSR & CPSR_V; }
|
||||||
|
bool flagC() const { return CPSR & CPSR_C; }
|
||||||
|
bool flagZ() const { return CPSR & CPSR_Z; }
|
||||||
|
bool flagN() const { return CPSR & CPSR_N; }
|
||||||
|
bool checkCondition(int cond) const {
|
||||||
|
switch (cond) {
|
||||||
|
case 0: return flagZ();
|
||||||
|
case 1: return !flagZ();
|
||||||
|
case 2: return flagC();
|
||||||
|
case 3: return !flagC();
|
||||||
|
case 4: return flagN();
|
||||||
|
case 5: return !flagN();
|
||||||
|
case 6: return flagV();
|
||||||
|
case 7: return !flagV();
|
||||||
|
case 8: return flagC() && !flagZ();
|
||||||
|
case 9: return !flagC() || flagZ();
|
||||||
|
case 0xA: return flagN() == flagV();
|
||||||
|
case 0xB: return flagN() != flagV();
|
||||||
|
case 0xC: return !flagZ() && (flagN() == flagV());
|
||||||
|
case 0xD: return flagZ() || (flagN() != flagV());
|
||||||
|
case 0xE: return true;
|
||||||
|
/*case 0xF:*/
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Mode modeFromCPSR(uint32_t v) { return (Mode)(v & CPSR_ModeMask); }
|
||||||
|
Mode currentMode() const { return modeFromCPSR(CPSR); }
|
||||||
|
BankIndex currentBank() const { return modeToBank[(Mode)(CPSR & 0xF)]; }
|
||||||
|
bool isPrivileged() const { return (CPSR & 0x1F) > User32; }
|
||||||
|
bool isMMUEnabled() const { return (cp15_control & 1); }
|
||||||
|
bool isAlignmentFaultEnabled() const { return (cp15_control & 2); }
|
||||||
|
bool isCacheEnabled() const { return (cp15_control & 4); }
|
||||||
|
bool isWriteBufferEnabled() const { return (cp15_control & 8); }
|
||||||
|
|
||||||
|
void switchMode(Mode mode);
|
||||||
|
void switchBank(BankIndex bank);
|
||||||
|
void raiseException(Mode mode, uint32_t savedPC, uint32_t newPC);
|
||||||
|
|
||||||
|
// MMU/TLB
|
||||||
|
enum MMUFaultSorP : uint64_t {
|
||||||
|
SorPLinefetchError = 4,
|
||||||
|
SorPTranslationFault = 5,
|
||||||
|
SorPOtherBusError = 8,
|
||||||
|
SorPDomainFault = 9,
|
||||||
|
SorPPermissionFault = 0xD,
|
||||||
|
};
|
||||||
|
|
||||||
|
MMUFault encodeFault(MMUFault fault, int domain, uint32_t virtAddr) const {
|
||||||
|
return (MMUFault)(fault | (domain << 4) | ((uint64_t)virtAddr << 32));
|
||||||
|
}
|
||||||
|
MMUFault encodeFaultSorP(MMUFaultSorP baseFault, bool isPage, int domain, uint32_t virtAddr) const {
|
||||||
|
return (MMUFault)(baseFault | (isPage ? 2 : 0) | (domain << 4) | ((uint64_t)virtAddr << 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { TlbSize = 64 };
|
||||||
|
struct TlbEntry { uint32_t addrMask, addr, lv1Entry, lv2Entry; };
|
||||||
|
TlbEntry tlb[TlbSize];
|
||||||
|
int nextTlbIndex = 0;
|
||||||
|
|
||||||
|
void flushTlb();
|
||||||
|
void flushTlb(uint32_t virtAddr);
|
||||||
|
variant<TlbEntry *, MMUFault> translateAddressUsingTlb(uint32_t virtAddr, TlbEntry *useMe=nullptr);
|
||||||
|
TlbEntry *_allocateTlbEntry(uint32_t addrMask, uint32_t addr);
|
||||||
|
|
||||||
|
static uint32_t physAddrFromTlbEntry(TlbEntry *tlbEntry, uint32_t virtAddr);
|
||||||
|
MMUFault checkAccessPermissions(TlbEntry *entry, uint32_t virtAddr, bool isWrite) const;
|
||||||
|
|
||||||
|
bool faultTriggeredThisCycle = false;
|
||||||
|
void reportFault(MMUFault fault);
|
||||||
|
|
||||||
|
// Instruction/Data Cache
|
||||||
|
enum {
|
||||||
|
CacheSets = 4,
|
||||||
|
CacheBlocksPerSet = 128,
|
||||||
|
CacheBlockSize = 0x10,
|
||||||
|
|
||||||
|
CacheAddressLineMask = 0x0000000F,
|
||||||
|
CacheAddressSetMask = 0x00000030, CacheAddressSetShift = 4,
|
||||||
|
CacheAddressTagMask = 0xFFFFFFC0,
|
||||||
|
|
||||||
|
CacheBlockEnabled = 1
|
||||||
|
};
|
||||||
|
uint32_t cacheBlockTags[CacheSets][CacheBlocksPerSet];
|
||||||
|
uint8_t cacheBlocks[CacheSets][CacheBlocksPerSet][CacheBlockSize];
|
||||||
|
|
||||||
|
void clearCache();
|
||||||
|
uint8_t *findCacheLine(uint32_t virtAddr);
|
||||||
|
pair<MaybeU32, MMUFault> addCacheLineAndRead(uint32_t physAddr, uint32_t virtAddr, ValueSize valueSize, int domain, bool isPage);
|
||||||
|
MaybeU32 readCached(uint32_t virtAddr, ValueSize valueSize);
|
||||||
|
bool writeCached(uint32_t value, uint32_t virtAddr, ValueSize valueSize);
|
||||||
|
|
||||||
|
// Instruction Loop
|
||||||
|
int prefetchCount;
|
||||||
|
uint32_t prefetch[2];
|
||||||
|
MMUFault prefetchFaults[2];
|
||||||
|
|
||||||
|
uint32_t executeInstruction(uint32_t insn);
|
||||||
|
|
||||||
|
uint32_t execDataProcessing(bool I, uint32_t Opcode, bool S, uint32_t Rn, uint32_t Rd, uint32_t Operand2);
|
||||||
|
uint32_t execMultiply(uint32_t AS, uint32_t Rd, uint32_t Rn, uint32_t Rs, uint32_t Rm);
|
||||||
|
uint32_t execSingleDataSwap(bool B, uint32_t Rn, uint32_t Rd, uint32_t Rm);
|
||||||
|
uint32_t execSingleDataTransfer(uint32_t IPUBWL, uint32_t Rn, uint32_t Rd, uint32_t offset);
|
||||||
|
uint32_t execBlockDataTransfer(uint32_t PUSWL, uint32_t Rn, uint32_t registerList);
|
||||||
|
uint32_t execBranch(bool L, uint32_t offset);
|
||||||
|
uint32_t execCP15RegisterTransfer(uint32_t CPOpc, bool L, uint32_t CRn, uint32_t Rd, uint32_t CP, uint32_t CRm);
|
||||||
|
};
|
664
WindCore/emu.cpp
664
WindCore/emu.cpp
|
@ -2,6 +2,7 @@
|
||||||
#include "wind.h"
|
#include "wind.h"
|
||||||
#include "wind_hw.h"
|
#include "wind_hw.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
|
||||||
#define INCLUDE_BANK1
|
#define INCLUDE_BANK1
|
||||||
|
@ -41,19 +42,19 @@ uint32_t Emu::readReg8(uint32_t reg) {
|
||||||
} else if (reg == PDDDR) {
|
} else if (reg == PDDDR) {
|
||||||
return portDirections & 0xFF;
|
return portDirections & 0xFF;
|
||||||
} else {
|
} else {
|
||||||
// printf("RegRead8 unknown:: pc=%08x lr=%08x reg=%03x\n", cpu.gprs[ARM_PC]-4, cpu.gprs[ARM_LR], reg);
|
// printf("RegRead8 unknown:: pc=%08x lr=%08x reg=%03x\n", getGPR(15)-4, getGPR(14), reg);
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint32_t Emu::readReg32(uint32_t reg) {
|
uint32_t Emu::readReg32(uint32_t reg) {
|
||||||
if (reg == LCDCTL) {
|
if (reg == LCDCTL) {
|
||||||
printf("LCD control read pc=%08x lr=%08x !!!\n", cpu.gprs[ARM_PC], cpu.gprs[ARM_LR]);
|
printf("LCD control read pc=%08x lr=%08x !!!\n", getGPR(15), getGPR(14));
|
||||||
return lcdControl;
|
return lcdControl;
|
||||||
} else if (reg == LCDST) {
|
} else if (reg == LCDST) {
|
||||||
printf("LCD state read pc=%08x lr=%08x !!!\n", cpu.gprs[ARM_PC], cpu.gprs[ARM_LR]);
|
printf("LCD state read pc=%08x lr=%08x !!!\n", getGPR(15), getGPR(14));
|
||||||
return 0xFFFFFFFF;
|
return 0xFFFFFFFF;
|
||||||
} else if (reg == PWRSR) {
|
} else if (reg == PWRSR) {
|
||||||
// printf("!!! PWRSR read pc=%08x lr=%08x !!!\n", cpu.gprs[ARM_PC], cpu.gprs[ARM_LR]);
|
// printf("!!! PWRSR read pc=%08x lr=%08x !!!\n", getGPR(15), getGPR(14));
|
||||||
return pwrsr;
|
return pwrsr;
|
||||||
} else if (reg == INTSR) {
|
} else if (reg == INTSR) {
|
||||||
return pendingInterrupts & interruptMask;
|
return pendingInterrupts & interruptMask;
|
||||||
|
@ -85,7 +86,7 @@ uint32_t Emu::readReg32(uint32_t reg) {
|
||||||
} else if (reg == KSCAN) {
|
} else if (reg == KSCAN) {
|
||||||
return kScan;
|
return kScan;
|
||||||
} else {
|
} else {
|
||||||
// printf("RegRead32 unknown:: pc=%08x lr=%08x reg=%03x\n", cpu.gprs[ARM_PC]-4, cpu.gprs[ARM_LR], reg);
|
// printf("RegRead32 unknown:: pc=%08x lr=%08x reg=%03x\n", getGPR(15)-4, getGPR(14), reg);
|
||||||
return 0xFFFFFFFF;
|
return 0xFFFFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,13 +109,13 @@ void Emu::writeReg8(uint32_t reg, uint8_t value) {
|
||||||
uint32_t oldPorts = portValues;
|
uint32_t oldPorts = portValues;
|
||||||
portValues &= 0xFF00FFFF;
|
portValues &= 0xFF00FFFF;
|
||||||
portValues |= (uint32_t)value << 16;
|
portValues |= (uint32_t)value << 16;
|
||||||
if ((portValues & 0x10000) && !(oldPorts & 0x10000))
|
if ((portValues & 0x10000) && !(oldPorts & 0x10000))
|
||||||
etna.setPromBit0High();
|
etna.setPromBit0High();
|
||||||
else if (!(portValues & 0x10000) && (oldPorts & 0x10000))
|
else if (!(portValues & 0x10000) && (oldPorts & 0x10000))
|
||||||
etna.setPromBit0Low();
|
etna.setPromBit0Low();
|
||||||
if ((portValues & 0x20000) && !(oldPorts & 0x20000))
|
if ((portValues & 0x20000) && !(oldPorts & 0x20000))
|
||||||
etna.setPromBit1High();
|
etna.setPromBit1High();
|
||||||
diffPorts(oldPorts, portValues);
|
diffPorts(oldPorts, portValues);
|
||||||
} else if (reg == PCDR) {
|
} else if (reg == PCDR) {
|
||||||
uint32_t oldPorts = portValues;
|
uint32_t oldPorts = portValues;
|
||||||
portValues &= 0xFFFF00FF;
|
portValues &= 0xFFFF00FF;
|
||||||
|
@ -140,7 +141,7 @@ void Emu::writeReg8(uint32_t reg, uint8_t value) {
|
||||||
} else if (reg == KSCAN) {
|
} else if (reg == KSCAN) {
|
||||||
kScan = value;
|
kScan = value;
|
||||||
} else {
|
} else {
|
||||||
// printf("RegWrite8 unknown:: pc=%08x reg=%03x value=%02x\n", cpu.gprs[ARM_PC]-4, reg, value);
|
// printf("RegWrite8 unknown:: pc=%08x reg=%03x value=%02x\n", getGPR(15)-4, reg, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Emu::writeReg32(uint32_t reg, uint32_t value) {
|
void Emu::writeReg32(uint32_t reg, uint32_t value) {
|
||||||
|
@ -163,7 +164,7 @@ void Emu::writeReg32(uint32_t reg, uint32_t value) {
|
||||||
// diffInterrupts(interruptMask, interruptMask &~ value);
|
// diffInterrupts(interruptMask, interruptMask &~ value);
|
||||||
interruptMask &= ~value;
|
interruptMask &= ~value;
|
||||||
} else if (reg == HALT) {
|
} else if (reg == HALT) {
|
||||||
cpu.halted = true;
|
halted = true;
|
||||||
// BLEOI = 0x410,
|
// BLEOI = 0x410,
|
||||||
// MCEOI = 0x414,
|
// MCEOI = 0x414,
|
||||||
} else if (reg == TEOI) {
|
} else if (reg == TEOI) {
|
||||||
|
@ -184,511 +185,280 @@ void Emu::writeReg32(uint32_t reg, uint32_t value) {
|
||||||
} else if (reg == TC2EOI) {
|
} else if (reg == TC2EOI) {
|
||||||
pendingInterrupts &= ~(1 << TC2OI);
|
pendingInterrupts &= ~(1 << TC2OI);
|
||||||
} else {
|
} else {
|
||||||
// printf("RegWrite32 unknown:: pc=%08x reg=%03x value=%08x\n", cpu.gprs[ARM_PC]-4, reg, value);
|
// printf("RegWrite32 unknown:: pc=%08x reg=%03x value=%08x\n", getGPR(15)-4, reg, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Emu::isPhysAddressValid(uint32_t physAddress) const {
|
bool Emu::isPhysAddressValid(uint32_t physAddress) const {
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
uint8_t region = (physAddress >> 24) & 0xF1;
|
||||||
switch (region) {
|
switch (region) {
|
||||||
case 0: return true;
|
case 0: return true;
|
||||||
case 0x80: return (physAddress <= 0x80000FFF);
|
case 0x80: return (physAddress <= 0x80000FFF);
|
||||||
case 0xC0: return true;
|
case 0xC0: return true;
|
||||||
case 0xC1: return true;
|
case 0xC1: return true;
|
||||||
case 0xD0: return true;
|
case 0xD0: return true;
|
||||||
case 0xD1: return true;
|
case 0xD1: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Emu::readPhys8(uint32_t physAddress) {
|
|
||||||
uint32_t result = 0xFF;
|
MaybeU32 Emu::readPhysical(uint32_t physAddr, ValueSize valueSize) {
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
uint8_t region = (physAddr >> 24) & 0xF1;
|
||||||
if (region == 0)
|
if (valueSize == V8) {
|
||||||
result = ROM[physAddress & 0xFFFFFF];
|
if (region == 0)
|
||||||
else if (region == 0x20 && physAddress <= 0x20000FFF)
|
return ROM[physAddr & 0xFFFFFF];
|
||||||
result = etna.readReg8(physAddress & 0xFFF);
|
else if (region == 0x20 && physAddr <= 0x20000FFF)
|
||||||
else if (region == 0x80 && physAddress <= 0x80000FFF)
|
return etna.readReg8(physAddr & 0xFFF);
|
||||||
result = readReg8(physAddress & 0xFFF);
|
else if (region == 0x80 && physAddr <= 0x80000FFF)
|
||||||
else if (region == 0xC0)
|
return readReg8(physAddr & 0xFFF);
|
||||||
result = MemoryBlockC0[physAddress & MemoryBlockMask];
|
else if (region == 0xC0)
|
||||||
|
return MemoryBlockC0[physAddr & MemoryBlockMask];
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xC1)
|
else if (region == 0xC1)
|
||||||
result = MemoryBlockC1[physAddress & MemoryBlockMask];
|
return MemoryBlockC1[physAddr & MemoryBlockMask];
|
||||||
#endif
|
#endif
|
||||||
else if (region == 0xD0)
|
else if (region == 0xD0)
|
||||||
result = MemoryBlockD0[physAddress & MemoryBlockMask];
|
return MemoryBlockD0[physAddr & MemoryBlockMask];
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xD1)
|
else if (region == 0xD1)
|
||||||
result = MemoryBlockD1[physAddress & MemoryBlockMask];
|
return MemoryBlockD1[physAddr & MemoryBlockMask];
|
||||||
#endif
|
#endif
|
||||||
// else
|
} else {
|
||||||
// printf("<%08x> unmapped read8 addr p:%08x\n", cpu.gprs[ARM_PC] - 4, physAddress);
|
uint32_t result;
|
||||||
return result;
|
if (region == 0)
|
||||||
}
|
LOAD_32LE(result, physAddr & 0xFFFFFF, ROM);
|
||||||
uint32_t Emu::readPhys16(uint32_t physAddress) {
|
else if (region == 0x20 && physAddr <= 0x20000FFF)
|
||||||
uint32_t result = 0xFFFFFFFF;
|
result = etna.readReg32(physAddr & 0xFFF);
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
else if (region == 0x80 && physAddr <= 0x80000FFF)
|
||||||
if (region == 0)
|
result = readReg32(physAddr & 0xFFF);
|
||||||
LOAD_16LE(result, physAddress & 0xFFFFFF, ROM);
|
else if (region == 0xC0)
|
||||||
else if (region == 0xC0)
|
LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockC0);
|
||||||
LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockC0);
|
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xC1)
|
else if (region == 0xC1)
|
||||||
LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockC1);
|
LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockC1);
|
||||||
#endif
|
#endif
|
||||||
else if (region == 0xD0)
|
else if (region == 0xD0)
|
||||||
LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockD0);
|
LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockD0);
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xD1)
|
else if (region == 0xD1)
|
||||||
LOAD_16LE(result, physAddress & MemoryBlockMask, MemoryBlockD1);
|
LOAD_32LE(result, physAddr & MemoryBlockMask, MemoryBlockD1);
|
||||||
#endif
|
#endif
|
||||||
// else
|
else
|
||||||
// printf("<%08x> unmapped read16 addr p:%08x\n", cpu.gprs[ARM_PC] - 4, physAddress);
|
return {};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
uint32_t Emu::readPhys32(uint32_t physAddress) {
|
|
||||||
uint32_t result = 0xFFFFFFFF;
|
return {};
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
|
||||||
if (region == 0)
|
|
||||||
LOAD_32LE(result, physAddress & 0xFFFFFF, ROM);
|
|
||||||
else if (region == 0x20 && physAddress <= 0x20000FFF)
|
|
||||||
result = etna.readReg32(physAddress & 0xFFF);
|
|
||||||
else if (region == 0x80 && physAddress <= 0x80000FFF)
|
|
||||||
result = readReg32(physAddress & 0xFFF);
|
|
||||||
else if (region == 0xC0)
|
|
||||||
LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockC0);
|
|
||||||
#ifdef INCLUDE_BANK1
|
|
||||||
else if (region == 0xC1)
|
|
||||||
LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockC1);
|
|
||||||
#endif
|
|
||||||
else if (region == 0xD0)
|
|
||||||
LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockD0);
|
|
||||||
#ifdef INCLUDE_BANK1
|
|
||||||
else if (region == 0xD1)
|
|
||||||
LOAD_32LE(result, physAddress & MemoryBlockMask, MemoryBlockD1);
|
|
||||||
#endif
|
|
||||||
// else
|
|
||||||
// printf("<%08x> unmapped read32 addr p:%08x\n", cpu.gprs[ARM_PC] - 4, physAddress);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::writePhys8(uint32_t physAddress, uint8_t value) {
|
bool Emu::writePhysical(uint32_t value, uint32_t physAddr, ValueSize valueSize) {
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
uint8_t region = (physAddr >> 24) & 0xF1;
|
||||||
if (region == 0xC0)
|
if (valueSize == V8) {
|
||||||
MemoryBlockC0[physAddress & MemoryBlockMask] = (uint8_t)value;
|
if (region == 0xC0)
|
||||||
|
MemoryBlockC0[physAddr & MemoryBlockMask] = (uint8_t)value;
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xC1)
|
else if (region == 0xC1)
|
||||||
MemoryBlockC1[physAddress & MemoryBlockMask] = (uint8_t)value;
|
MemoryBlockC1[physAddr & MemoryBlockMask] = (uint8_t)value;
|
||||||
#endif
|
#endif
|
||||||
else if (region == 0xD0)
|
else if (region == 0xD0)
|
||||||
MemoryBlockD0[physAddress & MemoryBlockMask] = (uint8_t)value;
|
MemoryBlockD0[physAddr & MemoryBlockMask] = (uint8_t)value;
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xD1)
|
else if (region == 0xD1)
|
||||||
MemoryBlockD1[physAddress & MemoryBlockMask] = (uint8_t)value;
|
MemoryBlockD1[physAddr & MemoryBlockMask] = (uint8_t)value;
|
||||||
#endif
|
#endif
|
||||||
else if (region == 0x20 && physAddress <= 0x20000FFF)
|
else if (region == 0x20 && physAddr <= 0x20000FFF)
|
||||||
etna.writeReg8(physAddress & 0xFFF, value);
|
etna.writeReg8(physAddr & 0xFFF, value);
|
||||||
else if (region == 0x80 && physAddress <= 0x80000FFF)
|
else if (region == 0x80 && physAddr <= 0x80000FFF)
|
||||||
writeReg8(physAddress & 0xFFF, value);
|
writeReg8(physAddr & 0xFFF, value);
|
||||||
// else
|
else
|
||||||
// printf("<%08x> unmapped write8 addr p:%08x :: %02x\n", cpu.gprs[ARM_PC] - 4, physAddress, value);
|
return false;
|
||||||
}
|
} else {
|
||||||
void Emu::writePhys16(uint32_t physAddress, uint16_t value) {
|
uint8_t region = (physAddr >> 24) & 0xF1;
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
if (region == 0xC0)
|
||||||
if (region == 0xC0)
|
STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockC0);
|
||||||
STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockC0);
|
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xC1)
|
else if (region == 0xC1)
|
||||||
STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockC1);
|
STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockC1);
|
||||||
#endif
|
#endif
|
||||||
else if (region == 0xD0)
|
else if (region == 0xD0)
|
||||||
STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockD0);
|
STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockD0);
|
||||||
#ifdef INCLUDE_BANK1
|
#ifdef INCLUDE_BANK1
|
||||||
else if (region == 0xD1)
|
else if (region == 0xD1)
|
||||||
STORE_16LE(value, physAddress & MemoryBlockMask, MemoryBlockD1);
|
STORE_32LE(value, physAddr & MemoryBlockMask, MemoryBlockD1);
|
||||||
#endif
|
#endif
|
||||||
// else
|
else if (region == 0x20 && physAddr <= 0x20000FFF)
|
||||||
// printf("<%08x> unmapped write16 addr p:%08x :: %04x\n", cpu.gprs[ARM_PC] - 4, physAddress, value);
|
etna.writeReg32(physAddr & 0xFFF, value);
|
||||||
}
|
else if (region == 0x80 && physAddr <= 0x80000FFF)
|
||||||
void Emu::writePhys32(uint32_t physAddress, uint32_t value) {
|
writeReg32(physAddr & 0xFFF, value);
|
||||||
uint8_t region = (physAddress >> 24) & 0xF1;
|
else
|
||||||
if (region == 0xC0)
|
return false;
|
||||||
STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockC0);
|
}
|
||||||
#ifdef INCLUDE_BANK1
|
return true;
|
||||||
else if (region == 0xC1)
|
|
||||||
STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockC1);
|
|
||||||
#endif
|
|
||||||
else if (region == 0xD0)
|
|
||||||
STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockD0);
|
|
||||||
#ifdef INCLUDE_BANK1
|
|
||||||
else if (region == 0xD1)
|
|
||||||
STORE_32LE(value, physAddress & MemoryBlockMask, MemoryBlockD1);
|
|
||||||
#endif
|
|
||||||
else if (region == 0x20 && physAddress <= 0x20000FFF)
|
|
||||||
etna.writeReg32(physAddress & 0xFFF, value);
|
|
||||||
else if (region == 0x80 && physAddress <= 0x80000FFF)
|
|
||||||
writeReg32(physAddress & 0xFFF, value);
|
|
||||||
// else
|
|
||||||
// printf("<%08x> unmapped write32 addr p:%08x :: %08x\n", cpu.gprs[ARM_PC] - 4, physAddress, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Emu::virtToPhys(uint32_t virtAddress) {
|
|
||||||
if (!isMMU())
|
|
||||||
return virtAddress;
|
|
||||||
|
|
||||||
// find the TTB
|
|
||||||
uint32_t ttbEntryAddr = translationTableBase & 0xFFFFC000;
|
|
||||||
ttbEntryAddr |= ((virtAddress & 0xFFF00000) >> 18);
|
|
||||||
uint32_t ttbEntry = readPhys32(ttbEntryAddr);
|
|
||||||
|
|
||||||
if ((ttbEntry & 3) == 1) {
|
|
||||||
// Page
|
|
||||||
uint32_t pageTableAddr = ttbEntry & 0xFFFFFC00;
|
|
||||||
pageTableAddr |= ((virtAddress & 0x000FF000) >> 10);
|
|
||||||
uint32_t pageTableEntry = readPhys32(pageTableAddr);
|
|
||||||
if ((pageTableEntry & 3) == 1) {
|
|
||||||
// Large Page
|
|
||||||
uint32_t lpBaseAddr = pageTableEntry & 0xFFFF0000;
|
|
||||||
return lpBaseAddr | (virtAddress & 0x0000FFFF);
|
|
||||||
} else if ((pageTableEntry & 3) == 2) {
|
|
||||||
// Small Page
|
|
||||||
uint32_t lpBaseAddr = pageTableEntry & 0xFFFFF000;
|
|
||||||
return lpBaseAddr | (virtAddress & 0x00000FFF);
|
|
||||||
} else {
|
|
||||||
// Fault/Reserved
|
|
||||||
// TODO: should raise Abort here?
|
|
||||||
printf("!!! lv2 bad entry=%d vaddr=%08x !!!\n", pageTableEntry & 3, virtAddress);
|
|
||||||
return 0xFFFFFFFF;
|
|
||||||
}
|
|
||||||
} else if ((ttbEntry & 3) == 2) {
|
|
||||||
// Section
|
|
||||||
uint32_t sectBaseAddr = ttbEntry & 0xFFF00000;
|
|
||||||
return sectBaseAddr | (virtAddress & 0x000FFFFF);
|
|
||||||
} else {
|
|
||||||
// Fault/Reserved
|
|
||||||
// TODO: should raise Abort here?
|
|
||||||
printf("!!! lv1 bad entry=%d vaddr=%08x !!!\n", ttbEntry & 3, virtAddress);
|
|
||||||
return 0xFFFFFFFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Emu::configure() {
|
void Emu::configure() {
|
||||||
if (configured) return;
|
if (configured) return;
|
||||||
configured = true;
|
configured = true;
|
||||||
|
|
||||||
uart1.cpu = &cpu;
|
uart1.cpu = this;
|
||||||
uart2.cpu = &cpu;
|
uart2.cpu = this;
|
||||||
memset(&tc1, 0, sizeof(tc1));
|
memset(&tc1, 0, sizeof(tc1));
|
||||||
memset(&tc2, 0, sizeof(tc1));
|
memset(&tc2, 0, sizeof(tc1));
|
||||||
cpu.owner = this;
|
|
||||||
|
|
||||||
nextTickAt = TICK_INTERVAL;
|
nextTickAt = TICK_INTERVAL;
|
||||||
rtc = getRTC();
|
rtc = getRTC();
|
||||||
|
|
||||||
configureMemoryBindings();
|
reset();
|
||||||
configureCpuHandlers();
|
|
||||||
|
|
||||||
ARMReset(&cpu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Emu::configureMemoryBindings() {
|
|
||||||
cpu.memory.load8 = [](struct ARMCore *cpu, uint32_t address, int *) {
|
|
||||||
return ((Emu *)cpu->owner)->readVirt8(address);
|
|
||||||
};
|
|
||||||
cpu.memory.load16 = [](struct ARMCore *cpu, uint32_t address, int *) {
|
|
||||||
return ((Emu *)cpu->owner)->readVirt16(address);
|
|
||||||
};
|
|
||||||
cpu.memory.load32 = [](struct ARMCore *cpu, uint32_t address, int *) {
|
|
||||||
return ((Emu *)cpu->owner)->readVirt32(address);
|
|
||||||
};
|
|
||||||
cpu.memory.loadMultiple = [](struct ARMCore *cpu, uint32_t address, int mask, enum LSMDirection direction, int *cycleCounter) {
|
|
||||||
uint32_t value;
|
|
||||||
int i, offset = 4, popcount = 0;
|
|
||||||
|
|
||||||
if (direction & LSM_D) {
|
|
||||||
offset = -4;
|
|
||||||
popcount = popcount32(mask);
|
|
||||||
address -= (popcount << 2) - 4;
|
|
||||||
}
|
|
||||||
if (direction & LSM_B)
|
|
||||||
address += offset;
|
|
||||||
|
|
||||||
if (!mask) {
|
|
||||||
value = cpu->memory.load32(cpu, address, cycleCounter);
|
|
||||||
cpu->gprs[ARM_PC] = value;
|
|
||||||
address += 64;
|
|
||||||
}
|
|
||||||
for (i = 0; i < 16; i++) {
|
|
||||||
if (mask & (1 << i)) {
|
|
||||||
value = cpu->memory.load32(cpu, address, cycleCounter);
|
|
||||||
cpu->gprs[i] = value;
|
|
||||||
address += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (direction & LSM_B)
|
|
||||||
address -= offset;
|
|
||||||
if (direction & LSM_D)
|
|
||||||
address -= (popcount << 2) + 4;
|
|
||||||
|
|
||||||
return address;
|
|
||||||
};
|
|
||||||
|
|
||||||
cpu.memory.store8 = [](struct ARMCore *cpu, uint32_t address, int8_t value, int *) {
|
|
||||||
((Emu *)cpu->owner)->writeVirt8(address, value);
|
|
||||||
};
|
|
||||||
cpu.memory.store16 = [](struct ARMCore *cpu, uint32_t address, int16_t value, int *) {
|
|
||||||
((Emu *)cpu->owner)->writeVirt16(address, value);
|
|
||||||
};
|
|
||||||
cpu.memory.store32 = [](struct ARMCore *cpu, uint32_t address, int32_t value, int *) {
|
|
||||||
((Emu *)cpu->owner)->writeVirt32(address, value);
|
|
||||||
};
|
|
||||||
cpu.memory.storeMultiple = [](struct ARMCore *cpu, uint32_t address, int mask, enum LSMDirection direction, int *cycleCounter) {
|
|
||||||
uint32_t value;
|
|
||||||
int i, offset = 4, popcount = 0;
|
|
||||||
|
|
||||||
if (direction & LSM_D) {
|
|
||||||
offset = -4;
|
|
||||||
popcount = popcount32(mask);
|
|
||||||
address -= (popcount << 2) - 4;
|
|
||||||
}
|
|
||||||
if (direction & LSM_B)
|
|
||||||
address += offset;
|
|
||||||
|
|
||||||
if (!mask) {
|
|
||||||
value = cpu->gprs[ARM_PC] + 4;
|
|
||||||
cpu->memory.store32(cpu, address, value, cycleCounter);
|
|
||||||
address += 64;
|
|
||||||
}
|
|
||||||
for (i = 0; i < 16; i++) {
|
|
||||||
if (mask & (1 << i)) {
|
|
||||||
value = cpu->gprs[i];
|
|
||||||
if (i == ARM_PC) value += 4;
|
|
||||||
cpu->memory.store32(cpu, address, value, cycleCounter);
|
|
||||||
address += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (direction & LSM_B)
|
|
||||||
address -= offset;
|
|
||||||
if (direction & LSM_D)
|
|
||||||
address -= (popcount << 2) + 4;
|
|
||||||
|
|
||||||
return address;
|
|
||||||
};
|
|
||||||
|
|
||||||
cpu.memory.activeSeqCycles32 = 0;
|
|
||||||
cpu.memory.activeNonseqCycles32 = 0;
|
|
||||||
cpu.memory.stall = [](struct ARMCore *cpu, int32_t wait) {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Emu::configureCpuHandlers() {
|
|
||||||
cpu.irqh.reset = [](struct ARMCore *cpu) {
|
|
||||||
printf("reset...\n");
|
|
||||||
};
|
|
||||||
cpu.irqh.processEvents = [](struct ARMCore *cpu) {
|
|
||||||
// printf("processEvents...\n");
|
|
||||||
};
|
|
||||||
cpu.irqh.swi32 = [](struct ARMCore *cpu, int immediate) {
|
|
||||||
ARMRaiseSWI(cpu);
|
|
||||||
};
|
|
||||||
cpu.irqh.hitIllegal = [](struct ARMCore *cpu, uint32_t opcode) {
|
|
||||||
printf("hitIllegal... %08x\n", opcode);
|
|
||||||
};
|
|
||||||
cpu.irqh.bkpt32 = [](struct ARMCore *cpu, int immediate) {
|
|
||||||
printf("bkpt32... %08x\n", immediate);
|
|
||||||
};
|
|
||||||
cpu.irqh.readCPSR = [](struct ARMCore *cpu) {
|
|
||||||
// printf("readCPSR...\n");
|
|
||||||
// printf("at %08x our priv became %s\n", cpu->gprs[ARM_PC]-4, privname(cpu));
|
|
||||||
};
|
|
||||||
cpu.irqh.hitStub = [](struct ARMCore *cpu, uint32_t opcode) {
|
|
||||||
Emu *emu = (Emu *)cpu->owner;
|
|
||||||
if ((opcode & 0x0F100F10) == 0x0E100F10) {
|
|
||||||
// coprocessor read
|
|
||||||
int cpReg = (opcode & 0x000F0000) >> 16;
|
|
||||||
int armReg = (opcode & 0x0000F000) >> 12;
|
|
||||||
if (cpReg == 0)
|
|
||||||
cpu->gprs[armReg] = 0x41807100; //5mx device id
|
|
||||||
} else if ((opcode & 0x0F100F10) == 0x0E000F10) {
|
|
||||||
// coprocessor write
|
|
||||||
int cpReg = (opcode & 0x000F0000) >> 16;
|
|
||||||
int armReg = (opcode & 0x0000F000) >> 12;
|
|
||||||
if (cpReg == 1) {
|
|
||||||
emu->controlReg = cpu->gprs[armReg];
|
|
||||||
printf("mmu is now %s\n", emu->isMMU() ? "on" : "off");
|
|
||||||
} else if (cpReg == 2) {
|
|
||||||
emu->translationTableBase = cpu->gprs[armReg];
|
|
||||||
} else if (cpReg == 3) {
|
|
||||||
emu->domainAccessControl = cpu->gprs[armReg];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("hitStub... %08x\n", opcode);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::loadROM(const char *path) {
|
void Emu::loadROM(const char *path) {
|
||||||
FILE *f = fopen(path, "rb");
|
FILE *f = fopen(path, "rb");
|
||||||
fread(ROM, 1, sizeof(ROM), f);
|
fread(ROM, 1, sizeof(ROM), f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::executeUntil(int64_t cycles) {
|
void Emu::executeUntil(int64_t cycles) {
|
||||||
if (!configured)
|
if (!configured)
|
||||||
configure();
|
configure();
|
||||||
|
|
||||||
while (!asleep && cpu.cycles < cycles) {
|
while (!asleep && passedCycles < cycles) {
|
||||||
if (cpu.cycles >= nextTickAt) {
|
if (passedCycles >= nextTickAt) {
|
||||||
// increment RTCDIV
|
// increment RTCDIV
|
||||||
if ((pwrsr & 0x3F) == 0x3F) {
|
if ((pwrsr & 0x3F) == 0x3F) {
|
||||||
rtc++;
|
rtc++;
|
||||||
pwrsr &= ~0x3F;
|
pwrsr &= ~0x3F;
|
||||||
} else {
|
} else {
|
||||||
pwrsr++;
|
pwrsr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTickAt += TICK_INTERVAL;
|
nextTickAt += TICK_INTERVAL;
|
||||||
pendingInterrupts |= (1<<TINT);
|
pendingInterrupts |= (1<<TINT);
|
||||||
}
|
}
|
||||||
if (tc1.tick(cpu.cycles))
|
if (tc1.tick(passedCycles))
|
||||||
pendingInterrupts |= (1<<TC1OI);
|
pendingInterrupts |= (1<<TC1OI);
|
||||||
if (tc2.tick(cpu.cycles))
|
if (tc2.tick(passedCycles))
|
||||||
pendingInterrupts |= (1<<TC2OI);
|
pendingInterrupts |= (1<<TC2OI);
|
||||||
|
|
||||||
if ((pendingInterrupts & interruptMask & FIQ_INTERRUPTS) != 0)
|
if ((pendingInterrupts & interruptMask & FIQ_INTERRUPTS) != 0)
|
||||||
ARMRaiseFIQ(&cpu);
|
requestFIQ();
|
||||||
if ((pendingInterrupts & interruptMask & IRQ_INTERRUPTS) != 0)
|
if ((pendingInterrupts & interruptMask & IRQ_INTERRUPTS) != 0)
|
||||||
ARMRaiseIRQ(&cpu);
|
requestIRQ();
|
||||||
|
|
||||||
// if (cpu.cycles >= 30000000) {
|
// what's running?
|
||||||
// static bool lcdtest = false;
|
if (halted) {
|
||||||
// if (!lcdtest) {
|
// keep the clock moving
|
||||||
// printf("lcdtest\n");
|
passedCycles++;
|
||||||
// pendingInterrupts |= (1<<LCDINT);
|
} else {
|
||||||
// lcdtest = true;
|
passedCycles += tick();
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// what's running?
|
uint32_t new_pc = getGPR(15) - 0xC;
|
||||||
if (cpu.halted) {
|
if (_breakpoints.find(new_pc) != _breakpoints.end())
|
||||||
// keep the clock moving
|
return;
|
||||||
cpu.cycles++;
|
}
|
||||||
} else {
|
}
|
||||||
uint32_t pc = cpu.gprs[ARM_PC] - 4;
|
|
||||||
uint32_t phys_pc = virtToPhys(pc);
|
|
||||||
debugPC(phys_pc);
|
|
||||||
ARMRun(&cpu);
|
|
||||||
|
|
||||||
uint32_t new_pc = cpu.gprs[ARM_PC] - 4;
|
|
||||||
if (_breakpoints.find(new_pc) != _breakpoints.end())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::dumpRAM(const char *path) {
|
void Emu::dumpRAM(const char *path) {
|
||||||
FILE *f = fopen(path, "wb");
|
FILE *f = fopen(path, "wb");
|
||||||
fwrite(MemoryBlockC0, 1, sizeof(MemoryBlockC0), f);
|
fwrite(MemoryBlockC0, 1, sizeof(MemoryBlockC0), f);
|
||||||
fwrite(MemoryBlockC1, 1, sizeof(MemoryBlockC1), f);
|
fwrite(MemoryBlockC1, 1, sizeof(MemoryBlockC1), f);
|
||||||
fwrite(MemoryBlockD0, 1, sizeof(MemoryBlockD0), f);
|
fwrite(MemoryBlockD0, 1, sizeof(MemoryBlockD0), f);
|
||||||
fwrite(MemoryBlockD1, 1, sizeof(MemoryBlockD1), f);
|
fwrite(MemoryBlockD1, 1, sizeof(MemoryBlockD1), f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Emu::printRegs() {
|
void Emu::printRegs() {
|
||||||
printf("R00:%08x R01:%08x R02:%08x R03:%08x\n", cpu.gprs[0], cpu.gprs[1], cpu.gprs[2], cpu.gprs[3]);
|
printf("R00:%08x R01:%08x R02:%08x R03:%08x\n", getGPR(0), getGPR(1), getGPR(2), getGPR(3));
|
||||||
printf("R04:%08x R05:%08x R06:%08x R07:%08x\n", cpu.gprs[4], cpu.gprs[5], cpu.gprs[6], cpu.gprs[7]);
|
printf("R04:%08x R05:%08x R06:%08x R07:%08x\n", getGPR(4), getGPR(5), getGPR(6), getGPR(7));
|
||||||
printf("R08:%08x R09:%08x R10:%08x R11:%08x\n", cpu.gprs[8], cpu.gprs[9], cpu.gprs[10], cpu.gprs[11]);
|
printf("R08:%08x R09:%08x R10:%08x R11:%08x\n", getGPR(8), getGPR(9), getGPR(10), getGPR(11));
|
||||||
printf("R12:%08x R13:%08x R14:%08x R15:%08x\n", cpu.gprs[12], cpu.gprs[13], cpu.gprs[14], cpu.gprs[15]);
|
printf("R12:%08x R13:%08x R14:%08x R15:%08x\n", getGPR(12), getGPR(13), getGPR(14), getGPR(15));
|
||||||
printf("cpsr=%08x spsr=%08x\n", cpu.cpsr.packed, cpu.spsr.packed);
|
// printf("cpsr=%08x spsr=%08x\n", cpu.cpsr.packed, cpu.spsr.packed);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *Emu::identifyObjectCon(uint32_t ptr) {
|
const char *Emu::identifyObjectCon(uint32_t ptr) {
|
||||||
if (ptr == readVirt32(0x80000980)) return "process";
|
if (ptr == readVirtualDebug(0x80000980, V32).value()) return "process";
|
||||||
if (ptr == readVirt32(0x80000984)) return "thread";
|
if (ptr == readVirtualDebug(0x80000984, V32).value()) return "thread";
|
||||||
if (ptr == readVirt32(0x80000988)) return "chunk";
|
if (ptr == readVirtualDebug(0x80000988, V32).value()) return "chunk";
|
||||||
// if (ptr == readVirt32(0x8000098C)) return "semaphore";
|
// if (ptr == readVirtualDebug(0x8000098C, V32).value()) return "semaphore";
|
||||||
// if (ptr == readVirt32(0x80000990)) return "mutex";
|
// if (ptr == readVirtualDebug(0x80000990, V32).value()) return "mutex";
|
||||||
if (ptr == readVirt32(0x80000994)) return "logicaldevice";
|
if (ptr == readVirtualDebug(0x80000994, V32).value()) return "logicaldevice";
|
||||||
if (ptr == readVirt32(0x80000998)) return "physicaldevice";
|
if (ptr == readVirtualDebug(0x80000998, V32).value()) return "physicaldevice";
|
||||||
if (ptr == readVirt32(0x8000099C)) return "channel";
|
if (ptr == readVirtualDebug(0x8000099C, V32).value()) return "channel";
|
||||||
if (ptr == readVirt32(0x800009A0)) return "server";
|
if (ptr == readVirtualDebug(0x800009A0, V32).value()) return "server";
|
||||||
// if (ptr == readVirt32(0x800009A4)) return "unk9A4"; // name always null
|
// if (ptr == readVirtualDebug(0x800009A4, V32).value()) return "unk9A4"; // name always null
|
||||||
if (ptr == readVirt32(0x800009AC)) return "library";
|
if (ptr == readVirtualDebug(0x800009AC, V32).value()) return "library";
|
||||||
// if (ptr == readVirt32(0x800009B0)) return "unk9B0"; // name always null
|
// if (ptr == readVirtualDebug(0x800009B0, V32).value()) return "unk9B0"; // name always null
|
||||||
// if (ptr == readVirt32(0x800009B4)) return "unk9B4"; // name always null
|
// if (ptr == readVirtualDebug(0x800009B4, V32).value()) return "unk9B4"; // name always null
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::fetchStr(uint32_t str, char *buf) {
|
void Emu::fetchStr(uint32_t str, char *buf) {
|
||||||
if (str == 0) {
|
if (str == 0) {
|
||||||
strcpy(buf, "<NULL>");
|
strcpy(buf, "<NULL>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int size = readVirt32(str);
|
int size = readVirtualDebug(str, V32).value();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
buf[i] = readVirt8(str + 4 + i);
|
buf[i] = readVirtualDebug(str + 4 + i, V8).value();
|
||||||
}
|
}
|
||||||
buf[size] = 0;
|
buf[size] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::fetchName(uint32_t obj, char *buf) {
|
void Emu::fetchName(uint32_t obj, char *buf) {
|
||||||
fetchStr(readVirt32(obj + 0x10), buf);
|
fetchStr(readVirtualDebug(obj + 0x10, V32).value(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::fetchProcessFilename(uint32_t obj, char *buf) {
|
void Emu::fetchProcessFilename(uint32_t obj, char *buf) {
|
||||||
fetchStr(readVirt32(obj + 0x3C), buf);
|
fetchStr(readVirtualDebug(obj + 0x3C, V32).value(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emu::debugPC(uint32_t pc) {
|
void Emu::debugPC(uint32_t pc) {
|
||||||
char objName[1000];
|
char objName[1000];
|
||||||
if (pc == 0x2CBC4) {
|
if (pc == 0x2CBC4) {
|
||||||
// CObjectCon::AddL()
|
// CObjectCon::AddL()
|
||||||
uint32_t container = cpu.gprs[0];
|
uint32_t container = getGPR(0);
|
||||||
uint32_t obj = cpu.gprs[1];
|
uint32_t obj = getGPR(1);
|
||||||
const char *wut = identifyObjectCon(container);
|
const char *wut = identifyObjectCon(container);
|
||||||
if (wut) {
|
if (wut) {
|
||||||
fetchName(obj, objName);
|
fetchName(obj, objName);
|
||||||
printf("OBJS: added %s at %08x <%s>", wut, obj, objName);
|
printf("OBJS: added %s at %08x <%s>", wut, obj, objName);
|
||||||
if (strcmp(wut, "process") == 0) {
|
if (strcmp(wut, "process") == 0) {
|
||||||
fetchProcessFilename(obj, objName);
|
fetchProcessFilename(obj, objName);
|
||||||
printf(" <%s>", objName);
|
printf(" <%s>", objName);
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const uint8_t *Emu::getLCDBuffer() const {
|
const uint8_t *Emu::getLCDBuffer() const {
|
||||||
if ((lcdAddress >> 24) == 0xC0)
|
if ((lcdAddress >> 24) == 0xC0)
|
||||||
return &MemoryBlockC0[lcdAddress & MemoryBlockMask];
|
return &MemoryBlockC0[lcdAddress & MemoryBlockMask];
|
||||||
else
|
else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t Emu::readKeyboard() {
|
uint8_t Emu::readKeyboard() {
|
||||||
uint8_t val = 0;
|
uint8_t val = 0;
|
||||||
if (kScan & 8) {
|
if (kScan & 8) {
|
||||||
// Select one keyboard
|
// Select one keyboard
|
||||||
int whichColumn = kScan & 7;
|
int whichColumn = kScan & 7;
|
||||||
for (int i = 0; i < 7; i++)
|
for (int i = 0; i < 7; i++)
|
||||||
if (keyboardKeys[whichColumn * 7 + i])
|
if (keyboardKeys[whichColumn * 7 + i])
|
||||||
val |= (1 << i);
|
val |= (1 << i);
|
||||||
} else if (kScan == 0) {
|
} else if (kScan == 0) {
|
||||||
// Report all columns combined
|
// Report all columns combined
|
||||||
// EPOC's keyboard driver relies on this...
|
// EPOC's keyboard driver relies on this...
|
||||||
for (int i = 0; i < 8*7; i++)
|
for (int i = 0; i < 8*7; i++)
|
||||||
if (keyboardKeys[i])
|
if (keyboardKeys[i])
|
||||||
val |= (1 << (i % 7));
|
val |= (1 << (i % 7));
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "arm.h"
|
#include "arm710a.h"
|
||||||
#include "wind_hw.h"
|
#include "wind_hw.h"
|
||||||
#include "etna.h"
|
#include "etna.h"
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
class Emu {
|
class Emu : public ARM710a {
|
||||||
public:
|
public:
|
||||||
uint8_t ROM[0x1000000];
|
uint8_t ROM[0x1000000];
|
||||||
uint8_t MemoryBlockC0[0x800000];
|
uint8_t MemoryBlockC0[0x800000];
|
||||||
|
@ -14,9 +14,6 @@ public:
|
||||||
enum { MemoryBlockMask = 0x7FFFFF };
|
enum { MemoryBlockMask = 0x7FFFFF };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t controlReg;
|
|
||||||
uint32_t translationTableBase;
|
|
||||||
uint32_t domainAccessControl;
|
|
||||||
uint16_t pendingInterrupts = 0;
|
uint16_t pendingInterrupts = 0;
|
||||||
uint16_t interruptMask = 0;
|
uint16_t interruptMask = 0;
|
||||||
uint32_t portValues = 0;
|
uint32_t portValues = 0;
|
||||||
|
@ -27,19 +24,14 @@ private:
|
||||||
uint32_t kScan = 0;
|
uint32_t kScan = 0;
|
||||||
uint32_t rtc = 0;
|
uint32_t rtc = 0;
|
||||||
|
|
||||||
|
int64_t passedCycles = 0;
|
||||||
int64_t nextTickAt = 0;
|
int64_t nextTickAt = 0;
|
||||||
Timer tc1, tc2;
|
Timer tc1, tc2;
|
||||||
UART uart1, uart2;
|
UART uart1, uart2;
|
||||||
Etna etna;
|
Etna etna;
|
||||||
bool asleep = false;
|
bool halted = false, asleep = false;
|
||||||
|
|
||||||
std::unordered_set<uint32_t> _breakpoints;
|
std::unordered_set<uint32_t> _breakpoints;
|
||||||
|
|
||||||
struct ARMCore cpu;
|
|
||||||
|
|
||||||
inline bool isMMU() {
|
|
||||||
return (controlReg & 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getRTC();
|
uint32_t getRTC();
|
||||||
|
|
||||||
|
@ -49,31 +41,15 @@ private:
|
||||||
void writeReg32(uint32_t reg, uint32_t value);
|
void writeReg32(uint32_t reg, uint32_t value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool isPhysAddressValid(uint32_t physAddress) const;
|
bool isPhysAddressValid(uint32_t addr) const;
|
||||||
|
MaybeU32 readPhysical(uint32_t physAddr, ValueSize valueSize) override;
|
||||||
uint32_t readPhys8(uint32_t physAddress);
|
bool writePhysical(uint32_t value, uint32_t physAddr, ValueSize valueSize) override;
|
||||||
uint32_t readPhys16(uint32_t physAddress);
|
|
||||||
uint32_t readPhys32(uint32_t physAddress);
|
|
||||||
void writePhys8(uint32_t physAddress, uint8_t value);
|
|
||||||
void writePhys16(uint32_t physAddress, uint16_t value);
|
|
||||||
void writePhys32(uint32_t physAddress, uint32_t value);
|
|
||||||
|
|
||||||
uint32_t virtToPhys(uint32_t virtAddress);
|
|
||||||
|
|
||||||
uint32_t readVirt8(uint32_t virtAddress) { return readPhys8(virtToPhys(virtAddress)); }
|
|
||||||
uint32_t readVirt16(uint32_t virtAddress) { return readPhys16(virtToPhys(virtAddress)); }
|
|
||||||
uint32_t readVirt32(uint32_t virtAddress) { return readPhys32(virtToPhys(virtAddress)); }
|
|
||||||
void writeVirt8(uint32_t virtAddress, uint8_t value) { writePhys8(virtToPhys(virtAddress), value); }
|
|
||||||
void writeVirt16(uint32_t virtAddress, uint16_t value) { writePhys16(virtToPhys(virtAddress), value); }
|
|
||||||
void writeVirt32(uint32_t virtAddress, uint32_t value) { writePhys32(virtToPhys(virtAddress), value); }
|
|
||||||
|
|
||||||
const uint8_t *getLCDBuffer() const;
|
const uint8_t *getLCDBuffer() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool configured = false;
|
bool configured = false;
|
||||||
void configure();
|
void configure();
|
||||||
void configureMemoryBindings();
|
|
||||||
void configureCpuHandlers();
|
|
||||||
|
|
||||||
void printRegs();
|
void printRegs();
|
||||||
const char *identifyObjectCon(uint32_t ptr);
|
const char *identifyObjectCon(uint32_t ptr);
|
||||||
|
@ -90,8 +66,7 @@ public:
|
||||||
Emu();
|
Emu();
|
||||||
void loadROM(const char *path);
|
void loadROM(const char *path);
|
||||||
void dumpRAM(const char *path);
|
void dumpRAM(const char *path);
|
||||||
void executeUntil(int64_t cycles);
|
void executeUntil(int64_t cycles);
|
||||||
int64_t currentCycles() const { return cpu.cycles; }
|
std::unordered_set<uint32_t> &breakpoints() { return _breakpoints; }
|
||||||
uint32_t getGPR(int index) const { return cpu.gprs[index]; }
|
uint64_t currentCycles() const { return passedCycles; }
|
||||||
std::unordered_set<uint32_t> &breakpoints() { return _breakpoints; }
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "wind.h"
|
#include "wind.h"
|
||||||
#include "arm.h"
|
#include "arm710a.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
struct Timer {
|
struct Timer {
|
||||||
struct ARMCore *cpu;
|
ARM710a *cpu;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
TICK_INTERVAL_SLOW = CLOCK_SPEED / 2000,
|
TICK_INTERVAL_SLOW = CLOCK_SPEED / 2000,
|
||||||
|
@ -50,7 +50,7 @@ struct Timer {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UART {
|
struct UART {
|
||||||
struct ARMCore *cpu;
|
ARM710a *cpu;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
IntRx = 1,
|
IntRx = 1,
|
||||||
|
@ -105,7 +105,7 @@ struct UART {
|
||||||
// UART0INTM?
|
// UART0INTM?
|
||||||
// UART0INTR?
|
// UART0INTR?
|
||||||
} else {
|
} else {
|
||||||
printf("unhandled 8bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]);
|
printf("unhandled 8bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->getGPR(15), cpu->getGPR(14));
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ struct UART {
|
||||||
// we pretend we are never busy, never have full fifo
|
// we pretend we are never busy, never have full fifo
|
||||||
return FlagReceiveFifoEmpty;
|
return FlagReceiveFifoEmpty;
|
||||||
} else {
|
} else {
|
||||||
printf("unhandled 32bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]);
|
printf("unhandled 32bit uart read %x at pc=%08x lr=%08x\n", reg, cpu->getGPR(15), cpu->getGPR(14));
|
||||||
return 0xFFFFFFFF;
|
return 0xFFFFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ struct UART {
|
||||||
printf("uart interruptmask updated: %d\n", value);
|
printf("uart interruptmask updated: %d\n", value);
|
||||||
// UART0INTR?
|
// UART0INTR?
|
||||||
} else {
|
} else {
|
||||||
printf("unhandled 8bit uart write %x value %02x at pc=%08x lr=%08x\n", reg, value, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]);
|
printf("unhandled 8bit uart write %x value %02x at pc=%08x lr=%08x\n", reg, value, cpu->getGPR(15), cpu->getGPR(14));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void writeReg32(uint32_t reg, uint32_t value) {
|
void writeReg32(uint32_t reg, uint32_t value) {
|
||||||
|
@ -151,7 +151,7 @@ struct UART {
|
||||||
printf("uart interrupts %x -> %x\n", interrupts, value);
|
printf("uart interrupts %x -> %x\n", interrupts, value);
|
||||||
interrupts = value;
|
interrupts = value;
|
||||||
} else {
|
} else {
|
||||||
printf("unhandled 32bit uart write %x value %08x at pc=%08x lr=%08x\n", reg, value, cpu->gprs[ARM_PC], cpu->gprs[ARM_LR]);
|
printf("unhandled 32bit uart write %x value %08x at pc=%08x lr=%08x\n", reg, value, cpu->getGPR(15), cpu->getGPR(14));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,8 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
CONFIG += c++11
|
CONFIG += c++17
|
||||||
|
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
|
|
|
@ -12,13 +12,13 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
emu = new Emu;
|
emu = new Emu;
|
||||||
emu->loadROM("/Users/ash/src/psion/Sys$rom.bin");
|
emu->loadROM("/Users/ash/src/psion/Sys$rom.bin");
|
||||||
|
|
||||||
timer = new QTimer(this);
|
timer = new QTimer(this);
|
||||||
timer->setInterval(1000/64);
|
timer->setInterval(1000/64);
|
||||||
connect(timer, SIGNAL(timeout()), SLOT(execTimer()));
|
connect(timer, SIGNAL(timeout()), SLOT(execTimer()));
|
||||||
|
|
||||||
updateScreen();
|
updateScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
@ -30,10 +30,10 @@ void MainWindow::updateScreen()
|
||||||
{
|
{
|
||||||
ui->cycleCounter->setText(QString("Cycles: %1").arg(emu->currentCycles()));
|
ui->cycleCounter->setText(QString("Cycles: %1").arg(emu->currentCycles()));
|
||||||
|
|
||||||
updateMemory();
|
updateMemory();
|
||||||
|
|
||||||
ui->regsLabel->setText(
|
ui->regsLabel->setText(
|
||||||
QString("R0: %1 / R1: %2 / R2: %3 / R3: %4 / R4: %5 / R5: %6 / R6: %7 / R7: %8\nR8: %9 / R9: %10 / R10:%11 / R11:%12 / R12:%13 / SP: %14 / LR: %15 / PC: %16")
|
QString("R0: %1 / R1: %2 / R2: %3 / R3: %4 / R4: %5 / R5: %6 / R6: %7 / R7: %8\nR8: %9 / R9: %10 / R10:%11 / R11:%12 / R12:%13 / SP: %14 / LR: %15 / PC: %16")
|
||||||
.arg(emu->getGPR(0), 8, 16)
|
.arg(emu->getGPR(0), 8, 16)
|
||||||
.arg(emu->getGPR(1), 8, 16)
|
.arg(emu->getGPR(1), 8, 16)
|
||||||
.arg(emu->getGPR(2), 8, 16)
|
.arg(emu->getGPR(2), 8, 16)
|
||||||
|
@ -56,24 +56,27 @@ void MainWindow::updateScreen()
|
||||||
const int context = 8 * 4;
|
const int context = 8 * 4;
|
||||||
uint32_t pc = emu->getGPR(15) - 4;
|
uint32_t pc = emu->getGPR(15) - 4;
|
||||||
uint32_t minCode = pc - context;
|
uint32_t minCode = pc - context;
|
||||||
if (minCode >= (UINT32_MAX - context))
|
if (minCode >= (UINT32_MAX - context))
|
||||||
minCode = 0;
|
minCode = 0;
|
||||||
uint32_t maxCode = pc + context;
|
uint32_t maxCode = pc + context;
|
||||||
if (maxCode < context)
|
if (maxCode < context)
|
||||||
maxCode = UINT32_MAX;
|
maxCode = UINT32_MAX;
|
||||||
|
|
||||||
QStringList codeLines;
|
QStringList codeLines;
|
||||||
for (uint32_t addr = minCode; addr >= minCode && addr <= maxCode; addr += 4) {
|
for (uint32_t addr = minCode; addr >= minCode && addr <= maxCode; addr += 4) {
|
||||||
const char *prefix = (addr == pc) ? "==>" : " ";
|
const char *prefix = (addr == pc) ? "==>" : " ";
|
||||||
struct ARMInstructionInfo info;
|
struct ARMInstructionInfo info;
|
||||||
char buffer[512];
|
char buffer[512];
|
||||||
|
|
||||||
uint32_t opcode = emu->readVirt32(addr);
|
auto result = emu->readVirtual(addr, ARM710a::V32);
|
||||||
ARMDecodeARM(opcode, &info);
|
if (result.first.has_value()) {
|
||||||
ARMDisassemble(&info, addr, buffer, sizeof(buffer));
|
uint32_t opcode = result.first.value();
|
||||||
codeLines.append(QString("%1 %2 | %3 | %4").arg(prefix).arg(addr, 8, 16).arg(opcode, 8, 16).arg(buffer));
|
ARMDecodeARM(opcode, &info);
|
||||||
|
ARMDisassemble(&info, addr, buffer, sizeof(buffer));
|
||||||
|
codeLines.append(QString("%1 %2 | %3 | %4").arg(prefix).arg(addr, 8, 16).arg(opcode, 8, 16).arg(buffer));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ui->codeLabel->setText(codeLines.join('\n'));
|
ui->codeLabel->setText(codeLines.join('\n'));
|
||||||
|
|
||||||
// now, the actual screen
|
// now, the actual screen
|
||||||
const uint8_t *lcdBuf = emu->getLCDBuffer();
|
const uint8_t *lcdBuf = emu->getLCDBuffer();
|
||||||
|
@ -95,12 +98,12 @@ void MainWindow::updateScreen()
|
||||||
for (int x = 0; x < img.width(); x++) {
|
for (int x = 0; x < img.width(); x++) {
|
||||||
uint8_t byte = lcdBuf[lineOffs + (x / ppb)];
|
uint8_t byte = lcdBuf[lineOffs + (x / ppb)];
|
||||||
int shift = (x & (ppb - 1)) * bpp;
|
int shift = (x & (ppb - 1)) * bpp;
|
||||||
int mask = (1 << bpp) - 1;
|
int mask = (1 << bpp) - 1;
|
||||||
int palIdx = (byte >> shift) & mask;
|
int palIdx = (byte >> shift) & mask;
|
||||||
int palValue = palette[palIdx];
|
int palValue = palette[palIdx];
|
||||||
|
|
||||||
palValue |= (palValue << 4);
|
palValue |= (palValue << 4);
|
||||||
scanline[x] = palValue ^ 0xFF;
|
scanline[x] = palValue ^ 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +201,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
|
||||||
void MainWindow::keyReleaseEvent(QKeyEvent *event)
|
void MainWindow::keyReleaseEvent(QKeyEvent *event)
|
||||||
{
|
{
|
||||||
int k = resolveKey(event->key());
|
int k = resolveKey(event->key());
|
||||||
if (k >= 0)
|
if (k >= 0)
|
||||||
emu->keyboardKeys[k] = false;
|
emu->keyboardKeys[k] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,71 +246,72 @@ void MainWindow::execTimer()
|
||||||
|
|
||||||
void MainWindow::on_addBreakButton_clicked()
|
void MainWindow::on_addBreakButton_clicked()
|
||||||
{
|
{
|
||||||
uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16);
|
uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16);
|
||||||
emu->breakpoints().insert(addr);
|
emu->breakpoints().insert(addr);
|
||||||
updateBreakpointsList();
|
updateBreakpointsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_removeBreakButton_clicked()
|
void MainWindow::on_removeBreakButton_clicked()
|
||||||
{
|
{
|
||||||
uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16);
|
uint32_t addr = ui->breakpointAddress->text().toUInt(nullptr, 16);
|
||||||
emu->breakpoints().erase(addr);
|
emu->breakpoints().erase(addr);
|
||||||
updateBreakpointsList();
|
updateBreakpointsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateBreakpointsList()
|
void MainWindow::updateBreakpointsList()
|
||||||
{
|
{
|
||||||
ui->breakpointsList->clear();
|
ui->breakpointsList->clear();
|
||||||
for (uint32_t addr : emu->breakpoints()) {
|
for (uint32_t addr : emu->breakpoints()) {
|
||||||
ui->breakpointsList->addItem(QString::number(addr, 16));
|
ui->breakpointsList->addItem(QString::number(addr, 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_memoryViewAddress_textEdited(const QString &)
|
void MainWindow::on_memoryViewAddress_textEdited(const QString &)
|
||||||
{
|
{
|
||||||
updateMemory();
|
updateMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateMemory()
|
void MainWindow::updateMemory()
|
||||||
{
|
{
|
||||||
uint32_t virtBase = ui->memoryViewAddress->text().toUInt(nullptr, 16) & ~0xFF;
|
uint32_t virtBase = ui->memoryViewAddress->text().toUInt(nullptr, 16) & ~0xFF;
|
||||||
uint32_t physBase = emu->virtToPhys(virtBase);
|
auto physBaseOpt = emu->virtToPhys(virtBase);
|
||||||
bool ok = (physBase != 0xFFFFFFFF) && emu->isPhysAddressValid(physBase);
|
auto physBase = physBaseOpt.value_or(0xFFFFFFFF);
|
||||||
if (ok && (virtBase != physBase))
|
bool ok = physBaseOpt.has_value() && emu->isPhysAddressValid(physBase);
|
||||||
ui->physicalAddressLabel->setText(QStringLiteral("Physical: %1").arg(physBase, 8, 16, QLatin1Char('0')));
|
if (ok && (virtBase != physBase))
|
||||||
|
ui->physicalAddressLabel->setText(QStringLiteral("Physical: %1").arg(physBase, 8, 16, QLatin1Char('0')));
|
||||||
|
|
||||||
uint8_t block[0x100];
|
uint8_t block[0x100];
|
||||||
if (ok) {
|
if (ok) {
|
||||||
for (int i = 0; i < 0x100; i++) {
|
for (int i = 0; i < 0x100; i++) {
|
||||||
block[i] = emu->readPhys8(physBase + i);
|
block[i] = emu->readPhysical(physBase + i, ARM710a::V8).value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList output;
|
QStringList output;
|
||||||
for (int row = 0; row < 16; row++) {
|
for (int row = 0; row < 16; row++) {
|
||||||
QString outLine;
|
QString outLine;
|
||||||
outLine.reserve(8 + 2 + (2 * 16) + 3 + 16);
|
outLine.reserve(8 + 2 + (2 * 16) + 3 + 16);
|
||||||
outLine.append(QStringLiteral("%1 |").arg(virtBase + (row * 16), 8, 16));
|
outLine.append(QStringLiteral("%1 |").arg(virtBase + (row * 16), 8, 16));
|
||||||
for (int col = 0; col < 16; col++) {
|
for (int col = 0; col < 16; col++) {
|
||||||
if (ok)
|
if (ok)
|
||||||
outLine.append(QStringLiteral(" %1").arg(block[row*16+col], 2, 16, QLatin1Char('0')));
|
outLine.append(QStringLiteral(" %1").arg(block[row*16+col], 2, 16, QLatin1Char('0')));
|
||||||
else
|
else
|
||||||
outLine.append(QStringLiteral(" ??"));
|
outLine.append(QStringLiteral(" ??"));
|
||||||
}
|
}
|
||||||
outLine.append(QStringLiteral(" | "));
|
outLine.append(QStringLiteral(" | "));
|
||||||
for (int col = 0; col < 16; col++) {
|
for (int col = 0; col < 16; col++) {
|
||||||
uint8_t byte = block[row*16+col];
|
uint8_t byte = block[row*16+col];
|
||||||
if (!ok)
|
if (!ok)
|
||||||
outLine.append('?');
|
outLine.append('?');
|
||||||
else if (byte >= 0x20 && byte <= 0x7E)
|
else if (byte >= 0x20 && byte <= 0x7E)
|
||||||
outLine.append(byte);
|
outLine.append(byte);
|
||||||
else
|
else
|
||||||
outLine.append('.');
|
outLine.append('.');
|
||||||
}
|
}
|
||||||
output.append(outLine);
|
output.append(outLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->memoryViewLabel->setText(output.join('\n'));
|
ui->memoryViewLabel->setText(output.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_memoryAdd1_clicked() { adjustMemoryAddress(1); }
|
void MainWindow::on_memoryAdd1_clicked() { adjustMemoryAddress(1); }
|
||||||
|
@ -320,24 +324,24 @@ void MainWindow::on_memorySub10_clicked() { adjustMemoryAddress(-0x10); }
|
||||||
void MainWindow::on_memorySub100_clicked() { adjustMemoryAddress(-0x100); }
|
void MainWindow::on_memorySub100_clicked() { adjustMemoryAddress(-0x100); }
|
||||||
|
|
||||||
void MainWindow::adjustMemoryAddress(int offset) {
|
void MainWindow::adjustMemoryAddress(int offset) {
|
||||||
uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16);
|
uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16);
|
||||||
address += offset;
|
address += offset;
|
||||||
ui->memoryViewAddress->setText(QString("%1").arg(address, 8, 16, QLatin1Char('0')));
|
ui->memoryViewAddress->setText(QString("%1").arg(address, 8, 16, QLatin1Char('0')));
|
||||||
updateMemory();
|
updateMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_writeByteButton_clicked()
|
void MainWindow::on_writeByteButton_clicked()
|
||||||
{
|
{
|
||||||
uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16);
|
uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16);
|
||||||
uint8_t value = (uint8_t)ui->memoryWriteValue->text().toUInt(nullptr, 16);
|
uint8_t value = (uint8_t)ui->memoryWriteValue->text().toUInt(nullptr, 16);
|
||||||
emu->writeVirt8(address, value);
|
emu->writeVirtual(value, address, ARM710a::V8);
|
||||||
updateMemory();
|
updateMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_writeDwordButton_clicked()
|
void MainWindow::on_writeDwordButton_clicked()
|
||||||
{
|
{
|
||||||
uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16);
|
uint32_t address = ui->memoryViewAddress->text().toUInt(nullptr, 16);
|
||||||
uint32_t value = ui->memoryWriteValue->text().toUInt(nullptr, 16);
|
uint32_t value = ui->memoryWriteValue->text().toUInt(nullptr, 16);
|
||||||
emu->writeVirt32(address, value);
|
emu->writeVirtual(value, address, ARM710a::V32);
|
||||||
updateMemory();
|
updateMemory();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue