broken draft of a fully custom ARM emulator core

This commit is contained in:
Ash Wolf 2019-12-22 05:02:55 +00:00
parent 868112e9fc
commit c93b268061
8 changed files with 1559 additions and 569 deletions

View File

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

976
WindCore/arm710a.cpp Normal file
View File

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

261
WindCore/arm710a.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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