#pragma once #include #include #include using namespace std; // Everything I thought is a lie. // Turns out the 5mx/Windermere is an ARM710T, not an ARM710a. // 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 // Speedhacks: //#define ARM710T_CACHE typedef optional MaybeU32; class ARM710 { 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, MMUFaultTypeMask = 0xF, MMUFaultDomainMask = 0xF0, MMUFaultDomainShift = 4, MMUFaultAddressMask = 0xFFFFFFFF00000000, MMUFaultAddressShift = 32 }; ARM710(bool _isTVersion) { isTVersion = _isTVersion; cp15_id = _isTVersion ? 0x41807100 : 0x41047100; clearAllValues(); } virtual ~ARM710() { } 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; #ifdef ARM710T_CACHE clearCache(); #endif flushTlb(); } void setProcessorID(uint32_t v) { cp15_id = v; } bool canAcceptFIQ() const { return !(CPSR & CPSR_FIQDisable); } bool canAcceptIRQ() const { return !(CPSR & CPSR_IRQDisable); } void requestFIQ(); // pull nFIQ low void requestIRQ(); // pull nIRQ low void reset(); // pull nRESET low bool instructionReady() const { return (prefetchCount == 2); } 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 readVirtual(uint32_t virtAddr, ValueSize valueSize); virtual MaybeU32 readPhysical(uint32_t physAddr, ValueSize valueSize) = 0; MMUFault writeVirtual(uint32_t value, uint32_t virtAddr, ARM710::ValueSize valueSize); virtual bool writePhysical(uint32_t value, uint32_t physAddr, ARM710::ValueSize valueSize) = 0; uint32_t getGPR(int index) const { return GPRs[index]; } uint32_t getCPSR() const { return CPSR; } uint32_t getRealPC() const { return GPRs[15] - (4 * prefetchCount); } void setLogger(std::function newLogger) { logger = newLogger; } uint32_t lastPcExecuted() const { return pcHistory[(pcHistoryIndex - 1) % PcHistoryCount].addr; } public: void log(const char *format, ...); void logPcHistory(); private: std::function logger; enum { PcHistoryCount = 10 }; struct { uint32_t addr, insn; } pcHistory[PcHistoryCount]; uint32_t pcHistoryIndex = 0; 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 isTVersion; 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) { /*EQ*/ case 0: return flagZ(); /*NE*/ case 1: return !flagZ(); /*CS*/ case 2: return flagC(); /*CC*/ case 3: return !flagC(); /*MI*/ case 4: return flagN(); /*PL*/ case 5: return !flagN(); /*VS*/ case 6: return flagV(); /*VC*/ case 7: return !flagV(); /*HI*/ case 8: return flagC() && !flagZ(); /*LS*/ case 9: return !flagC() || flagZ(); /*GE*/ case 0xA: return flagN() == flagV(); /*LT*/ case 0xB: return flagN() != flagV(); /*GT*/ case 0xC: return !flagZ() && (flagN() == flagV()); /*LE*/ case 0xD: return flagZ() || (flagN() != flagV()); /*AL*/ case 0xE: return true; /*NV*/ /*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 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 #ifdef ARM710T_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 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); #endif // 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 execMultiplyLong(uint32_t UAS, uint32_t RdHi, uint32_t RdLo, 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); };