summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/mips/r4000.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/mips/r4000.cpp')
-rw-r--r--src/devices/cpu/mips/r4000.cpp3067
1 files changed, 3067 insertions, 0 deletions
diff --git a/src/devices/cpu/mips/r4000.cpp b/src/devices/cpu/mips/r4000.cpp
new file mode 100644
index 00000000000..ca95b107c65
--- /dev/null
+++ b/src/devices/cpu/mips/r4000.cpp
@@ -0,0 +1,3067 @@
+// 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 LOG_STATS (1U << 5)
+
+#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) (cp0_64() ? ((r) + (o)) : s64(s32((r) + (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 R4400")
+DEFINE_DEVICE_TYPE(R4600, r4600_device, "r4600", "QED R4600")
+
+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_FCR30, "FCR30", m_fcr30).formatstr("%08X");
+ 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<u32[]>(0x100U << config_ic);
+ m_icache_data = std::make_unique<u32 []>((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();
+
+ if (m_ll_watch)
+ {
+ m_ll_watch->remove();
+ m_ll_watch = nullptr;
+ }
+
+ m_cp0[CP0_WatchLo] = 0;
+ m_cp0[CP0_WatchHi] = 0;
+
+ // initialize tlb mru index with identity mapping
+ for (unsigned i = 0; i < ARRAY_LENGTH(m_tlb); i++)
+ {
+ m_tlb_mru[TRANSLATE_READ][i] = i;
+ m_tlb_mru[TRANSLATE_WRITE][i] = i;
+ m_tlb_mru[TRANSLATE_FETCH][i] = i;
+ }
+
+ // initialize statistics
+ m_tlb_scans = 0;
+ m_tlb_loops = 0;
+ m_icache_hits = 0;
+ m_icache_misses = 0;
+}
+
+void r4000_base_device::device_stop()
+{
+ if ((m_icache_hits + m_icache_misses) > 0)
+ LOGMASKED(LOG_STATS, "icache hit ratio %.3f%% (%d hits %d misses)\n",
+ double(m_icache_hits) / double(m_icache_hits + m_icache_misses) * 100.0, m_icache_hits, m_icache_misses);
+
+ if (m_tlb_scans > 0)
+ LOGMASKED(LOG_STATS, "tlb scans %d loops %d average %.3f loops per scan\n", m_tlb_scans, m_tlb_loops, double(m_tlb_loops) / double(m_tlb_scans));
+}
+
+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<util::disasm_interface> r4000_base_device::create_disassembler()
+{
+ return std::make_unique<mips3_disassembler>();
+}
+
+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] = s64(s32(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] = s64(s32(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<s32>(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(s32(u32(m_r[RSREG]) / u32(m_r[RTREG])));
+ m_hi = s64(s32(u32(m_r[RSREG]) % u32(m_r[RTREG])));
+ }
+ m_icount -= 35;
+ break;
+ case 0x1c: // DMULT
+ m_lo = mul_64x64(m_r[RSREG], m_r[RTREG], reinterpret_cast<s64 *>(&m_hi));
+ m_icount -= 7;
+ break;
+ case 0x1d: // DMULTU
+ m_lo = mulu_64x64(m_r[RSREG], m_r[RTREG], &m_hi);
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x01: // BGEZ
+ if (s64(m_r[RSREG]) >= 0)
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x02: // BLTZL
+ if (s64(m_r[RSREG]) < 0)
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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] >= u64(s64(s16(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] >= u64(s64(s16(op))))
+ cpu_exception(EXCEPTION_TR);
+ break;
+ case 0x0c: // TEQI
+ if (m_r[RSREG] == u64(s64(s16(op))))
+ cpu_exception(EXCEPTION_TR);
+ break;
+ //case 0x0d: // *
+ case 0x0e: // TNEI
+ if (m_r[RSREG] != u64(s64(s16(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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x05: // BNE
+ if (m_r[RSREG] != m_r[RTREG])
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x06: // BLEZ
+ if (s64(m_r[RSREG]) <= 0)
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x07: // BGTZ
+ if (s64(m_r[RSREG]) > 0)
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ 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] = m_r[RSREG] < u64(s64(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(s16(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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ 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 (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || cp0_64())
+ cpu_ldl(op);
+ else
+ cpu_exception(EXCEPTION_RI);
+ break;
+ case 0x1b: // LDR
+ if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || cp0_64())
+ cpu_ldr(op);
+ else
+ cpu_exception(EXCEPTION_RI);
+ break;
+ //case 0x1c: // *
+ //case 0x1d: // *
+ //case 0x1e: // *
+ //case 0x1f: // *
+ case 0x20: // LB
+ load<s8>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](s8 data)
+ {
+ m_r[RTREG] = data;
+ });
+ break;
+ case 0x21: // LH
+ load<s16>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](s16 data)
+ {
+ m_r[RTREG] = data;
+ });
+ break;
+ case 0x22: // LWL
+ cpu_lwl(op);
+ break;
+ case 0x23: // LW
+ load<s32>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](s32 data)
+ {
+ m_r[RTREG] = data;
+ });
+ break;
+ case 0x24: // LBU
+ load<s8>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](u8 data)
+ {
+ m_r[RTREG] = data;
+ });
+ break;
+ case 0x25: // LHU
+ load<u16>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](u16 data)
+ {
+ m_r[RTREG] = data;
+ });
+ break;
+ case 0x26: // LWR
+ cpu_lwr(op);
+ break;
+ case 0x27: // LWU
+ load<u32>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](u32 data)
+ {
+ m_r[RTREG] = data;
+ });
+ break;
+ case 0x28: // SB
+ store<u8>(ADDR(m_r[RSREG], s16(op)), u8(m_r[RTREG]));
+ break;
+ case 0x29: // SH
+ store<u16>(ADDR(m_r[RSREG], s16(op)), u16(m_r[RTREG]));
+ break;
+ case 0x2a: // SWL
+ cpu_swl(op);
+ break;
+ case 0x2b: // SW
+ store<u32>(ADDR(m_r[RSREG], s16(op)), u32(m_r[RTREG]));
+ break;
+ case 0x2c: // SDL
+ if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || cp0_64())
+ cpu_sdl(op);
+ else
+ cpu_exception(EXCEPTION_RI);
+ break;
+ case 0x2d: // SDR
+ if (!(SR & SR_KSU) || (SR & (SR_EXL | SR_ERL)) || cp0_64())
+ 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], s16(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], s16(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], s16(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<s32>(ADDR(m_r[RSREG], s16(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<u64>(ADDR(m_r[RSREG], s16(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<u64>(ADDR(m_r[RSREG], s16(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<u32>(ADDR(m_r[RSREG], s16(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<u64>(ADDR(m_r[RSREG], s16(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<u64>(ADDR(m_r[RSREG], s16(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;
+ }
+ 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)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 3) ^ R4000_ENDIAN_LE_BE(3, 0)) << 3;
+
+ load<u32>(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)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 0x3) ^ R4000_ENDIAN_LE_BE(0, 3)) << 3;
+
+ load<u32>(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)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 3) ^ R4000_ENDIAN_LE_BE(3, 0)) << 3;
+
+ store<u32>(offset & ~3, u32(m_r[RTREG]) >> shift, ~u32(0) >> shift);
+}
+
+void r4000_base_device::cpu_swr(u32 const op)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 3) ^ R4000_ENDIAN_LE_BE(0, 3)) << 3;
+
+ store<u32>(offset & ~3, u32(m_r[RTREG]) << shift, ~u32(0) << shift);
+}
+
+void r4000_base_device::cpu_ldl(u32 const op)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(7, 0)) << 3;
+
+ load<u64>(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)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(0, 7)) << 3;
+
+ load<u64>(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)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(7, 0)) << 3;
+
+ store<u64>(offset & ~7, m_r[RTREG] >> shift, ~u64(0) >> shift);
+}
+
+void r4000_base_device::cpu_sdr(u32 const op)
+{
+ u64 const offset = ADDR(m_r[RSREG], s16(op));
+ unsigned const shift = ((offset & 7) ^ R4000_ENDIAN_LE_BE(0, 7)) << 3;
+
+ store<u64>(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)) || cp0_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)) || cp0_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;
+ }
+
+ 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);
+
+ // 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 = (cp0_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;
+ u32 const delta = m_cp0[CP0_Compare] - count;
+
+ m_cp0_timer->adjust(cycles_to_attotime(u64(delta) * 2));
+ }
+}
+
+TIMER_CALLBACK_MEMBER(r4000_base_device::cp0_timer_callback)
+{
+ m_cp0[CP0_Cause] |= CAUSE_IPEX5;
+}
+
+bool r4000_base_device::cp0_64() const
+{
+ switch (SR & (SR_KSU | SR_ERL | SR_EXL))
+ {
+ case SR_KSU_U: return bool(SR & SR_UX);
+ case SR_KSU_S: return bool(SR & SR_SX);
+
+ default:
+ return bool(SR & 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 30: m_r[RTREG] = m_fcr30; 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 30: // unknown
+ m_fcr30 = u32(m_r[RTREG]);
+ 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 + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x01: // BC1T
+ if (m_fcr31 & FCR31_C)
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ break;
+ case 0x02: // BC1FL
+ if (!(m_fcr31 & FCR31_C))
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ else
+ m_branch_state = NULLIFY;
+ break;
+ case 0x03: // BC1TL
+ if (m_fcr31 & FCR31_C)
+ {
+ m_branch_state = BRANCH;
+ m_branch_target = ADDR(m_pc + 4, s32(s16(op)) << 2);
+ }
+ 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);
+ else
+ cp1_set(FDREG, m_f[FSREG]);
+ }
+ 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);
+ else
+ cp1_set(FDREG, m_f[FSREG]);
+ }
+ 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<u32>(ADDR(m_r[RSREG], s16(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<u64>(ADDR(m_r[RSREG], s16(op)),
+ [this, op](u64 data)
+ {
+ if ((SR & SR_FR) || !(RTREG & 1))
+ m_f[RTREG] = data;
+ });
+ break;
+
+ case 0x39: // SWC1
+ if (SR & SR_FR)
+ store<u32>(ADDR(m_r[RSREG], s16(op)), u32(m_f[RTREG]));
+ else
+ if (RTREG & 1)
+ // store the high half of the even floating point register
+ store<u32>(ADDR(m_r[RSREG], s16(op)), u32(m_f[RTREG & ~1] >> 32));
+ else
+ // store the low half of the even floating point register
+ store<u32>(ADDR(m_r[RSREG], s16(op)), u32(m_f[RTREG & ~1]));
+ break;
+
+ case 0x3d: // SDC1
+ if ((SR & SR_FR) || !(RTREG & 1))
+ store<u64>(ADDR(m_r[RSREG], s16(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)) || cp0_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)) || cp0_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;
+
+ switch (SR & (SR_KSU | SR_ERL | SR_EXL))
+ {
+ case SR_KSU_U:
+ // 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
+ }
+ break;
+
+ case 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
+ }
+ break;
+
+ case SR_KSU_U | SR_KSU_S:
+ fatalerror("invalid ksu bits 0x%08x (%s)\n", u32(SR), machine().describe_context().c_str());
+ break;
+
+ default:
+ // 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
+ }
+ break;
+ }
+
+ // 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);
+
+ unsigned *mru = m_tlb_mru[intention & TRANSLATE_TYPE_MASK];
+ if (LOG_STATS)
+ m_tlb_scans++;
+
+ bool invalid = false;
+ bool modify = false;
+ for (unsigned i = 0; i < ARRAY_LENGTH(m_tlb); i++)
+ {
+ unsigned const index = mru[i];
+ 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;
+
+ if (LOG_STATS)
+ m_tlb_loops += i + 1;
+
+ 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);
+
+ // promote the entry in the mru index
+ if (i > 0)
+ std::swap(mru[i - 1], mru[i]);
+
+ 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);
+
+ // address errors shouldn't typically occur, so a breakpoint is handy
+ machine().debug_break();
+ }
+}
+
+template <typename T, typename U> std::enable_if_t<std::is_convertible<U, std::function<void(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 <typename T, typename U> std::enable_if_t<std::is_convertible<U, std::function<void(u64, 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 <typename T, typename U> std::enable_if_t<std::is_convertible<U, 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<void(u32)> &&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_misses++;
+
+ // 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_hits++;
+
+ // 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<u8>(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<s32>(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<u16>(unicode_string_pointer,
+ [this, unicode_string_pointer, &result](u16 const length)
+ {
+ if (length)
+ if (!load<u32>(unicode_string_pointer + 4,
+ [this, length, &result](s32 buffer)
+ {
+ for (int i = 0; i < length; i += 2)
+ load<u16>(buffer + i, [&result](wchar_t const character) { result += character; });
+ }))
+ result.assign(L"[unmapped]");
+ }))
+ result.assign(L"[unmapped]");
+
+ return utf8_from_wstring(result);
+}