diff --git a/scripts/src/cpu.lua b/scripts/src/cpu.lua index c3218773035..4095f62a4c3 100644 --- a/scripts/src/cpu.lua +++ b/scripts/src/cpu.lua @@ -1297,6 +1297,7 @@ end -------------------------------------------------- -- MIPS R4000 (MIPS III/IV) series --@src/devices/cpu/mips/mips3.h,CPUS["MIPS3"] = true +--@src/devices/cpu/mips/r4000.h,CPUS["MIPS3"] = true -------------------------------------------------- if (CPUS["MIPS3"]~=null) then @@ -1313,6 +1314,8 @@ if (CPUS["MIPS3"]~=null) then MAME_DIR .. "src/devices/cpu/mips/ps2vu.h", MAME_DIR .. "src/devices/cpu/mips/ps2vif1.cpp", MAME_DIR .. "src/devices/cpu/mips/ps2vif1.h", + MAME_DIR .. "src/devices/cpu/mips/r4000.cpp", + MAME_DIR .. "src/devices/cpu/mips/r4000.h", } end diff --git a/scripts/src/main.lua b/scripts/src/main.lua index bec62ff6f49..1184ff8ae9e 100644 --- a/scripts/src/main.lua +++ b/scripts/src/main.lua @@ -256,6 +256,7 @@ end "utils", ext_lib("expat"), "softfloat", + "softfloat3", ext_lib("jpeg"), "7z", } diff --git a/src/devices/cpu/mips/r4000.cpp b/src/devices/cpu/mips/r4000.cpp new file mode 100644 index 00000000000..7fb28b10dce --- /dev/null +++ b/src/devices/cpu/mips/r4000.cpp @@ -0,0 +1,3073 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +/* + * This is a stripped-down MIPS-III CPU derived from the main mips3 code. Its + * primary purpose is to act as a test-bed to aid in debugging MIPS-based + * systems, after which the changes/improvements from here are expected to + * be back-ported and incorporated into the original mips3 device. + * + * Because of this specific approach, no attempt is made to support many of the + * current features of the mips3 device at this time. Key differences bewteen + * this implementation and mips3 include: + * + * - only supports MIPS R4000/R4400 and QED R4600 + * - no dynamic recompilation + * - reworked address translation logic, including 64-bit modes + * - reworked softfloat3-based floating point + * - experimental primary instruction cache + * - memory tap based ll/sc + * - configurable endianness + * - it's very very very slow + * + * TODO + * - try to eliminate mode check in address calculations + * - find a better way to deal with software interrupts + * - enforce mode checks for cp1 + * - cache instructions + * - check/improve instruction timing + * + */ + +#include "emu.h" +#include "debugger.h" +#include "r4000.h" +#include "mips3dsm.h" + +#include "softfloat3/source/include/softfloat.h" + +#define LOG_GENERAL (1U << 0) +#define LOG_TLB (1U << 1) +#define LOG_CACHE (1U << 2) +#define LOG_EXCEPTION (1U << 3) +#define LOG_SYSCALL (1U << 4) + +#define VERBOSE (LOG_GENERAL) + +// operating system specific system call logging +#define SYSCALL_IRIX53 (1U << 0) +#define SYSCALL_WINNT4 (1U << 1) +#if VERBOSE & LOG_SYSCALL +#define SYSCALL_MASK (SYSCALL_IRIX53) +#else +#define SYSCALL_MASK (0) +#endif + +// experimental primary instruction cache +#define ICACHE 0 + +#include "logmacro.h" + +#define USE_ABI_REG_NAMES 1 + +// cpu instruction fiels +#define RSREG ((op >> 21) & 31) +#define RTREG ((op >> 16) & 31) +#define RDREG ((op >> 11) & 31) +#define SHIFT ((op >> 6) & 31) + +// cop1 instruction fields +#define FRREG ((op >> 21) & 31) +#define FTREG ((op >> 16) & 31) +#define FSREG ((op >> 11) & 31) +#define FDREG ((op >> 6) & 31) + +#define R4000_ENDIAN_LE_BE(le, be) ((m_cp0[CP0_Config] & CONFIG_BE) ? (be) : (le)) + +// identify odd-numbered cop1 registers +#define ODD_REGS 0x00010840U + +// address computation +#define ADDR(r, o) (m_64 ? (r + s16(o)) : s64(s32(u32(r) + s16(o)))) + +#define SR m_cp0[CP0_Status] +#define CAUSE m_cp0[CP0_Cause] + +DEFINE_DEVICE_TYPE(R4000, r4000_device, "r4000", "MIPS R4000") +DEFINE_DEVICE_TYPE(R4400, r4400_device, "r4400", "MIPS R4000") +DEFINE_DEVICE_TYPE(R4600, r4600_device, "r4600", "QED R4000") + +r4000_base_device::r4000_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, u32 prid, cache_size_t icache_size, cache_size_t dcache_size) + : cpu_device(mconfig, type, tag, owner, clock) + , m_program_config_le("program", ENDIANNESS_LITTLE, 64, 32) + , m_program_config_be("program", ENDIANNESS_BIG, 64, 32) + , m_ll_watch(nullptr) + , m_fcr0(0x00000500U) +{ + m_cp0[CP0_PRId] = prid; + + // default configuration + m_cp0[CP0_Config] = CONFIG_BE | (icache_size << 9) | (dcache_size << 6); +} + +void r4000_base_device::device_start() +{ + // TODO: save state + + state_add(STATE_GENPC, "GENPC", m_pc).noshow(); + state_add(STATE_GENPCBASE, "CURPC", m_pc).noshow(); + state_add(MIPS3_PC, "PC", m_pc).formatstr("%016X"); + + // exception processing + state_add(MIPS3_CP0 + CP0_Status, "SR", m_cp0[CP0_Status]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_EPC, "EPC", m_cp0[CP0_EPC]).formatstr("%016X"); + state_add(MIPS3_CP0 + CP0_Cause, "Cause", m_cp0[CP0_Cause]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_Context, "Context", m_cp0[CP0_Context]).formatstr("%016X"); + state_add(MIPS3_CP0 + CP0_BadVAddr, "BadVAddr", m_cp0[CP0_BadVAddr]).formatstr("%016X"); + state_add(MIPS3_CP0 + CP0_Compare, "Compare", m_cp0[CP0_Compare]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_WatchLo, "WatchLo", m_cp0[CP0_WatchLo]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_WatchHi, "WatchHi", m_cp0[CP0_WatchHi]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_XContext, "XContext", m_cp0[CP0_XContext]).formatstr("%016X"); + + // memory management + state_add(MIPS3_CP0 + CP0_Index, "Index", m_cp0[CP0_Index]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_EntryLo0, "EntryLo0", m_cp0[CP0_EntryLo0]).formatstr("%016X"); + state_add(MIPS3_CP0 + CP0_EntryLo1, "EntryLo1", m_cp0[CP0_EntryLo1]).formatstr("%016X"); + state_add(MIPS3_CP0 + CP0_PageMask, "PageMask", m_cp0[CP0_PageMask]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_Wired, "Wired", m_cp0[CP0_Wired]).formatstr("%08X"); + state_add(MIPS3_CP0 + CP0_EntryHi, "EntryHi", m_cp0[CP0_EntryHi]).formatstr("%016X"); + state_add(MIPS3_CP0 + CP0_LLAddr, "LLAddr", m_cp0[CP0_LLAddr]).formatstr("%08X"); + +#if USE_ABI_REG_NAMES + state_add(MIPS3_R0 + 0, "zero", m_r[0]).callimport().formatstr("%016X"); // Can't change R0 + state_add(MIPS3_R0 + 1, "at", m_r[1]).formatstr("%016X"); + state_add(MIPS3_R0 + 2, "v0", m_r[2]).formatstr("%016X"); + state_add(MIPS3_R0 + 3, "v1", m_r[3]).formatstr("%016X"); + state_add(MIPS3_R0 + 4, "a0", m_r[4]).formatstr("%016X"); + state_add(MIPS3_R0 + 5, "a1", m_r[5]).formatstr("%016X"); + state_add(MIPS3_R0 + 6, "a2", m_r[6]).formatstr("%016X"); + state_add(MIPS3_R0 + 7, "a3", m_r[7]).formatstr("%016X"); + state_add(MIPS3_R0 + 8, "t0", m_r[8]).formatstr("%016X"); + state_add(MIPS3_R0 + 9, "t1", m_r[9]).formatstr("%016X"); + state_add(MIPS3_R0 + 10, "t2", m_r[10]).formatstr("%016X"); + state_add(MIPS3_R0 + 11, "t3", m_r[11]).formatstr("%016X"); + state_add(MIPS3_R0 + 12, "t4", m_r[12]).formatstr("%016X"); + state_add(MIPS3_R0 + 13, "t5", m_r[13]).formatstr("%016X"); + state_add(MIPS3_R0 + 14, "t6", m_r[14]).formatstr("%016X"); + state_add(MIPS3_R0 + 15, "t7", m_r[15]).formatstr("%016X"); + state_add(MIPS3_R0 + 16, "s0", m_r[16]).formatstr("%016X"); + state_add(MIPS3_R0 + 17, "s1", m_r[17]).formatstr("%016X"); + state_add(MIPS3_R0 + 18, "s2", m_r[18]).formatstr("%016X"); + state_add(MIPS3_R0 + 19, "s3", m_r[19]).formatstr("%016X"); + state_add(MIPS3_R0 + 20, "s4", m_r[20]).formatstr("%016X"); + state_add(MIPS3_R0 + 21, "s5", m_r[21]).formatstr("%016X"); + state_add(MIPS3_R0 + 22, "s6", m_r[22]).formatstr("%016X"); + state_add(MIPS3_R0 + 23, "s7", m_r[23]).formatstr("%016X"); + state_add(MIPS3_R0 + 24, "t8", m_r[24]).formatstr("%016X"); + state_add(MIPS3_R0 + 25, "t9", m_r[25]).formatstr("%016X"); + state_add(MIPS3_R0 + 26, "k0", m_r[26]).formatstr("%016X"); + state_add(MIPS3_R0 + 27, "k1", m_r[27]).formatstr("%016X"); + state_add(MIPS3_R0 + 28, "gp", m_r[28]).formatstr("%016X"); + state_add(MIPS3_R0 + 29, "sp", m_r[29]).formatstr("%016X"); + state_add(MIPS3_R0 + 30, "fp", m_r[30]).formatstr("%016X"); + state_add(MIPS3_R0 + 31, "ra", m_r[31]).formatstr("%016X"); +#else + state_add(MIPS3_R0, "R0", m_r[0]).callimport().formatstr("%016X"); + for (unsigned i = 1; i < 32; i++) + state_add(MIPS3_R0 + i, util::string_format("R%d", i).c_str(), m_r[i]); +#endif + + state_add(MIPS3_HI, "HI", m_hi).formatstr("%016X"); + state_add(MIPS3_LO, "LO", m_lo).formatstr("%016X"); + + // floating point registers + state_add(MIPS3_FCR31, "FCR31", m_fcr31).formatstr("%08X"); + for (unsigned i = 0; i < 32; i++) + state_add(MIPS3_F0 + i, util::string_format("F%d", i).c_str(), m_f[i]); + + set_icountptr(m_icount); + + m_cp0_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(r4000_base_device::cp0_timer_callback), this)); + + // compute icache line selection mask and allocate tag and data + unsigned const config_ic = (m_cp0[CP0_Config] & CONFIG_IC) >> 9; + + m_icache_mask_hi = (0x1000U << config_ic) - 1; + m_icache_tag = std::make_unique(0x100U << config_ic); + m_icache_data = std::make_unique((0x1000U << config_ic) >> 2); +} + +void r4000_base_device::device_reset() +{ + m_branch_state = NONE; + m_pc = s64(s32(0xbfc00000)); + + m_cp0[CP0_Status] = SR_BEV | SR_ERL; + m_cp0[CP0_Wired] = 0; + m_cp0[CP0_Compare] = 0; + m_cp0[CP0_Count] = 0; + + m_cp0_timer_zero = total_cycles(); + m_64 = false; + + if (m_ll_watch) + { + m_ll_watch->remove(); + m_ll_watch = nullptr; + } + + m_cp0[CP0_WatchLo] = 0; + m_cp0[CP0_WatchHi] = 0; + + m_icache_hit = 0; + m_icache_miss = 0; +} + +void r4000_base_device::device_stop() +{ + if (ICACHE) + LOGMASKED(LOG_CACHE, "icache hit ratio %.3f%% (%d hits %d misses)\n", + double(m_icache_hit) / double(m_icache_hit + m_icache_miss) * 100.0, m_icache_hit, m_icache_miss); +} + +device_memory_interface::space_config_vector r4000_base_device::memory_space_config() const +{ + return space_config_vector{ + std::make_pair(AS_PROGRAM, R4000_ENDIAN_LE_BE(&m_program_config_le, &m_program_config_be)) + }; +} + +bool r4000_base_device::memory_translate(int spacenum, int intention, offs_t &address) +{ + // FIXME: address truncation + u64 placeholder = s32(address); + + translate_t const t = translate(intention, placeholder); + + if (t == ERROR || t == MISS) + return false; + + address = placeholder; + return true; +} + +std::unique_ptr r4000_base_device::create_disassembler() +{ + return std::make_unique(); +} + +void r4000_base_device::execute_run() +{ + // check interrupts + if ((CAUSE & SR & CAUSE_IP) && (SR & SR_IE) && !(SR & (SR_EXL | SR_ERL))) + cpu_exception(EXCEPTION_INT); + + while (m_icount > 0) + { + debugger_instruction_hook(m_pc); + + fetch(m_pc, + [this](u32 const op) + { + cpu_execute(op); + + // zero register zero + m_r[0] = 0; + }); + + // update pc and branch state + switch (m_branch_state) + { + case NONE: + m_pc += 4; + break; + + case DELAY: + m_branch_state = NONE; + m_pc = m_branch_target; + break; + + case BRANCH: + m_branch_state = DELAY; + m_pc += 4; + break; + + case EXCEPTION: + m_branch_state = NONE; + break; + + case NULLIFY: + m_branch_state = NONE; + m_pc += 8; + break; + } + + m_icount--; + } +} + +void r4000_base_device::execute_set_input(int inputnum, int state) +{ + if (state) + m_cp0[CP0_Cause] |= (CAUSE_IPEX0 << inputnum); + else + m_cp0[CP0_Cause] &= ~(CAUSE_IPEX0 << inputnum); +} + +void r4000_base_device::cpu_execute(u32 const op) +{ + switch (op >> 26) + { + case 0x00: // SPECIAL + switch (op & 0x3f) + { + case 0x00: // SLL + m_r[RDREG] = s64(s32(m_r[RTREG] << SHIFT)); + break; + //case 0x01: // * + case 0x02: // SRL + m_r[RDREG] = u32(m_r[RTREG]) >> SHIFT; + break; + case 0x03: // SRA + m_r[RDREG] = s64(s32(m_r[RTREG]) >> SHIFT); + break; + case 0x04: // SLLV + m_r[RDREG] = s64(s32(m_r[RTREG] << (m_r[RSREG] & 31))); + break; + //case 0x05: // * + case 0x06: // SRLV + m_r[RDREG] = u32(m_r[RTREG]) >> (m_r[RSREG] & 31); + break; + case 0x07: // SRAV + m_r[RDREG] = s64(s32(m_r[RTREG]) >> (m_r[RSREG] & 31)); + break; + case 0x08: // JR + m_branch_state = BRANCH; + m_branch_target = ADDR(m_r[RSREG], 0); + break; + case 0x09: // JALR + m_branch_state = BRANCH; + m_branch_target = ADDR(m_r[RSREG], 0); + m_r[RDREG] = ADDR(m_pc, 8); + break; + //case 0x0a: // * + //case 0x0b: // * + case 0x0c: // SYSCALL + if (VERBOSE & LOG_SYSCALL) + { + if (SYSCALL_MASK & SYSCALL_IRIX53) + { + switch (m_r[2]) + { + case 0x3e9: // 1001 = exit + LOGMASKED(LOG_SYSCALL, "exit(%d) (%s)\n", m_r[4], machine().describe_context()); + break; + + case 0x3ea: // 1002 = fork + LOGMASKED(LOG_SYSCALL, "fork() (%s)\n", machine().describe_context()); + break; + + case 0x3eb: // 1003 = read + LOGMASKED(LOG_SYSCALL, "read(%d, 0x%x, %d) (%s)\n", m_r[4], m_r[5], m_r[6], machine().describe_context()); + break; + + case 0x3ec: // 1004 = write + LOGMASKED(LOG_SYSCALL, "write(%d, 0x%x, %d) (%s)\n", m_r[4], m_r[5], m_r[6], machine().describe_context()); + if (m_r[4] == 1 || m_r[4] == 2) + printf("%s", debug_string(m_r[5], m_r[6]).c_str()); + break; + + case 0x3ed: // 1005 = open + LOGMASKED(LOG_SYSCALL, "open(\"%s\", %#o) (%s)\n", debug_string(m_r[4]), m_r[5], machine().describe_context()); + break; + + case 0x3ee: // 1006 = close + LOGMASKED(LOG_SYSCALL, "close(%d) (%s)\n", m_r[4], machine().describe_context()); + break; + + case 0x3ef: // 1007 = creat + LOGMASKED(LOG_SYSCALL, "creat(\"%s\", %#o) (%s)\n", debug_string(m_r[4]), m_r[5], machine().describe_context()); + break; + + case 0x423: // 1059 = exece + LOGMASKED(LOG_SYSCALL, "exece(\"%s\", [ %s ], [ %s ]) (%s)\n", debug_string(m_r[4]), debug_string_array(m_r[5]), debug_string_array(m_r[6]), machine().describe_context()); + break; + + default: + LOGMASKED(LOG_SYSCALL, "syscall 0x%x (%s)\n", m_r[2], machine().describe_context()); + break; + } + } + else if (SYSCALL_MASK & SYSCALL_WINNT4) + { + switch (m_r[2]) + { + case 0x4f: + load(m_r[7] + 8, + [this](s64 string_pointer) + { + LOGMASKED(LOG_SYSCALL, "NtOpenFile(%s) (%s)\n", debug_string(string_pointer), machine().describe_context()); + }); + break; + + default: + LOGMASKED(LOG_SYSCALL, "syscall 0x%x (%s)\n", m_r[2], machine().describe_context()); + break; + } + } + } + cpu_exception(EXCEPTION_SYS); + break; + case 0x0d: // BREAK + cpu_exception(EXCEPTION_BP); + break; + //case 0x0e: // * + case 0x0f: // SYNC + break; + case 0x10: // MFHI + m_r[RDREG] = m_hi; + break; + case 0x11: // MTHI + m_hi = m_r[RSREG]; + break; + case 0x12: // MFLO + m_r[RDREG] = m_lo; + break; + case 0x13: // MTLO + m_lo = m_r[RSREG]; + break; + case 0x14: // DSLLV + m_r[RDREG] = m_r[RTREG] << (m_r[RSREG] & 63); + break; + //case 0x15: // * + case 0x16: // DSRLV + m_r[RDREG] = m_r[RTREG] >> (m_r[RSREG] & 63); + break; + case 0x17: // DSRAV + m_r[RDREG] = s64(m_r[RTREG]) >> (m_r[RSREG] & 63); + break; + case 0x18: // MULT + { + u64 const product = mul_32x32(s32(m_r[RSREG]), s32(m_r[RTREG])); + + m_lo = s64(s32(product)); + m_hi = s64(s32(product >> 32)); + m_icount -= 3; + } + break; + case 0x19: // MULTU + { + u64 const product = mulu_32x32(u32(m_r[RSREG]), u32(m_r[RTREG])); + + m_lo = s64(s32(product)); + m_hi = s64(s32(product >> 32)); + m_icount -= 3; + } + break; + case 0x1a: // DIV + if (m_r[RTREG]) + { + m_lo = s64(s32(m_r[RSREG]) / s32(m_r[RTREG])); + m_hi = s64(s32(m_r[RSREG]) % s32(m_r[RTREG])); + } + m_icount -= 35; + break; + case 0x1b: // DIVU + if (m_r[RTREG]) + { + m_lo = s64(u32(m_r[RSREG]) / u32(m_r[RTREG])); + m_hi = s64(u32(m_r[RSREG]) % u32(m_r[RTREG])); + } + m_icount -= 35; + break; + case 0x1c: // DMULT + { + u64 const a_hi = u32(m_r[RSREG] >> 32); + u64 const b_hi = u32(m_r[RTREG] >> 32); + u64 const a_lo = u32(m_r[RSREG]); + u64 const b_lo = u32(m_r[RTREG]); + + u64 const p1 = a_lo * b_lo; + u64 const p2 = a_hi * b_lo; + u64 const p3 = a_lo * b_hi; + u64 const p4 = a_hi * b_hi; + u64 const carry = u32(((p1 >> 32) + u32(p2) + u32(p3)) >> 32); + + m_lo = p1 + (p2 << 32) + (p3 << 32); + m_hi = p4 + (p2 >> 32) + (p3 >> 32) + carry; + + // adjust for sign + if (m_r[RSREG] < 0) + m_hi -= m_r[RTREG]; + if (m_r[RTREG] < 0) + m_hi -= m_r[RSREG]; + + m_icount -= 7; + } + break; + case 0x1d: // DMULTU + { + u64 const a_hi = u32(m_r[RSREG] >> 32); + u64 const b_hi = u32(m_r[RTREG] >> 32); + u64 const a_lo = u32(m_r[RSREG]); + u64 const b_lo = u32(m_r[RTREG]); + + u64 const p1 = a_lo * b_lo; + u64 const p2 = a_hi * b_lo; + u64 const p3 = a_lo * b_hi; + u64 const p4 = a_hi * b_hi; + u64 const carry = u32(((p1 >> 32) + u32(p2) + u32(p3)) >> 32); + + m_lo = p1 + (p2 << 32) + (p3 << 32); + m_hi = p4 + (p2 >> 32) + (p3 >> 32) + carry; + + m_icount -= 7; + } + break; + case 0x1e: // DDIV + if (m_r[RTREG]) + { + m_lo = s64(m_r[RSREG]) / s64(m_r[RTREG]); + m_hi = s64(m_r[RSREG]) % s64(m_r[RTREG]); + } + m_icount -= 67; + break; + case 0x1f: // DDIVU + if (m_r[RTREG]) + { + m_lo = m_r[RSREG] / m_r[RTREG]; + m_hi = m_r[RSREG] % m_r[RTREG]; + } + m_icount -= 67; + break; + case 0x20: // ADD + { + u32 const sum = u32(m_r[RSREG]) + u32(m_r[RTREG]); + + // overflow: (sign(addend0) == sign(addend1)) && (sign(addend0) != sign(sum)) + if (!BIT(u32(m_r[RSREG]) ^ u32(m_r[RTREG]), 31) && BIT(u32(m_r[RSREG]) ^ sum, 31)) + cpu_exception(EXCEPTION_OV); + else + m_r[RDREG] = s64(s32(sum)); + } + break; + case 0x21: // ADDU + m_r[RDREG] = s64(s32(u32(m_r[RSREG]) + u32(m_r[RTREG]))); + break; + case 0x22: // SUB + { + u32 const difference = u32(m_r[RSREG]) - u32(m_r[RTREG]); + + // overflow: (sign(minuend) != sign(subtrahend)) && (sign(minuend) != sign(difference)) + if (BIT(u32(m_r[RSREG]) ^ u32(m_r[RTREG]), 31) && BIT(u32(m_r[RSREG]) ^ difference, 31)) + cpu_exception(EXCEPTION_OV); + else + m_r[RDREG] = s64(s32(difference)); + } + break; + case 0x23: // SUBU + m_r[RDREG] = s64(s32(u32(m_r[RSREG]) - u32(m_r[RTREG]))); + break; + case 0x24: // AND + m_r[RDREG] = m_r[RSREG] & m_r[RTREG]; + break; + case 0x25: // OR + m_r[RDREG] = m_r[RSREG] | m_r[RTREG]; + break; + case 0x26: // XOR + m_r[RDREG] = m_r[RSREG] ^ m_r[RTREG]; + break; + case 0x27: // NOR + m_r[RDREG] = ~(m_r[RSREG] | m_r[RTREG]); + break; + //case 0x28: // * + //case 0x29: // * + case 0x2a: // SLT + m_r[RDREG] = s64(m_r[RSREG]) < s64(m_r[RTREG]); + break; + case 0x2b: // SLTU + m_r[RDREG] = m_r[RSREG] < m_r[RTREG]; + break; + case 0x2c: // DADD + { + u64 const sum = m_r[RSREG] + m_r[RTREG]; + + // overflow: (sign(addend0) == sign(addend1)) && (sign(addend0) != sign(sum)) + if (!BIT(m_r[RSREG] ^ m_r[RTREG], 63) && BIT(m_r[RSREG] ^ sum, 63)) + cpu_exception(EXCEPTION_OV); + else + m_r[RDREG] = sum; + } + break; + case 0x2d: // DADDU + m_r[RDREG] = m_r[RSREG] + m_r[RTREG]; + break; + case 0x2e: // DSUB + { + u64 const difference = m_r[RSREG] - m_r[RTREG]; + + // overflow: (sign(minuend) != sign(subtrahend)) && (sign(minuend) != sign(difference)) + if (BIT(m_r[RSREG] ^ m_r[RTREG], 63) && BIT(m_r[RSREG] ^ difference, 63)) + cpu_exception(EXCEPTION_OV); + else + m_r[RDREG] = difference; + } + break; + case 0x2f: // DSUBU + m_r[RDREG] = m_r[RSREG] - m_r[RTREG]; + break; + case 0x30: // TGE + if (s64(m_r[RSREG]) >= s64(m_r[RTREG])) + cpu_exception(EXCEPTION_TR); + break; + case 0x31: // TGEU + if (m_r[RSREG] >= m_r[RTREG]) + cpu_exception(EXCEPTION_TR); + break; + case 0x32: // TLT + if (s64(m_r[RSREG]) < s64(m_r[RTREG])) + cpu_exception(EXCEPTION_TR); + break; + case 0x33: // TLTU + if (m_r[RSREG] < m_r[RTREG]) + cpu_exception(EXCEPTION_TR); + break; + case 0x34: // TEQ + if (m_r[RSREG] == m_r[RTREG]) + cpu_exception(EXCEPTION_TR); + break; + //case 0x35: // * + case 0x36: // TNE + if (m_r[RSREG] != m_r[RTREG]) + cpu_exception(EXCEPTION_TR); + break; + //case 0x37: // * + case 0x38: // DSLL + m_r[RDREG] = m_r[RTREG] << SHIFT; + break; + //case 0x39: // * + case 0x3a: // DSRL + m_r[RDREG] = m_r[RTREG] >> SHIFT; + break; + case 0x3b: // DSRA + m_r[RDREG] = s64(m_r[RTREG]) >> SHIFT; + break; + case 0x3c: // DSLL32 + m_r[RDREG] = m_r[RTREG] << (SHIFT + 32); + break; + //case 0x3d: // * + case 0x3e: // DSRL32 + m_r[RDREG] = m_r[RTREG] >> (SHIFT + 32); + break; + case 0x3f: // DSRA32 + m_r[RDREG] = s64(m_r[RTREG]) >> (SHIFT + 32); + break; + + default: + // * Operation codes marked with an asterisk cause reserved + // instruction exceptions in all current implementations and are + // reserved for future versions of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } + break; + case 0x01: // REGIMM + switch ((op >> 16) & 0x1f) + { + case 0x00: // BLTZ + if (s64(m_r[RSREG]) < 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x01: // BGEZ + if (s64(m_r[RSREG]) >= 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x02: // BLTZL + if (s64(m_r[RSREG]) < 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + case 0x03: // BGEZL + if (s64(m_r[RSREG]) >= 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + //case 0x04: // * + //case 0x05: // * + //case 0x06: // * + //case 0x07: // * + case 0x08: // TGEI + if (s64(m_r[RSREG]) >= s16(op)) + cpu_exception(EXCEPTION_TR); + break; + case 0x09: // TGEIU + if (m_r[RSREG] >= u16(op)) + cpu_exception(EXCEPTION_TR); + break; + case 0x0a: // TLTI + if (s64(m_r[RSREG]) < s16(op)) + cpu_exception(EXCEPTION_TR); + break; + case 0x0b: // TLTIU + if (m_r[RSREG] >= u16(op)) + cpu_exception(EXCEPTION_TR); + break; + case 0x0c: // TEQI + if (m_r[RSREG] == u16(op)) + cpu_exception(EXCEPTION_TR); + break; + //case 0x0d: // * + case 0x0e: // TNEI + if (m_r[RSREG] != u16(op)) + cpu_exception(EXCEPTION_TR); + break; + //case 0x0f: // * + case 0x10: // BLTZAL + if (s64(m_r[RSREG]) < 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + m_r[31] = ADDR(m_pc, 8); + break; + case 0x11: // BGEZAL + if (s64(m_r[RSREG]) >= 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + m_r[31] = ADDR(m_pc, 8); + break; + case 0x12: // BLTZALL + if (s64(m_r[RSREG]) < 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + m_r[31] = ADDR(m_pc, 8); + break; + case 0x13: // BGEZALL + if (s64(m_r[RSREG]) >= 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + m_r[31] = ADDR(m_pc, 8); + break; + //case 0x14: // * + //case 0x15: // * + //case 0x16: // * + //case 0x17: // * + //case 0x18: // * + //case 0x19: // * + //case 0x1a: // * + //case 0x1b: // * + //case 0x1c: // * + //case 0x1d: // * + //case 0x1e: // * + //case 0x1f: // * + + default: + // * Operation codes marked with an asterisk cause reserved + // instruction exceptions in all current implementations and are + // reserved for future versions of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } + break; + case 0x02: // J + m_branch_state = BRANCH; + m_branch_target = (ADDR(m_pc, 4) & ~0x0fffffffULL) | ((op & 0x03ffffffU) << 2); + break; + case 0x03: // JAL + m_branch_state = BRANCH; + m_branch_target = (ADDR(m_pc, 4) & ~0x0fffffffULL) | ((op & 0x03ffffffU) << 2); + m_r[31] = ADDR(m_pc, 8); + break; + case 0x04: // BEQ + if (m_r[RSREG] == m_r[RTREG]) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x05: // BNE + if (m_r[RSREG] != m_r[RTREG]) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x06: // BLEZ + if (s64(m_r[RSREG]) <= 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x07: // BGTZ + if (s64(m_r[RSREG]) > 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x08: // ADDI + { + u32 const sum = u32(m_r[RSREG]) + s16(op); + + // overflow: (sign(addend0) == sign(addend1)) && (sign(addend0) != sign(sum)) + if (!BIT(u32(m_r[RSREG]) ^ s32(s16(op)), 31) && BIT(u32(m_r[RSREG]) ^ sum, 31)) + cpu_exception(EXCEPTION_OV); + else + m_r[RTREG] = s64(s32(sum)); + } + break; + case 0x09: // ADDIU + m_r[RTREG] = s64(s32(u32(m_r[RSREG]) + s16(op))); + break; + case 0x0a: // SLTI + m_r[RTREG] = s64(m_r[RSREG]) < s64(s16(op)); + break; + case 0x0b: // SLTIU + m_r[RTREG] = u64(m_r[RSREG]) < u64(s16(op)); + break; + case 0x0c: // ANDI + m_r[RTREG] = m_r[RSREG] & u16(op); + break; + case 0x0d: // ORI + m_r[RTREG] = m_r[RSREG] | u16(op); + break; + case 0x0e: // XORI + m_r[RTREG] = m_r[RSREG] ^ u16(op); + break; + case 0x0f: // LUI + m_r[RTREG] = s64(s32(u16(op) << 16)); + break; + case 0x10: // COP0 + cp0_execute(op); + break; + case 0x11: // COP1 + cp1_execute(op); + break; + case 0x12: // COP2 + cp2_execute(op); + break; + //case 0x13: // * + case 0x14: // BEQL + if (m_r[RSREG] == m_r[RTREG]) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + case 0x15: // BNEL + if (m_r[RSREG] != m_r[RTREG]) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + case 0x16: // BLEZL + if (s64(m_r[RSREG]) <= 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + case 0x17: // BGTZL + if (s64(m_r[RSREG]) > 0) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + case 0x18: // DADDI + { + u64 const sum = m_r[RSREG] + s64(s16(op)); + + // overflow: (sign(addend0) == sign(addend1)) && (sign(addend0) != sign(sum)) + if (!BIT(m_r[RSREG] ^ s64(s16(op)), 63) && BIT(m_r[RSREG] ^ sum, 63)) + cpu_exception(EXCEPTION_OV); + else + m_r[RTREG] = sum; + } + break; + case 0x19: // DADDIU + m_r[RTREG] = m_r[RSREG] + s64(s16(op)); + break; + case 0x1a: // LDL + if (m_64 || !(SR & SR_KSU) || (SR & SR_EXL) || (SR & SR_ERL)) + cpu_ldl(op); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x1b: // LDR + if (m_64 || !(SR & SR_KSU) || (SR & SR_EXL) || (SR & SR_ERL)) + cpu_ldr(op); + else + cpu_exception(EXCEPTION_RI); + break; + //case 0x1c: // * + //case 0x1d: // * + //case 0x1e: // * + //case 0x1f: // * + case 0x20: // LB + load(ADDR(m_r[RSREG], op), + [this, op](s8 data) + { + m_r[RTREG] = data; + }); + break; + case 0x21: // LH + load(ADDR(m_r[RSREG], op), + [this, op](s16 data) + { + m_r[RTREG] = data; + }); + break; + case 0x22: // LWL + cpu_lwl(op); + break; + case 0x23: // LW + load(ADDR(m_r[RSREG], op), + [this, op](s32 data) + { + m_r[RTREG] = data; + }); + break; + case 0x24: // LBU + load(ADDR(m_r[RSREG], op), + [this, op](u8 data) + { + m_r[RTREG] = data; + }); + break; + case 0x25: // LHU + load(ADDR(m_r[RSREG], op), + [this, op](u16 data) + { + m_r[RTREG] = data; + }); + break; + case 0x26: // LWR + cpu_lwr(op); + break; + case 0x27: // LWU + load(ADDR(m_r[RSREG], op), + [this, op](u32 data) + { + m_r[RTREG] = data; + }); + break; + case 0x28: // SB + store(ADDR(m_r[RSREG], op), u8(m_r[RTREG])); + break; + case 0x29: // SH + store(ADDR(m_r[RSREG], op), u16(m_r[RTREG])); + break; + case 0x2a: // SWL + cpu_swl(op); + break; + case 0x2b: // SW + store(ADDR(m_r[RSREG], op), u32(m_r[RTREG])); + break; + case 0x2c: // SDL + if (m_64 || !(SR & SR_KSU) || (SR & SR_EXL) || (SR & SR_ERL)) + cpu_sdl(op); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x2d: // SDR + if (m_64 || !(SR & SR_KSU) || (SR & SR_EXL) || (SR & SR_ERL)) + cpu_sdr(op); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x2e: // SWR + cpu_swr(op); + break; + case 0x2f: // CACHE + if ((SR & SR_KSU) && !(SR & SR_CU0) && !(SR & (SR_EXL | SR_ERL))) + { + cpu_exception(EXCEPTION_CP0); + break; + } + + switch ((op >> 16) & 0x1f) + { + case 0x00: // index invalidate (I) + if (ICACHE) + { + m_icache_tag[(ADDR(m_r[RSREG], op) & m_icache_mask_hi) >> m_icache_shift] &= ~ICACHE_V; + break; + } + + case 0x04: // index load tag (I) + if (ICACHE) + { + u32 const tag = m_icache_tag[(ADDR(m_r[RSREG], op) & m_icache_mask_hi) >> m_icache_shift]; + + m_cp0[CP0_TagLo] = ((tag & ICACHE_PTAG) << 8) | ((tag & ICACHE_V) >> 18) | ((tag & ICACHE_P) >> 25); + m_cp0[CP0_ECC] = 0; // data ecc or parity + + break; + } + + case 0x08: // index store tag (I) + if (ICACHE) + { + // FIXME: compute parity + m_icache_tag[(ADDR(m_r[RSREG], op) & m_icache_mask_hi) >> m_icache_shift] = + (m_cp0[CP0_TagLo] & TAGLO_PTAGLO) >> 8 | (m_cp0[CP0_TagLo] & TAGLO_PSTATE) << 18; + + break; + } + + case 0x01: // index writeback invalidate (D) + case 0x02: // index invalidate (SI) + case 0x03: // index writeback invalidate (SD) + + case 0x05: // index load tag (D) + case 0x06: // index load tag (SI) + case 0x07: // index load tag (SI) + + case 0x09: // index store tag (D) + case 0x0a: // index store tag (SI) + case 0x0b: // index store tag (SD) + + case 0x0d: // create dirty exclusive (D) + case 0x0f: // create dirty exclusive (SD) + + case 0x10: // hit invalidate (I) + case 0x11: // hit invalidate (D) + case 0x12: // hit invalidate (SI) + case 0x13: // hit invalidate (SD) + + case 0x14: // fill (I) + case 0x15: // hit writeback invalidate (D) + case 0x17: // hit writeback invalidate (SD) + + case 0x18: // hit writeback (I) + case 0x19: // hit writeback (D) + case 0x1b: // hit writeback (SD) + + case 0x1e: // hit set virtual (SI) + case 0x1f: // hit set virtual (SD) + //LOGMASKED(LOG_CACHE, "cache 0x%08x unimplemented (%s)\n", op, machine().describe_context()); + break; + } + break; + case 0x30: // LL + load_linked(ADDR(m_r[RSREG], op), + [this, op](u64 address, s32 data) + { + // remove existing tap + if (m_ll_watch) + m_ll_watch->remove(); + + m_r[RTREG] = data; + m_cp0[CP0_LLAddr] = u32(address >> 4); + + // install write tap + // FIXME: physical address truncation + m_ll_watch = space(0).install_write_tap(offs_t(address & ~7), offs_t(address | 7), "ll", + [this, hi(bool(BIT(address, 2)))](offs_t offset, u64 &data, u64 mem_mask) + { + if (hi ? ACCESSING_BITS_32_63 : ACCESSING_BITS_0_31) + { + m_ll_watch->remove(); + m_ll_watch = nullptr; + } + }); + }); + break; + case 0x31: // LWC1 + cp1_execute(op); + break; + case 0x32: // LWC2 + cp2_execute(op); + break; + //case 0x33: // * + case 0x34: // LLD + load_linked(ADDR(m_r[RSREG], op), + [this, op](u64 address, u64 data) + { + // remove existing tap + if (m_ll_watch) + m_ll_watch->remove(); + + m_r[RTREG] = data; + m_cp0[CP0_LLAddr] = u32(address >> 4); + + // install write tap + // FIXME: address truncation + m_ll_watch = space(0).install_write_tap(offs_t(address & ~7), offs_t(address | 7), "lld", + [this](offs_t offset, u64 &data, u64 mem_mask) + { + m_ll_watch->remove(); + m_ll_watch = nullptr; + }); + }); + break; + case 0x35: // LDC1 + cp1_execute(op); + break; + case 0x36: // LDC2 + cp2_execute(op); + break; + case 0x37: // LD + load(ADDR(m_r[RSREG], op), + [this, op](u64 data) + { + m_r[RTREG] = data; + }); + break; + case 0x38: // SC + if (m_ll_watch) + { + m_ll_watch->remove(); + m_ll_watch = nullptr; + + store(ADDR(m_r[RSREG], op), u32(m_r[RTREG])); + m_r[RTREG] = 1; + } + else + m_r[RTREG] = 0; + break; + case 0x39: // SWC1 + cp1_execute(op); + break; + case 0x3a: // SWC2 + cp2_execute(op); + break; + //case 0x3b: // * + case 0x3c: // SCD + if (m_ll_watch) + { + m_ll_watch->remove(); + m_ll_watch = nullptr; + + store(ADDR(m_r[RSREG], op), m_r[RTREG]); + m_r[RTREG] = 1; + } + else + m_r[RTREG] = 0; + break; + case 0x3d: // SDC1 + cp1_execute(op); + break; + case 0x3e: // SDC2 + cp2_execute(op); + break; + case 0x3f: // SD + store(ADDR(m_r[RSREG], op), m_r[RTREG]); + break; + + default: + // * Operation codes marked with an asterisk cause reserved instruction + // exceptions in all current implementations and are reserved for future + // versions of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } +} + +void r4000_base_device::cpu_exception(u32 exception, u16 const vector) +{ + if (exception != EXCEPTION_INT) + LOGMASKED(LOG_EXCEPTION, "exception 0x%08x\n", exception); + + if (!(SR & SR_EXL)) + { + m_cp0[CP0_EPC] = m_pc; + + CAUSE = (CAUSE & CAUSE_IP) | exception; + + // if in a branch delay slot, restart at the branch instruction + if (m_branch_state == DELAY) + { + m_cp0[CP0_EPC] -= 4; + CAUSE |= CAUSE_BD; + } + + SR |= SR_EXL; + + m_64 = m_cp0[CP0_Status] & SR_KX; + } + else + CAUSE = (CAUSE & (CAUSE_BD | CAUSE_IP)) | exception; + + m_branch_state = EXCEPTION; + m_pc = ((SR & SR_BEV) ? s64(s32(0xbfc00200)) : s64(s32(0x80000000))) + vector; + + if (exception != EXCEPTION_INT) + debugger_exception_hook(exception); +} + +void r4000_base_device::cpu_lwl(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 3) ^ R4000_ENDIAN_LE_BE(3, 0)) << 3; + + load(offset & ~3, + [this, op, shift](u32 const data) + { + m_r[RTREG] = s32((m_r[RTREG] & ~u32(~u32(0) << shift)) | (data << shift)); + }); +} + +void r4000_base_device::cpu_lwr(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 0x3) ^ R4000_ENDIAN_LE_BE(0, 3)) << 3; + + load(offset & ~3, + [this, op, shift](u32 const data) + { + m_r[RTREG] = s32((m_r[RTREG] & ~u32(~u32(0) >> shift)) | (data >> shift)); + }); +} + +void r4000_base_device::cpu_swl(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 3) ^ R4000_ENDIAN_LE_BE(3, 0)) << 3; + + store(offset & ~3, u32(m_r[RTREG]) >> shift, ~u32(0) >> shift); +} + +void r4000_base_device::cpu_swr(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 3) ^ R4000_ENDIAN_LE_BE(0, 3)) << 3; + + store(offset & ~3, u32(m_r[RTREG]) << shift, ~u32(0) << shift); +} + +void r4000_base_device::cpu_ldl(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(7, 0)) << 3; + + load(offset & ~7, + [this, op, shift](u64 const data) + { + m_r[RTREG] = (m_r[RTREG] & ~u64(~u64(0) << shift)) | (data << shift); + }); +} + +void r4000_base_device::cpu_ldr(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(0, 7)) << 3; + + load(offset & ~7, + [this, op, shift](u64 const data) + { + m_r[RTREG] = (m_r[RTREG] & ~u64(~u64(0) >> shift)) | (data >> shift); + }); +} + +void r4000_base_device::cpu_sdl(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(7, 0)) << 3; + + store(offset & ~7, m_r[RTREG] >> shift, ~u64(0) >> shift); +} + +void r4000_base_device::cpu_sdr(u32 const op) +{ + unsigned const reverse = (SR & SR_RE) && ((SR & SR_KSU) == SR_KSU_U) ? 7 : 0; + u64 const offset = u64(ADDR(m_r[RSREG], op)) ^ reverse; + unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(0, 7)) << 3; + + store(offset & ~7, m_r[RTREG] << shift, ~u64(0) << shift); +} + +void r4000_base_device::cp0_execute(u32 const op) +{ + if ((SR & SR_KSU) && !(SR & SR_CU0) && !(SR & (SR_EXL | SR_ERL))) + { + cpu_exception(EXCEPTION_CP0); + return; + } + + switch ((op >> 21) & 0x1f) + { + case 0x00: // MFC0 + m_r[RTREG] = s32(cp0_get(RDREG)); + break; + case 0x01: // DMFC0 + // ε Operation codes marked with epsilon are valid when the + // processor is operating either in the Kernel mode or in the + // 64-bit non-Kernel (User or Supervisor) mode. These instructions + // cause a reserved instruction exception if 64-bit operation is + // not enabled in User or Supervisor mode. + if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || m_64) + m_r[RTREG] = cp0_get(RDREG); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x02: // CFC0 + break; + + case 0x04: // MTC0 + cp0_set(RDREG, s64(s32(m_r[RTREG]))); + break; + case 0x05: // DMTC0 + // ε Operation codes marked with epsilon are valid when the + // processor is operating either in the Kernel mode or in the + // 64-bit non-Kernel (User or Supervisor) mode. These instructions + // cause a reserved instruction exception if 64-bit operation is + // not enabled in User or Supervisor mode. + if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || m_64) + cp0_set(RDREG, m_r[RTREG]); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x06: // CTC0 + break; + + case 0x08: // BC0 + switch ((op >> 16) & 0x1f) + { + case 0x00: // BC0F + case 0x01: // BC0T + case 0x02: // BC0FL + case 0x03: // BC0TL + // fall through + + default: + // γ Operation codes marked with a gamma cause a reserved + // instruction exception. They are reserved for future versions + // of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } + break; + + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: + // CP0 function + switch (op & 0x3f) + { + case 0x01: // TLBR + cp0_tlbr(); + break; + case 0x02: // TLBWI + cp0_tlbwi(m_cp0[CP0_Index] & 0x3f); + break; + + case 0x06: // TLBWR + cp0_tlbwr(); + break; + + case 0x08: // TLBP + cp0_tlbp(); + break; + + case 0x10: // RFE + // ξ Operation codes marked with a xi cause a reserved + // instruction exception on R4000 processors. + cpu_exception(EXCEPTION_RI); + break; + + case 0x18: // ERET + if (SR & SR_ERL) + { + logerror("eret from error\n"); + m_branch_state = EXCEPTION; + m_pc = m_cp0[CP0_ErrorEPC]; + SR &= ~SR_ERL; + } + else + { + m_branch_state = EXCEPTION; + m_pc = m_cp0[CP0_EPC]; + SR &= ~SR_EXL; + } + cp0_mode_check(); + + if (m_ll_watch) + { + m_ll_watch->remove(); + m_ll_watch = nullptr; + } + break; + + default: + // Φ Operation codes marked with a phi are invalid but do not + // cause reserved instruction exceptions in R4000 implementations. + break; + } + break; + + default: + // γ Operation codes marked with a gamma cause a reserved + // instruction exception. They are reserved for future versions + // of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } +} + +u64 r4000_base_device::cp0_get(unsigned const reg) +{ + switch (reg) + { + case CP0_Count: + return u32((total_cycles() - m_cp0_timer_zero) / 2); + + case CP0_Random: + { + u8 const wired = m_cp0[CP0_Wired] & 0x3f; + + if (wired < ARRAY_LENGTH(m_tlb)) + return ((total_cycles() - m_cp0_timer_zero) % (ARRAY_LENGTH(m_tlb) - wired) + wired) & 0x3f; + else + return ARRAY_LENGTH(m_tlb) - 1; + } + break; + + default: + return m_cp0[reg]; + } +} + +void r4000_base_device::cp0_set(unsigned const reg, u64 const data) +{ + switch (reg) + { + case CP0_Count: + m_cp0[CP0_Count] = u32(data); + m_cp0_timer_zero = total_cycles() - m_cp0[CP0_Count] * 2; + + cp0_update_timer(); + break; + + case CP0_EntryHi: + m_cp0[CP0_EntryHi] = data & (EH_R | EH_VPN2_64 | EH_ASID); + break; + + case CP0_Compare: + m_cp0[CP0_Compare] = u32(data); + CAUSE &= ~CAUSE_IPEX5; + + cp0_update_timer(true); + break; + + case CP0_Status: + m_cp0[CP0_Status] = u32(data); + + // reevaluate operating mode + cp0_mode_check(); + + // FIXME: software interrupt check + if (CAUSE & SR & SR_IMSW) + m_icount = 0; + + if (data & SR_RE) + fatalerror("unimplemented reverse endian mode enabled (%s)\n", machine().describe_context().c_str()); + break; + + case CP0_Cause: + CAUSE = (CAUSE & ~CAUSE_IPSW) | (data & CAUSE_IPSW); + + // FIXME: software interrupt check + if (CAUSE & SR & SR_IMSW) + m_icount = 0; + break; + + case CP0_PRId: + // read-only register + break; + + case CP0_Config: + m_cp0[CP0_Config] = (m_cp0[CP0_Config] & ~CONFIG_WM) | (data & CONFIG_WM); + + if (m_cp0[CP0_Config] & CONFIG_IB) + { + m_icache_line_size = 32; + m_icache_shift = 5; + m_icache_mask_lo = ~u32(0x1f); + } + else + { + m_icache_line_size = 16; + m_icache_shift = 4; + m_icache_mask_lo = ~u32(0xf); + } + + LOGMASKED(LOG_CACHE, "icache/dcache line sizes %d/%d bytes\n", + m_icache_line_size, m_cp0[CP0_Config] & CONFIG_DB ? 32 : 16); + break; + + default: + m_cp0[reg] = data; + break; + } +} + +void r4000_base_device::cp0_tlbr() +{ + u8 const index = m_cp0[CP0_Index] & 0x3f; + + if (index < ARRAY_LENGTH(m_tlb)) + { + tlb_entry_t const &entry = m_tlb[index]; + + m_cp0[CP0_PageMask] = entry.mask; + m_cp0[CP0_EntryHi] = entry.vpn; + m_cp0[CP0_EntryLo0] = entry.pfn[0]; + m_cp0[CP0_EntryLo1] = entry.pfn[1]; + } +} + +void r4000_base_device::cp0_tlbwi(u8 const index) +{ + if (index < ARRAY_LENGTH(m_tlb)) + { + tlb_entry_t &entry = m_tlb[index]; + + entry.mask = m_cp0[CP0_PageMask]; + entry.vpn = m_cp0[CP0_EntryHi]; + if ((m_cp0[CP0_EntryLo0] & EL_G) && (m_cp0[CP0_EntryLo1] & EL_G)) + entry.vpn |= EH_G; + entry.pfn[0] = m_cp0[CP0_EntryLo0]; + entry.pfn[1] = m_cp0[CP0_EntryLo1]; + + entry.low_bit = 32 - count_leading_zeros((entry.mask >> 1) | 0xfff); + + LOGMASKED(LOG_TLB, "tlb write index %02d mask 0x%016x vpn2 0x%016x %c asid 0x%02x pfn0 0x%016x %c%c pfn1 0x%016x %c%c (%s)\n", + index, entry.mask, + entry.vpn, entry.vpn & EH_G ? 'G' : '-', entry.vpn & EH_ASID, + entry.pfn[0] & EL_PFN, entry.pfn[0] & EL_D ? 'D' : '-', entry.pfn[0] & EL_V ? 'V' : '-', + entry.pfn[1] & EL_PFN, entry.pfn[1] & EL_D ? 'D' : '-', entry.pfn[1] & EL_V ? 'V' : '-', + machine().describe_context()); + } +} + +void r4000_base_device::cp0_tlbwr() +{ + u8 const wired = m_cp0[CP0_Wired] & 0x3f; + u8 const unwired = ARRAY_LENGTH(m_tlb) - wired; + + u8 const index = (unwired > 0) ? ((total_cycles() - m_cp0_timer_zero) % unwired + wired) & 0x3f : (ARRAY_LENGTH(m_tlb) - 1); + + cp0_tlbwi(index); +} + +void r4000_base_device::cp0_tlbp() +{ + m_cp0[CP0_Index] = 0x80000000; + for (u8 index = 0; index < ARRAY_LENGTH(m_tlb); index++) + { + tlb_entry_t const &entry = m_tlb[index]; + + u64 const mask = (m_64 ? EH_R | (EH_VPN2_64 & ~entry.mask) : (EH_VPN2_32 & ~entry.mask)) + | ((entry.vpn & EH_G) ? 0 : EH_ASID); + + if ((entry.vpn & mask) == (m_cp0[CP0_EntryHi] & mask)) + { + m_cp0[CP0_Index] = index; + break; + } + } + + if (m_cp0[CP0_Index] == 0x80000000) + LOGMASKED(LOG_TLB, "tlbp miss 0x%08x\n", m_cp0[CP0_EntryHi]); + else + LOGMASKED(LOG_TLB, "tlbp hit 0x%08x index %02d\n", m_cp0[CP0_EntryHi], m_cp0[CP0_Index]); +} + +void r4000_base_device::cp0_update_timer(bool start) +{ + if (start || m_cp0_timer->enabled()) + { + u32 const count = (total_cycles() - m_cp0_timer_zero) / 2; + u64 const delta = m_cp0[CP0_Compare] - count; + + m_cp0_timer->adjust(cycles_to_attotime(delta * 2)); + } +} + +TIMER_CALLBACK_MEMBER(r4000_base_device::cp0_timer_callback) +{ + set_input_line(5, ASSERT_LINE); +} + +void r4000_base_device::cp0_mode_check() +{ + if (!(m_cp0[CP0_Status] & (SR_EXL | SR_ERL))) + switch (m_cp0[CP0_Status] & SR_KSU) + { + case SR_KSU_K: m_64 = m_cp0[CP0_Status] & SR_KX; break; + case SR_KSU_S: m_64 = m_cp0[CP0_Status] & SR_SX; break; + case SR_KSU_U: m_64 = m_cp0[CP0_Status] & SR_UX; break; + } + else + m_64 = m_cp0[CP0_Status] & SR_KX; +} + +void r4000_base_device::cp1_execute(u32 const op) +{ + if (!(SR & SR_CU1)) + { + cpu_exception(EXCEPTION_CP1); + return; + } + + softfloat_exceptionFlags = 0; + switch (op >> 26) + { + case 0x11: // COP1 + switch ((op >> 21) & 0x1f) + { + case 0x00: // MFC1 + if (SR & SR_FR) + m_r[RTREG] = s64(s32(m_f[RDREG])); + else + if (RDREG & 1) + // move the high half of the even floating point register + m_r[RTREG] = s64(s32(m_f[RDREG & ~1] >> 32)); + else + // move the low half of the even floating point register + m_r[RTREG] = s64(s32(m_f[RDREG & ~1] >> 0)); + break; + case 0x01: // DMFC1 + // TODO: MIPS3 only + if ((SR & SR_FR) || !(RDREG & 1)) + m_r[RTREG] = m_f[RDREG]; + break; + case 0x02: // CFC1 + switch (RDREG) + { + case 0: m_r[RTREG] = m_fcr0; break; + case 31: m_r[RTREG] = m_fcr31; break; + + default: + logerror("cfc1 undefined fpu control register %d (%s)\n", RDREG, machine().describe_context()); + break; + } + break; + case 0x04: // MTC1 + if (SR & SR_FR) + m_f[RDREG] = u32(m_r[RTREG]); + else + if (RDREG & 1) + // load the high half of the even floating point register + m_f[RDREG & ~1] = (m_r[RTREG] << 32) | u32(m_f[RDREG & ~1]); + else + // load the low half of the even floating point register + m_f[RDREG & ~1] = (m_f[RDREG & ~1] & ~0xffffffffULL) | u32(m_r[RTREG]); + break; + case 0x05: // DMTC1 + // TODO: MIPS3 only + if ((SR & SR_FR) || !(RDREG & 1)) + m_f[RDREG] = m_r[RTREG]; + break; + case 0x06: // CTC1 + switch (RDREG) + { + case 0: // register is read-only + break; + + case 31: + m_fcr31 = u32(m_r[RTREG]); + + // update rounding mode + switch (m_fcr31 & FCR31_RM) + { + case 0: softfloat_roundingMode = softfloat_round_near_even; break; + case 1: softfloat_roundingMode = softfloat_round_minMag; break; + case 2: softfloat_roundingMode = softfloat_round_max; break; + case 3: softfloat_roundingMode = softfloat_round_min; break; + } + + // exception check + if ((m_fcr31 & FCR31_CE) || ((m_fcr31 & FCR31_CM) >> 5) & (m_fcr31 & FCR31_EM)) + cpu_exception(EXCEPTION_FPE); + + break; + + default: + logerror("ctc1 undefined fpu control register %d (%s)\n", RDREG, machine().describe_context()); + break; + } + break; + case 0x08: // BC + switch ((op >> 16) & 0x1f) + { + case 0x00: // BC1F + if (!(m_fcr31 & FCR31_C)) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x01: // BC1T + if (m_fcr31 & FCR31_C) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + break; + case 0x02: // BC1FL + if (!(m_fcr31 & FCR31_C)) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + case 0x03: // BC1TL + if (m_fcr31 & FCR31_C) + { + m_branch_state = BRANCH; + m_branch_target = ADDR(m_pc, (s16(op) << 2) + 4); + } + else + m_branch_state = NULLIFY; + break; + + default: + // reserved instructions + cpu_exception(EXCEPTION_RI); + break; + } + break; + + case 0x10: // S + switch (op & 0x3f) + { + case 0x00: // ADD.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_add(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }).v); + break; + case 0x01: // SUB.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_sub(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }).v); + break; + case 0x02: // MUL.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_mul(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }).v); + break; + case 0x03: // DIV.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_div(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }).v); + break; + case 0x04: // SQRT.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_sqrt(float32_t{ u32(m_f[FSREG]) }).v); + break; + case 0x05: // ABS.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + if (f32_lt(float32_t{ u32(m_f[FSREG]) }, float32_t{ 0 })) + cp1_set(FDREG, f32_mul(float32_t{ u32(m_f[FSREG]) }, i32_to_f32(-1)).v); + break; + case 0x06: // MOV.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + m_f[FDREG] = m_f[FSREG]; + break; + case 0x07: // NEG.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_mul(float32_t{ u32(m_f[FSREG]) }, i32_to_f32(-1)).v); + break; + case 0x08: // ROUND.L.S + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i64(float32_t{ u32(m_f[FSREG]) }, softfloat_round_near_even, true)); + break; + case 0x09: // TRUNC.L.S + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i64(float32_t{ u32(m_f[FSREG]) }, softfloat_round_minMag, true)); + break; + case 0x0a: // CEIL.L.S + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i64(float32_t{ u32(m_f[FSREG]) }, softfloat_round_max, true)); + break; + case 0x0b: // FLOOR.L.S + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i64(float32_t{ u32(m_f[FSREG]) }, softfloat_round_min, true)); + break; + case 0x0c: // ROUND.W.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i32(float32_t{ u32(m_f[FSREG]) }, softfloat_round_near_even, true)); + break; + case 0x0d: // TRUNC.W.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i32(float32_t{ u32(m_f[FSREG]) }, softfloat_round_minMag, true)); + break; + case 0x0e: // CEIL.W.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i32(float32_t{ u32(m_f[FSREG]) }, softfloat_round_max, true)); + break; + case 0x0f: // FLOOR.W.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i32(float32_t{ u32(m_f[FSREG]) }, softfloat_round_min, true)); + break; + + case 0x21: // CVT.D.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_f64(float32_t{ u32(m_f[FSREG]) }).v); + break; + case 0x24: // CVT.W.S + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i32(float32_t{ u32(m_f[FSREG]) }, softfloat_roundingMode, true)); + break; + case 0x25: // CVT.L.S + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f32_to_i64(float32_t{ u32(m_f[FSREG]) }, softfloat_roundingMode, true)); + break; + + case 0x30: // C.F.S (false) + if ((SR & SR_FR) || !(op & ODD_REGS)) + m_fcr31 &= ~FCR31_C; + break; + case 0x31: // C.UN.S (unordered) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + // detect unordered + f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }); + if (softfloat_exceptionFlags & softfloat_flag_invalid) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x32: // C.EQ.S (equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x33: // C.UEQ.S (unordered equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x34: // C.OLT.S (less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_lt(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x35: // C.ULT.S (unordered less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_lt(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x36: // C.OLE.S (less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_le(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x37: // C.ULE.S (unordered less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_le(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + + case 0x38: // C.SF.S (signalling false) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + // detect unordered + f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }); + + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x39: // C.NGLE.S (not greater, less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + // detect unordered + f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }); + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_C | FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x3a: // C.SEQ.S (signalling equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3b: // C.NGL.S (not greater or less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_eq(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3c: // C.LT.S (less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_lt(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3d: // C.NGE.S (not greater or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_lt(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3e: // C.LE.S (less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_le(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3f: // C.NGT.S (not greater than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f32_le(float32_t{ u32(m_f[FSREG]) }, float32_t{ u32(m_f[FTREG]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + + default: // unimplemented operations + m_fcr31 |= FCR31_CE; + cpu_exception(EXCEPTION_FPE); + break; + } + break; + case 0x11: // D + switch (op & 0x3f) + { + case 0x00: // ADD.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_add(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }).v); + break; + case 0x01: // SUB.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_sub(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }).v); + break; + case 0x02: // MUL.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_mul(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }).v); + break; + case 0x03: // DIV.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_div(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }).v); + break; + case 0x04: // SQRT.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_sqrt(float64_t{ m_f[FSREG] }).v); + break; + case 0x05: // ABS.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + if (f64_lt(float64_t{ m_f[FSREG] }, float64_t{ 0 })) + cp1_set(FDREG, f64_mul(float64_t{ m_f[FSREG] }, i32_to_f64(-1)).v); + break; + case 0x06: // MOV.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + m_f[FDREG] = m_f[FSREG]; + break; + case 0x07: // NEG.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_mul(float64_t{ m_f[FSREG] }, i32_to_f64(-1)).v); + break; + case 0x08: // ROUND.L.D + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i64(float64_t{ m_f[FSREG] }, softfloat_round_near_even, true)); + break; + case 0x09: // TRUNC.L.D + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i64(float64_t{ m_f[FSREG] }, softfloat_round_minMag, true)); + break; + case 0x0a: // CEIL.L.D + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i64(float64_t{ m_f[FSREG] }, softfloat_round_max, true)); + break; + case 0x0b: // FLOOR.L.D + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i64(float64_t{ m_f[FSREG] }, softfloat_round_min, true)); + break; + case 0x0c: // ROUND.W.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i32(float64_t{ m_f[FSREG] }, softfloat_round_near_even, true)); + break; + case 0x0d: // TRUNC.W.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i32(float64_t{ m_f[FSREG] }, softfloat_round_minMag, true)); + break; + case 0x0e: // CEIL.W.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i32(float64_t{ m_f[FSREG] }, softfloat_round_max, true)); + break; + case 0x0f: // FLOOR.W.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i32(float64_t{ m_f[FSREG] }, softfloat_round_min, true)); + break; + + case 0x20: // CVT.S.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_f32(float64_t{ m_f[FSREG] }).v); + break; + case 0x24: // CVT.W.D + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i32(float64_t{ m_f[FSREG] }, softfloat_roundingMode, true)); + break; + case 0x25: // CVT.L.D + // TODO: MIPS3 only + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, f64_to_i64(float64_t{ m_f[FSREG] }, softfloat_roundingMode, true)); + break; + + case 0x30: // C.F.D (false) + if ((SR & SR_FR) || !(op & ODD_REGS)) + m_fcr31 &= ~FCR31_C; + break; + case 0x31: // C.UN.D (unordered) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + // detect unordered + f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }); + if (softfloat_exceptionFlags & softfloat_flag_invalid) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x32: // C.EQ.D (equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x33: // C.UEQ.D (unordered equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x34: // C.OLT.D (less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_lt(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x35: // C.ULT.D (unordered less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_lt(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x36: // C.OLE.D (less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_le(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x37: // C.ULE.D (unordered less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_le(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + } + break; + + case 0x38: // C.SF.D (signalling false) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + // detect unordered + f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }); + + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x39: // C.NGLE.D (not greater, less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + // detect unordered + f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }); + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_C | FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + else + m_fcr31 &= ~FCR31_C; + } + break; + case 0x3a: // C.SEQ.D (signalling equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3b: // C.NGL.D (not greater or less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_eq(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3c: // C.LT.D (less than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_lt(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3d: // C.NGE.D (not greater or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_lt(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3e: // C.LE.D (less than or equal) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_le(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] })) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + case 0x3f: // C.NGT.D (not greater than) + if ((SR & SR_FR) || !(op & ODD_REGS)) + { + if (f64_le(float64_t{ m_f[FSREG] }, float64_t{ m_f[FTREG] }) || (softfloat_exceptionFlags & softfloat_flag_invalid)) + m_fcr31 |= FCR31_C; + else + m_fcr31 &= ~FCR31_C; + + if (softfloat_exceptionFlags & softfloat_flag_invalid) + { + m_fcr31 |= FCR31_CV; + cpu_exception(EXCEPTION_FPE); + } + } + break; + + default: // unimplemented operations + m_fcr31 |= FCR31_CE; + cpu_exception(EXCEPTION_FPE); + break; + } + break; + case 0x14: // W + switch (op & 0x3f) + { + case 0x20: // CVT.S.W + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, i32_to_f32(s32(m_f[FSREG])).v); + break; + case 0x21: // CVT.D.W + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, i32_to_f64(s32(m_f[FSREG])).v); + break; + + default: // unimplemented operations + m_fcr31 |= FCR31_CE; + cpu_exception(EXCEPTION_FPE); + break; + } + break; + case 0x15: // L + // TODO: MIPS3 only + switch (op & 0x3f) + { + case 0x02a00020: // CVT.S.L + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, i64_to_f32(s64(m_f[FSREG])).v); + break; + case 0x02a00021: // CVT.D.L + if ((SR & SR_FR) || !(op & ODD_REGS)) + cp1_set(FDREG, i64_to_f64(s64(m_f[FSREG])).v); + break; + + default: // unimplemented operations + m_fcr31 |= FCR31_CE; + cpu_exception(EXCEPTION_FPE); + break; + } + break; + + default: // unimplemented operations + m_fcr31 |= FCR31_CE; + cpu_exception(EXCEPTION_FPE); + break; + } + break; + + case 0x31: // LWC1 + load(ADDR(m_r[RSREG], op), + [this, op](u32 data) + { + if (SR & SR_FR) + m_f[RTREG] = data; + else + if (RTREG & 1) + // load the high half of the even floating point register + m_f[RTREG & ~1] = (u64(data) << 32) | u32(m_f[RTREG & ~1]); + else + // load the low half of the even floating point register + m_f[RTREG & ~1] = (m_f[RTREG & ~1] & ~0xffffffffULL) | data; + }); + break; + + case 0x35: // LDC1 + load(ADDR(m_r[RSREG], op), + [this, op](u64 data) + { + if ((SR & SR_FR) || !(RTREG & 1)) + m_f[RTREG] = data; + }); + break; + + case 0x39: // SWC1 + if (SR & SR_FR) + store(ADDR(m_r[RSREG], op), u32(m_f[RTREG])); + else + if (RTREG & 1) + // store the high half of the even floating point register + store(ADDR(m_r[RSREG], op), u32(m_f[RTREG & ~1] >> 32)); + else + // store the low half of the even floating point register + store(ADDR(m_r[RSREG], op), u32(m_f[RTREG & ~1])); + break; + + case 0x3d: // SDC1 + if ((SR & SR_FR) || !(RTREG & 1)) + store(ADDR(m_r[RSREG], op), m_f[RTREG]); + break; + } +} + +void r4000_base_device::cp1_set(unsigned const reg, u64 const data) +{ + // translate softfloat exception flags to cause register + if (softfloat_exceptionFlags) + { + if (softfloat_exceptionFlags & softfloat_flag_inexact) + m_fcr31 |= FCR31_CI; + if (softfloat_exceptionFlags & softfloat_flag_underflow) + m_fcr31 |= FCR31_CU; + if (softfloat_exceptionFlags & softfloat_flag_overflow) + m_fcr31 |= FCR31_CO; + if (softfloat_exceptionFlags & softfloat_flag_infinite) + m_fcr31 |= FCR31_CZ; + if (softfloat_exceptionFlags & softfloat_flag_invalid) + m_fcr31 |= FCR31_CV; + + // check if exception is enabled + if (((m_fcr31 & FCR31_CM) >> 5) & (m_fcr31 & FCR31_EM)) + { + cpu_exception(EXCEPTION_FPE); + return; + } + + // set flags + m_fcr31 |= ((m_fcr31 & FCR31_CM) >> 10); + } + + m_f[reg] = data; +} + +void r4000_base_device::cp2_execute(u32 const op) +{ + if (!(SR & SR_CU2)) + { + cpu_exception(EXCEPTION_CP2); + return; + } + + switch (op >> 26) + { + case 0x12: // COP2 + switch ((op >> 21) & 0x1f) + { + case 0x00: // MFC2 + logerror("mfc2 unimplemented (%s)\n", machine().describe_context()); + break; + case 0x01: // DMFC2 + // ε Operation codes marked with epsilon are valid when the + // processor is operating either in the Kernel mode or in the + // 64-bit non-Kernel (User or Supervisor) mode. These instructions + // cause a reserved instruction exception if 64-bit operation is + // not enabled in User or Supervisor mode. + if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || m_64) + logerror("dmfc2 unimplemented (%s)\n", machine().describe_context()); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x02: // CFC2 + logerror("cfc2 unimplemented (%s)\n", machine().describe_context()); + break; + + case 0x04: // MTC2 + logerror("mtc2 unimplemented (%s)\n", machine().describe_context()); + break; + case 0x05: // DMTC2 + // ε Operation codes marked with epsilon are valid when the + // processor is operating either in the Kernel mode or in the + // 64-bit non-Kernel (User or Supervisor) mode. These instructions + // cause a reserved instruction exception if 64-bit operation is + // not enabled in User or Supervisor mode. + if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || m_64) + logerror("dmtc2 unimplemented (%s)\n", machine().describe_context()); + else + cpu_exception(EXCEPTION_RI); + break; + case 0x06: // CTC2 + logerror("ctc2 unimplemented (%s)\n", machine().describe_context()); + break; + + case 0x08: // BC2 + switch ((op >> 16) & 0x1f) + { + case 0x00: // BC2F + case 0x01: // BC2F + case 0x02: // BC2FL + case 0x03: // BC2TL + logerror("bc2 unimplemented (%s)\n", machine().describe_context()); + break; + + default: + // γ Operation codes marked with a gamma cause a reserved + // instruction exception. They are reserved for future versions + // of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } + break; + + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: + // CP2 function + logerror("function unimplemented (%s)\n", machine().describe_context()); + break; + + default: + // γ Operation codes marked with a gamma cause a reserved + // instruction exception. They are reserved for future versions + // of the architecture. + cpu_exception(EXCEPTION_RI); + break; + } + break; + + case 0x32: // LWC2 + logerror("lwc2 unimplemented (%s)\n", machine().describe_context()); + break; + + case 0x36: // LDC2 + logerror("ldc2 unimplemented (%s)\n", machine().describe_context()); + break; + + case 0x3a: // SWC2 + logerror("swc2 unimplemented (%s)\n", machine().describe_context()); + break; + + case 0x3e: // SDC2 + logerror("sdc2 unimplemented (%s)\n", machine().describe_context()); + break; + } +} + +r4000_base_device::translate_t r4000_base_device::translate(int intention, u64 &address) +{ + /* + * Decode the program address into one of the following ranges depending on + * the active status register bits. + * + * 32-bit modes + * user: 0x0000'0000-0x7fff'ffff (useg, mapped) + * + * super: 0x0000'0000-0x7fff'ffff (suseg, mapped) + * 0xc000'0000-0xdfff'ffff (ssseg, mapped) + * + * kernel: 0x0000'0000-0x7fff'ffff (kuseg, mapped) + * 0x8000'0000-0x9fff'ffff (kseg0, unmapped, cached) + * 0xa000'0000-0xbfff'ffff (kseg1, unmapped, uncached) + * 0xc000'0000-0xdfff'ffff (ksseg, mapped) + * 0xe000'0000-0xffff'ffff (kseg3, mapped) + * + * 64-bit modes + * user: 0x0000'0000'0000'0000-0x0000'00ff'ffff'ffff (xuseg, mapped) + * + * super: 0x0000'0000'0000'0000-0x0000'00ff'ffff'ffff (xsuseg, mapped) + * 0x4000'0000'0000'0000-0x4000'00ff'ffff'ffff (xsseg, mapped) + * 0xffff'ffff'c000'0000-0xffff'ffff'dfff'ffff (csseg, mapped) + * + * kernel: 0x0000'0000'0000'0000-0x0000'00ff'ffff'ffff (xkuseg, mapped) + * 0x4000'0000'0000'0000-0x4000'00ff'ffff'ffff (xksseg, mapped) + * 0x8000'0000'0000'0000-0xbfff'ffff'ffff'ffff (xkphys, unmapped) + * 0xc000'0000'0000'0000-0xc000'00ff'7fff'ffff (xkseg, mapped) + * 0xffff'ffff'8000'0000-0xffff'ffff'9fff'ffff (ckseg0, unmapped, cached) + * 0xffff'ffff'a000'0000-0xffff'ffff'bfff'ffff (ckseg1, unmapped, uncached) + * 0xffff'ffff'c000'0000-0xffff'ffff'dfff'ffff (cksseg, mapped) + * 0xffff'ffff'e000'0000-0xffff'ffff'ffff'ffff (ckseg3, mapped) + */ + + bool extended = false; + + if (!(SR & SR_KSU) || (SR & SR_EXL) || (SR & SR_ERL)) + { + // kernel mode + if (SR & SR_KX) + { + // 64-bit kernel mode + if (address & 0xffff'ff00'0000'0000) + if ((address & 0xffff'ff00'0000'0000) == 0x4000'0000'0000'0000) + extended = true; // xksseg + else + if ((address & 0xc000'0000'0000'0000) == 0x8000'0000'0000'0000) + { + address &= 0x0000'000f'ffff'ffff; // xkphys + + // FIXME: caching depends on top three bits + return CACHED; + } + else + if ((address & 0xffff'ff00'0000'0000) == 0xc000'0000'0000'0000) + if ((address & 0x0000'00ff'8000'0000) == 0x0000'00ff'8000'0000) + return ERROR; // exception + else + extended = true; // xkseg + else + // FIXME: ckseg0 caching depends on config regiter + switch (address & 0xffff'ffff'e000'0000) + { + case 0xffff'ffff'8000'0000: address &= 0x7fff'ffff; return CACHED; // ckseg0 + case 0xffff'ffff'a000'0000: address &= 0x1fff'ffff; return UNCACHED; // ckseg1 + case 0xffff'ffff'c000'0000: extended = true; break; // cksseg + case 0xffff'ffff'e000'0000: extended = true; break; // ckseg3 + default: return ERROR; // exception + } + else + if (SR & SR_ERL) + // FIXME: documentation says 2^31, but assume it should be 2^40 + return UNCACHED; // xkuseg (unmapped, uncached) + else + extended = true; // xkuseg + } + else + { + // 32-bit kernel mode + if (address & 0xffff'ffff'8000'0000) + switch (address & 0xffff'ffff'e000'0000) + { + case 0xffff'ffff'8000'0000: address &= 0x7fff'ffff; return CACHED; // kseg0 + case 0xffff'ffff'a000'0000: address &= 0x1fff'ffff; return UNCACHED; // kseg1 + case 0xffff'ffff'c000'0000: extended = false; break; // ksseg + case 0xffff'ffff'e000'0000: extended = false; break; // kseg3 + default: return ERROR; // exception + } + else + if (SR & SR_ERL) + return UNCACHED; // kuseg (unmapped, uncached) + else + extended = false; // kuseg + } + } + else if ((SR & SR_KSU) == SR_KSU_S) + { + // supervisor mode + if (SR & SR_SX) + { + // 64-bit supervisor mode + if (address & 0xffff'ff00'0000'0000) + if ((address & 0xffff'ff00'0000'0000) == 0x4000'0000'0000'0000) + extended = true; // xsseg + else + if ((address & 0xffff'ffff'e000'0000) == 0xffff'ffff'c000'0000) + extended = true; // csseg + else + return ERROR; // exception + else + extended = true; // xsuseg + } + else + { + // 32-bit supervisor mode + if (address & 0xffff'ffff'8000'0000) + if ((address & 0xffff'ffff'e000'0000) == 0xffff'ffff'c000'0000) + extended = false; // sseg + else + return ERROR; // exception + else + extended = false; // suseg + } + } + else + { + // user mode + if (SR & SR_UX) + { + // 64-bit user mode + if (address & 0xffff'ff00'0000'0000) + return ERROR; // exception + else + extended = true; // xuseg + } + else + { + // 32-bit user mode + if (address & 0xffff'ffff'8000'0000) + return ERROR; // exception + else + extended = false; // useg + } + } + + // address needs translation, using a combination of VPN2 and ASID + u64 const key = (address & (extended ? (EH_R | EH_VPN2_64) : EH_VPN2_32)) | (m_cp0[CP0_EntryHi] & EH_ASID); + + bool invalid = false; + bool modify = false; + for (unsigned i = 0; i < ARRAY_LENGTH(m_tlb); i++) + { + unsigned const index = (m_last[intention & TRANSLATE_TYPE_MASK] + i) % ARRAY_LENGTH(m_tlb); + tlb_entry_t const &entry = m_tlb[index]; + + // test vpn and asid + u64 const mask = (extended ? EH_R | (EH_VPN2_64 & ~entry.mask) : (EH_VPN2_32 & ~entry.mask)) + | ((entry.vpn & EH_G) ? 0 : EH_ASID); + + if ((entry.vpn & mask) != (key & mask)) + continue; + + u64 const pfn = entry.pfn[BIT(address, entry.low_bit)]; + + // test valid + if (!(pfn & EL_V)) + { + invalid = true; + break; + } + + // test dirty + if ((intention & TRANSLATE_WRITE) && !(pfn & EL_D)) + { + modify = true; + break; + } + + // translate the address + address &= (entry.mask >> 1) | 0xfff; + address |= ((pfn & EL_PFN) << 6) & ~(entry.mask >> 1); + + // remember the last-used tlb entry + m_last[intention & TRANSLATE_TYPE_MASK] = index; + + return (pfn & EL_C) == C_2 ? UNCACHED : CACHED; + } + + // tlb miss, invalid entry, or a store to a non-dirty entry + if (!machine().side_effects_disabled() && !(intention & TRANSLATE_DEBUG_MASK)) + { + if (VERBOSE & LOG_TLB) + { + char const mode[] = { 'r', 'w', 'x' }; + + if (modify) + LOGMASKED(LOG_TLB, "tlb modify asid %d address 0x%016x (%s)\n", + m_cp0[CP0_EntryHi] & EH_ASID, address, machine().describe_context()); + else + LOGMASKED(LOG_TLB, "tlb miss %c asid %d address 0x%016x (%s)\n", + mode[intention & TRANSLATE_TYPE_MASK], m_cp0[CP0_EntryHi] & EH_ASID, address, machine().describe_context()); + } + + // load tlb exception registers + m_cp0[CP0_BadVAddr] = address; + m_cp0[CP0_EntryHi] = key; + m_cp0[CP0_Context] = (m_cp0[CP0_Context] & CONTEXT_PTEBASE) | ((address >> 9) & CONTEXT_BADVPN2); + m_cp0[CP0_XContext] = (m_cp0[CP0_XContext] & XCONTEXT_PTEBASE) | ((address >> 31) & XCONTEXT_R) | ((address >> 9) & XCONTEXT_BADVPN2); + + if (invalid || modify || (SR & SR_EXL)) + cpu_exception(modify ? EXCEPTION_MOD : (intention & TRANSLATE_WRITE) ? EXCEPTION_TLBS : EXCEPTION_TLBL); + else + cpu_exception((intention & TRANSLATE_WRITE) ? EXCEPTION_TLBS : EXCEPTION_TLBL, extended ? 0x000 : 0x080); + } + + return MISS; +} + +void r4000_base_device::address_error(int intention, u64 const address) +{ + if (!machine().side_effects_disabled() && !(intention & TRANSLATE_DEBUG_MASK)) + { + logerror("address_error 0x%016x (%s)\n", address, machine().describe_context()); + + // TODO: check this + if (!(SR & SR_EXL)) + m_cp0[CP0_BadVAddr] = address; + + cpu_exception((intention & TRANSLATE_WRITE) ? EXCEPTION_ADES : EXCEPTION_ADEL); + } +} + +template std::enable_if_t>::value, bool> r4000_base_device::load(u64 address, U &&apply) +{ + // alignment error + if (address & (sizeof(T) - 1)) + { + address_error(TRANSLATE_READ, address); + return false; + } + + translate_t const t = translate(TRANSLATE_READ, address); + + // address error + if (t == ERROR) + { + address_error(TRANSLATE_READ, address); + + return false; + } + + // tlb miss + if (t == MISS) + return false; + + // watchpoint + if ((m_cp0[CP0_WatchLo] & WATCHLO_R) && !(SR & SR_EXL)) + { + u64 const watch_address = ((m_cp0[CP0_WatchHi] & WATCHHI_PADDR1) << 32) | (m_cp0[CP0_WatchLo] & WATCHLO_PADDR0); + + if ((address & ~7) == watch_address) + { + cpu_exception(EXCEPTION_WATCH); + return false; + } + } + + // TODO: cache lookup + + switch (sizeof(T)) + { + case 1: apply(T(space(0).read_byte(address))); break; + case 2: apply(T(space(0).read_word(address))); break; + case 4: apply(T(space(0).read_dword(address))); break; + case 8: apply(T(space(0).read_qword(address))); break; + } + + return true; +} + +template std::enable_if_t>::value, bool> r4000_base_device::load_linked(u64 address, U &&apply) +{ + // alignment error + if (address & (sizeof(T) - 1)) + { + address_error(TRANSLATE_READ, address); + return false; + } + + translate_t const t = translate(TRANSLATE_READ, address); + + // address error + if (t == ERROR) + { + address_error(TRANSLATE_READ, address); + return false; + } + + // tlb miss + if (t == MISS) + return false; + + // watchpoint + if ((m_cp0[CP0_WatchLo] & WATCHLO_R) && !(SR & SR_EXL)) + { + u64 const watch_address = ((m_cp0[CP0_WatchHi] & WATCHHI_PADDR1) << 32) | (m_cp0[CP0_WatchLo] & WATCHLO_PADDR0); + + if ((address & ~7) == watch_address) + { + cpu_exception(EXCEPTION_WATCH); + return false; + } + } + + // TODO: cache lookup + + switch (sizeof(T)) + { + case 4: apply(address, T(space(0).read_dword(address))); break; + case 8: apply(address, T(space(0).read_qword(address))); break; + } + + return true; +} + +template std::enable_if_t::value, bool> r4000_base_device::store(u64 address, U data, T mem_mask) +{ + // alignment error + if (address & (sizeof(T) - 1)) + { + address_error(TRANSLATE_READ, address); + return false; + } + + translate_t const t = translate(TRANSLATE_WRITE, address); + + // address error + if (t == ERROR) + { + address_error(TRANSLATE_WRITE, address); + return false; + } + + // tlb miss + if (t == MISS) + return false; + + // watchpoint + if ((m_cp0[CP0_WatchLo] & WATCHLO_W) && !(SR & SR_EXL)) + { + u64 const watch_address = ((m_cp0[CP0_WatchHi] & WATCHHI_PADDR1) << 32) | (m_cp0[CP0_WatchLo] & WATCHLO_PADDR0); + + if ((address & ~7) == watch_address) + { + cpu_exception(EXCEPTION_WATCH); + return false; + } + } + + // TODO: cache lookup + + switch (sizeof(T)) + { + case 1: space(0).write_byte(address, T(data)); break; + case 2: space(0).write_word(address, T(data), mem_mask); break; + case 4: space(0).write_dword(address, T(data), mem_mask); break; + case 8: space(0).write_qword(address, T(data), mem_mask); break; + } + + return true; +} + +bool r4000_base_device::fetch(u64 address, std::function &&apply) +{ + u64 const program_address = address; + + // alignment error + if (address & 3) + { + address_error(TRANSLATE_FETCH, address); + + return false; + } + + translate_t const t = translate(TRANSLATE_FETCH, address); + + // address error + if (t == ERROR) + { + address_error(TRANSLATE_FETCH, address); + + return false; + } + + // tlb miss + if (t == MISS) + return false; + + if (ICACHE) + { + if (t == UNCACHED) + { + apply(space(0).read_dword(address)); + + return true; + } + + // look up the tag + u32 const cache_address = (program_address & m_icache_mask_hi); + u32 &tag = m_icache_tag[cache_address >> m_icache_shift]; + + // check for cache miss + if (!(tag & ICACHE_V) || (tag & ICACHE_PTAG) != (address >> 12)) + { + // cache miss + m_icache_miss++; + + // reload the cache line + tag = ICACHE_V | (address >> 12); + for (unsigned i = 0; i < m_icache_line_size; i += 8) + { + u64 const data = space(0).read_qword((address & m_icache_mask_lo) | i); + + m_icache_data[(((cache_address & m_icache_mask_lo) | i) >> 2) + 0] = u32(data); + m_icache_data[(((cache_address & m_icache_mask_lo) | i) >> 2) + 1] = data >> 32; + } + } + else + m_icache_hit++; + + // apply the result + apply(m_icache_data[cache_address >> 2]); + } + else + apply(space(0).read_dword(address)); + + return true; +} + +std::string r4000_base_device::debug_string(u64 string_pointer, unsigned limit) +{ + auto const suppressor(machine().disable_side_effects()); + + bool done = false; + bool mapped = false; + std::string result(""); + + while (!done) + { + done = true; + load(string_pointer++, [limit, &done, &mapped, &result](u8 byte) + { + mapped = true; + if (byte != 0) + { + result += byte; + + done = result.length() == limit; + } + }); + } + + if (!mapped) + result.assign("[unmapped]"); + + return result; +} + +std::string r4000_base_device::debug_string_array(u64 array_pointer) +{ + auto const suppressor(machine().disable_side_effects()); + + bool done = false; + std::string result(""); + + while (!done) + { + done = true; + load(array_pointer, [this, &done, &result](u64 string_pointer) + { + if (string_pointer != 0) + { + if (!result.empty()) + result += ", "; + + result += '\"' + debug_string(string_pointer) + '\"'; + + done = false; + } + }); + + array_pointer += 4; + } + + return result; +} + +std::string r4000_base_device::debug_unicode_string(u64 unicode_string_pointer) +{ + auto const suppressor(machine().disable_side_effects()); + + std::wstring result(L""); + + if (!load(unicode_string_pointer, + [this, unicode_string_pointer, &result](u16 const length) + { + if (length) + if (!load(unicode_string_pointer + 4, + [this, length, &result](s32 buffer) + { + for (int i = 0; i < length; i += 2) + load(buffer + i, [&result](wchar_t const character) { result += character; }); + })) + result.assign(L"[unmapped]"); + })) + result.assign(L"[unmapped]"); + + return utf8_from_wstring(result); +} diff --git a/src/devices/cpu/mips/r4000.h b/src/devices/cpu/mips/r4000.h new file mode 100644 index 00000000000..d067d305a5f --- /dev/null +++ b/src/devices/cpu/mips/r4000.h @@ -0,0 +1,460 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +#ifndef MAME_CPU_MIPS_R4000_H +#define MAME_CPU_MIPS_R4000_H + +#pragma once + +DECLARE_DEVICE_TYPE(R4000, r4000_device) +DECLARE_DEVICE_TYPE(R4400, r4400_device) +DECLARE_DEVICE_TYPE(R4600, r4600_device) + +class r4000_base_device : public cpu_device +{ +public: + enum config_mask : u32 + { + CONFIG_K0 = 0x00000007, // kseg0 cache coherency + CONFIG_CU = 0x00000080, // store conditional cache coherent + CONFIG_DB = 0x00000010, // primary d-cache line 32 bytes + CONFIG_IB = 0x00000020, // primary i-cache line 32 bytes + CONFIG_DC = 0x000001c0, // primary d-cache size + CONFIG_IC = 0x00000e00, // primary i-cache size + CONFIG_EB = 0x00002000, // sub-block ordering + CONFIG_EM = 0x00004000, // parity mode enable + CONFIG_BE = 0x00008000, // big endian + CONFIG_SM = 0x00010000, // dirty shared disable + CONFIG_SC = 0x00020000, // secondary cache absent + CONFIG_EW = 0x000c0000, // system port width + CONFIG_SW = 0x00100000, // secondary cache port width + CONFIG_SS = 0x00200000, // split secondary cache mode + CONFIG_SB = 0x00c00000, // secondary cache line size + CONFIG_EP = 0x0f000000, // transmit data pattern + CONFIG_EC = 0x70000000, // system clock ratio + CONFIG_CM = 0x80000000, // master/checker enable + + CONFIG_WM = 0x0000003f, // runtime-writable bits + }; + void set_config(u32 data, u32 mem_mask = ~u32(0)) + { + if (!configured()) + COMBINE_DATA(&m_cp0[CP0_Config]); + } + +protected: + enum cache_size_t + { + CACHE_4K = 0, + CACHE_8K = 1, + CACHE_16K = 2, + CACHE_32K = 3, + CACHE_64K = 4, + CACHE_128K = 5, + CACHE_256K = 6, + CACHE_512K = 7, + }; + r4000_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, u32 prid, cache_size_t icache_size, cache_size_t dcache_size); + + enum cp0_reg : int + { + CP0_Index = 0, + CP0_Random = 1, + CP0_EntryLo0 = 2, + CP0_EntryLo1 = 3, + CP0_Context = 4, + CP0_PageMask = 5, + CP0_Wired = 6, + CP0_BadVAddr = 8, + CP0_Count = 9, + CP0_EntryHi = 10, + CP0_Compare = 11, + CP0_Status = 12, + CP0_Cause = 13, + CP0_EPC = 14, + CP0_PRId = 15, + CP0_Config = 16, + CP0_LLAddr = 17, + CP0_WatchLo = 18, + CP0_WatchHi = 19, + CP0_XContext = 20, + CP0_ECC = 26, + CP0_CacheErr = 27, + CP0_TagLo = 28, + CP0_TagHi = 29, + CP0_ErrorEPC = 30, + }; + + enum cp0_sr_mask : u32 + { + SR_IE = 0x00000001, // interrupt enable + SR_EXL = 0x00000002, // exception level + SR_ERL = 0x00000004, // error level + SR_KSU = 0x00000018, // kernel/supervisor/user mode + SR_UX = 0x00000020, // 64-bit addressing user mode + SR_SX = 0x00000040, // 64-bit addressing supervisor mode + SR_KX = 0x00000080, // 64-bit addressing kernel mode + SR_IMSW0 = 0x00000100, // software interrupt 0 enable + SR_IMSW1 = 0x00000200, // software interrupt 1 enable + SR_IMEX0 = 0x00000400, // external interrupt 0 enable + SR_IMEX1 = 0x00000800, // external interrupt 1 enable + SR_IMEX2 = 0x00001000, // external interrupt 2 enable + SR_IMEX3 = 0x00002000, // external interrupt 3 enable + SR_IMEX4 = 0x00004000, // external interrupt 4 enable + SR_IMEX5 = 0x00008000, // external interrupt 5 enable + SR_DE = 0x00010000, // disable cache parity/ecc exceptions + SR_CE = 0x00020000, // cache ecc check enable + SR_CH = 0x00040000, // cache hit + SR_SR = 0x00100000, // soft reset + SR_TS = 0x00200000, // tlb shutdown (read only) + SR_BEV = 0x00400000, // bootstrap exception vectors + SR_RE = 0x02000000, // reverse endian + SR_FR = 0x04000000, // enable additional floating-point registers + SU_RP = 0x08000000, // reduced power + SR_CU0 = 0x10000000, // coprocessor usable 0 + SR_CU1 = 0x20000000, // coprocessor usable 1 + SR_CU2 = 0x40000000, // coprocessor usable 2 + SR_CU3 = 0x80000000, // coprocessor usable 3 + + SR_IMSW = 0x00000300, + SR_IM = 0x0000ff00, // interrupt mask + SR_DS = 0x01ff0000, // diagnostic status + }; + + enum cp0_sr_ksu_mode : u32 + { + SR_KSU_K = 0x00000000, // kernel mode + SR_KSU_S = 0x00000008, // supervisor mode + SR_KSU_U = 0x00000010, // user mode + }; + + enum cp0_cause_mask : u32 + { + CAUSE_EXCCODE = 0x0000007c, // exception code + CAUSE_IPSW0 = 0x00000100, // software interrupt 0 pending + CAUSE_IPSW1 = 0x00000200, // software interrupt 1 pending + CAUSE_IPEX0 = 0x00000400, // external interrupt 0 pending + CAUSE_IPEX1 = 0x00000800, // external interrupt 1 pending + CAUSE_IPEX2 = 0x00001000, // external interrupt 2 pending + CAUSE_IPEX3 = 0x00002000, // external interrupt 3 pending + CAUSE_IPEX4 = 0x00004000, // external interrupt 4 pending + CAUSE_IPEX5 = 0x00008000, // external interrupt 5 pending + CAUSE_CE = 0x30000000, // coprocessor unit + CAUSE_BD = 0x80000000, // branch delay slot + + CAUSE_IPSW = 0x00000300, + CAUSE_IPHW = 0x0000fc00, + CAUSE_IP = 0x0000ff00, + }; + + enum cp0_watchlo_mask : u32 + { + WATCHLO_W = 0x00000001, // trap on store + WATCHLO_R = 0x00000002, // trap on load + WATCHLO_PADDR0 = 0xfffffff8, // physical address bits 31:3 + }; + enum cp0_watchhi_mask : u32 + { + WATCHHI_PADDR1 = 0x0000000f, // physical address bits 35:32 + }; + + enum cp0_tlb_mask : u64 + { + TLB_MASK = 0x0000'0000'01ff'e000, + }; + enum cp0_tlb_eh : u64 + { + EH_ASID = 0x0000'0000'0000'00ff, // address space id + EH_G = 0x0000'0000'0000'1000, // global (tlb only) + EH_VPN2_32 = 0x0000'0000'ffff'e000, // virtual page number (32-bit mode) + EH_VPN2_64 = 0x0000'00ff'ffff'e000, // virtual page number (64-bit mode) + EH_R = 0xc000'0000'0000'0000, // region (64-bit mode) + }; + enum cp0_tlb_el : u64 + { + EL_G = 0x0000'0000'0000'0001, // global (entrylo only) + EL_V = 0x0000'0000'0000'0002, // valid + EL_D = 0x0000'0000'0000'0004, // dirty + EL_C = 0x0000'0000'0000'0038, // coherency + EL_PFN = 0x0000'0000'3fff'ffc0, // page frame number + }; + enum cp0_tlb_el_c : u64 + { + C_0 = 0x00, // reserved + C_1 = 0x08, // reserved + C_2 = 0x10, // uncached + C_3 = 0x18, // cacheable noncoherent (noncoherent) + C_4 = 0x20, // cacheable coherent exclusive (exclusive) + C_5 = 0x28, // cacheable coherent exclusive on write (sharable) + C_6 = 0x30, // cacheable coherent update on write (update) + C_7 = 0x38, // reserved + }; + + enum cp0_context_mask : u64 + { + CONTEXT_PTEBASE = 0xffff'ffff'ff80'0000, + CONTEXT_BADVPN2 = 0x0000'0000'007f'fff0, + }; + + enum cp0_xcontext_mask : u64 + { + XCONTEXT_PTEBASE = 0xffff'fffe'0000'0000, // page table entry base + XCONTEXT_R = 0x0000'0001'8000'0000, // region + XCONTEXT_BADVPN2 = 0x0000'0000'7fff'fff0, // bad virtual page number / 2 + }; + + enum cp0_pagemask_mask : u32 + { + PAGEMASK = 0x01ff'e000, + }; + + enum exception_mask : u32 + { + EXCEPTION_INT = 0x00000000, // interrupt + EXCEPTION_MOD = 0x00000004, // tlb modification + EXCEPTION_TLBL = 0x00000008, // tlb load + EXCEPTION_TLBS = 0x0000000c, // tlb store + EXCEPTION_ADEL = 0x00000010, // address error load + EXCEPTION_ADES = 0x00000014, // address error store + EXCEPTION_IBE = 0x00000018, // bus error (instruction fetch) + EXCEPTION_DBE = 0x0000001c, // bus error (data reference: load or store) + EXCEPTION_SYS = 0x00000020, // syscall + EXCEPTION_BP = 0x00000024, // breakpoint + EXCEPTION_RI = 0x00000028, // reserved instruction + EXCEPTION_CPU = 0x0000002c, // coprocessor unusable + EXCEPTION_OV = 0x00000030, // arithmetic overflow + EXCEPTION_TR = 0x00000034, // trap + EXCEPTION_VCEI = 0x00000038, // virtual coherency exception instruction + EXCEPTION_FPE = 0x0000003c, // floating point + EXCEPTION_WATCH = 0x0000005c, // reference to watchhi/watchlo address + EXCEPTION_VCED = 0x0000007c, // virtual coherency exception data + + EXCEPTION_CP0 = 0x0000002c, // coprocessor 0 unusable + EXCEPTION_CP1 = 0x1000002c, // coprocessor 1 unusable + EXCEPTION_CP2 = 0x2000002c, // coprocessor 2 unusable + EXCEPTION_CP3 = 0x3000002c, // coprocessor 3 unusable + }; + + enum cp1_fcr31_mask : u32 + { + FCR31_RM = 0x00000003, // rounding mode + + FCR31_FI = 0x00000004, // inexact operation flag + FCR31_FU = 0x00000008, // underflow flag + FCR31_FO = 0x00000010, // overflow flag + FCR31_FZ = 0x00000020, // divide by zero flag + FCR31_FV = 0x00000040, // invalid operation flag + + FCR31_EI = 0x00000080, // inexact operation enable + FCR31_EU = 0x00000100, // underflow enable + FCR31_EO = 0x00000200, // overflow enable + FCR31_EZ = 0x00000400, // divide by zero enable + FCR31_EV = 0x00000800, // invalid operation enable + + FCR31_CI = 0x00001000, // inexact operation cause + FCR31_CU = 0x00002000, // underflow cause + FCR31_CO = 0x00004000, // overflow cause + FCR31_CZ = 0x00008000, // divide by zero cause + FCR31_CV = 0x00010000, // invalid operation cause + FCR31_CE = 0x00020000, // unimplemented operation cause + + FCR31_C = 0x00800000, // condition + FCR31_FS = 0x01000000, // flush denormalized results + + FCR31_FM = 0x0000007c, // flag mask + FCR31_EM = 0x00000f80, // enable mask + FCR31_CM = 0x0001f000, // cause mask (except unimplemented) + }; + + enum mips3_registers : unsigned + { + MIPS3_R0 = 0, + MIPS3_CP0 = 32, + MIPS3_F0 = 64, + + MIPS3_PC = 96, + MIPS3_HI, + MIPS3_LO, + MIPS3_FCR31, + }; + + enum cp0_taglo_mask : u32 + { + TAGLO_PTAGLO = 0xffffff00, // physical adddress bits 35:12 + TAGLO_PSTATE = 0x000000c0, // primary cache state + TAGLO_P = 0x00000001, // primary tag even parity + }; + enum icache_mask : u32 + { + ICACHE_PTAG = 0x00ffffff, // physical tag + ICACHE_V = 0x01000000, // valid + ICACHE_P = 0x02000000, // even parity + }; + enum dcache_mask : u32 + { + DCACHE_PTAG = 0x00ffffff, // physical tag + DCACHE_CS = 0x01000000, // primary cache state + DCACHE_P = 0x02000000, // even parity for ptag and cs + DCACHE_W = 0x02000000, // write-back + DCACHE_WP = 0x02000000, // even parity for write-back + }; + + // device_t overrides + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_stop() override; + + // device_memory_interface overrides + virtual space_config_vector memory_space_config() const override; + virtual bool memory_translate(int spacenum, int intention, offs_t &address) override; + + // device_disasm_interface overrides + virtual std::unique_ptr create_disassembler() override; + + // device_execute_interface overrides + virtual u32 execute_min_cycles() const override { return 1; } + virtual u32 execute_max_cycles() const override { return 40; } + virtual u32 execute_input_lines() const override { return 6; } + virtual void execute_run() override; + virtual void execute_set_input(int inputnum, int state) override; + + // cpu implementation + void cpu_execute(u32 const op); + void cpu_exception(u32 exception, u16 const vector = 0x180); + void cpu_lwl(u32 const op); + void cpu_lwr(u32 const op); + void cpu_ldl(u32 const op); + void cpu_ldr(u32 const op); + void cpu_swl(u32 const op); + void cpu_swr(u32 const op); + void cpu_sdl(u32 const op); + void cpu_sdr(u32 const op); + + // cp0 implementation + void cp0_execute(u32 const op); + u64 cp0_get(unsigned const reg); + void cp0_set(unsigned const reg, u64 const data); + void cp0_tlbr(); + void cp0_tlbwi(u8 const index); + void cp0_tlbwr(); + void cp0_tlbp(); + + // cp0 helpers + TIMER_CALLBACK_MEMBER(cp0_timer_callback); + void cp0_update_timer(bool start = false); + void cp0_mode_check(); + + // cp1 implementation + void cp1_execute(u32 const op); + void cp1_set(unsigned const reg, u64 const data); + + // cp2 implementation + void cp2_execute(u32 const op); + + // address and memory handling + enum translate_t { ERROR, MISS, UNCACHED, CACHED }; + translate_t translate(int intention, u64 &address); + void address_error(int intention, u64 const address); + + template std::enable_if_t>::value, bool> load(u64 program_address, U &&apply); + template std::enable_if_t>::value, bool> load_linked(u64 program_address, U &&apply); + template std::enable_if_t::value, bool> store(u64 program_address, U data, T mem_mask = ~T(0)); + bool fetch(u64 address, std::function &&apply); + + // debugging helpers + std::string debug_string(u64 string_pointer, unsigned limit = 0); + std::string debug_string_array(u64 array_pointer); + std::string debug_unicode_string(u64 unicode_string_pointer); + + // device configuration state + address_space_config m_program_config_le; + address_space_config m_program_config_be; + + // runtime state + int m_icount; + + // cpu state + u64 m_pc; + u64 m_r[32]; + u64 m_hi; + u64 m_lo; + enum branch_state_t : unsigned + { + NONE = 0, + DELAY = 1, // delay slot instruction active + BRANCH = 2, // branch instruction active + EXCEPTION = 3, // exception triggered + NULLIFY = 4, // next instruction nullified + } + m_branch_state; + u64 m_branch_target; + + // cp0 state + u64 m_cp0[32]; + u64 m_cp0_timer_zero; + emu_timer *m_cp0_timer; + memory_passthrough_handler *m_ll_watch; + struct tlb_entry_t + { + u64 mask; + u64 vpn; + u64 pfn[2]; + + u8 low_bit; + } + m_tlb[48]; + unsigned m_last[3]; + bool m_64; + + // cp1 state + u64 m_f[32]; // floating point registers + u32 m_fcr0; // implementation and revision register + u32 m_fcr31; // control/status register + + // experimental icache state + u32 m_icache_mask_hi; + u32 m_icache_mask_lo; + unsigned m_icache_line_size; + unsigned m_icache_shift; + std::unique_ptr m_icache_tag; + std::unique_ptr m_icache_data; + + // experimental icache statistics + u64 m_icache_hit; + u64 m_icache_miss; +}; + +class r4000_device : public r4000_base_device +{ +public: + // NOTE: R4000 chips prior to 3.0 have an xtlb bug + r4000_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : r4000_base_device(mconfig, R4000, tag, owner, clock, 0x0430, CACHE_8K, CACHE_8K) + { + // no secondary cache + m_cp0[CP0_Config] |= CONFIG_SC; + } +}; + +class r4400_device : public r4000_base_device +{ +public: + r4400_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : r4000_base_device(mconfig, R4400, tag, owner, clock, 0x0440, CACHE_16K, CACHE_16K) + { + // no secondary cache + m_cp0[CP0_Config] |= CONFIG_SC; + } +}; + +class r4600_device : public r4000_base_device +{ +public: + r4600_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : r4000_base_device(mconfig, R4600, tag, owner, clock, 0x2000, CACHE_16K, CACHE_16K) + { + // no secondary cache + m_cp0[CP0_Config] |= CONFIG_SC; + } +}; +#endif // MAME_CPU_MIPS_R4000_H