mirror of
https://github.com/holub/mame
synced 2025-10-06 09:00:04 +03:00

While this behaviour is undefined according to the MIPS R4000 Microprocessor User's Manual, various factors point toward it most likely being correct, including: 1. The fact MIPS-I exposes 16x64-bit floating-point registers, but internally implements them as pairs of 32-bit registers (with only the even-numbered pairs being valid for arithmetic operations), making it somewhat likely MOV.S, like LWC1 and SWC1, can access either half. 2. Explicit mention in IDT documentation and "See MIPS Run", i.e. "The odd-numbered registers can be accessed by move and load/store instructions", and other commentary. 3. The presence of paired-single operations in later MIPS32/64 specifications, which operate on independent single-precision values stored in each of the lower and upper halves of a single floating-point register.
1950 lines
57 KiB
C++
1950 lines
57 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles, Patrick Mackinlay
|
|
|
|
/*
|
|
* MIPS-I emulation, including R2000[A], R3000[A] and IDT R30xx devices. The
|
|
* IDT devices come in two variations: those with an "E" suffix include a TLB,
|
|
* while those without have hard-wired address translation.
|
|
*
|
|
* TODO
|
|
* - R3041 features
|
|
* - cache emulation
|
|
*
|
|
*/
|
|
#include "emu.h"
|
|
#include "mips1.h"
|
|
#include "mips1dsm.h"
|
|
#include "debugger.h"
|
|
#include "softfloat3/source/include/softfloat.h"
|
|
|
|
#define LOG_GENERAL (1U << 0)
|
|
#define LOG_TLB (1U << 1)
|
|
#define LOG_IOP (1U << 2)
|
|
#define LOG_RISCOS (1U << 3)
|
|
|
|
//#define VERBOSE (LOG_GENERAL|LOG_TLB)
|
|
|
|
#include "logmacro.h"
|
|
|
|
#define RSREG ((op >> 21) & 31)
|
|
#define RTREG ((op >> 16) & 31)
|
|
#define RDREG ((op >> 11) & 31)
|
|
#define SHIFT ((op >> 6) & 31)
|
|
|
|
#define FTREG ((op >> 16) & 31)
|
|
#define FSREG ((op >> 11) & 31)
|
|
#define FDREG ((op >> 6) & 31)
|
|
|
|
#define SIMMVAL s16(op)
|
|
#define UIMMVAL u16(op)
|
|
#define LIMMVAL (op & 0x03ffffff)
|
|
|
|
#define SR m_cop0[COP0_Status]
|
|
#define CAUSE m_cop0[COP0_Cause]
|
|
|
|
DEFINE_DEVICE_TYPE(R2000, r2000_device, "r2000", "MIPS R2000")
|
|
DEFINE_DEVICE_TYPE(R2000A, r2000a_device, "r2000a", "MIPS R2000A")
|
|
DEFINE_DEVICE_TYPE(R3000, r3000_device, "r3000", "MIPS R3000")
|
|
DEFINE_DEVICE_TYPE(R3000A, r3000a_device, "r3000a", "MIPS R3000A")
|
|
DEFINE_DEVICE_TYPE(R3041, r3041_device, "r3041", "IDT R3041")
|
|
DEFINE_DEVICE_TYPE(R3051, r3051_device, "r3051", "IDT R3051")
|
|
DEFINE_DEVICE_TYPE(R3052, r3052_device, "r3052", "IDT R3052")
|
|
DEFINE_DEVICE_TYPE(R3052E, r3052e_device, "r3052e", "IDT R3052E")
|
|
DEFINE_DEVICE_TYPE(R3071, r3071_device, "r3071", "IDT R3071")
|
|
DEFINE_DEVICE_TYPE(R3081, r3081_device, "r3081", "IDT R3081")
|
|
DEFINE_DEVICE_TYPE(SONYPS2_IOP, iop_device, "sonyiop", "Sony Playstation 2 IOP")
|
|
|
|
ALLOW_SAVE_TYPE(mips1core_device_base::branch_state_t);
|
|
|
|
mips1core_device_base::mips1core_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, u32 cpurev, size_t icache_size, size_t dcache_size)
|
|
: cpu_device(mconfig, type, tag, owner, clock)
|
|
, m_program_config_be("program", ENDIANNESS_BIG, 32, 32)
|
|
, m_program_config_le("program", ENDIANNESS_LITTLE, 32, 32)
|
|
, m_icache_config("icache", ENDIANNESS_BIG, 32, 32)
|
|
, m_dcache_config("dcache", ENDIANNESS_BIG, 32, 32)
|
|
, m_cpurev(cpurev)
|
|
, m_endianness(ENDIANNESS_BIG)
|
|
, m_icount(0)
|
|
, m_icache_size(icache_size)
|
|
, m_dcache_size(dcache_size)
|
|
, m_in_brcond{ *this, *this, *this, *this }
|
|
{
|
|
}
|
|
|
|
mips1_device_base::mips1_device_base(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, u32 cpurev, size_t icache_size, size_t dcache_size)
|
|
: mips1core_device_base(mconfig, type, tag, owner, clock, cpurev, icache_size, dcache_size)
|
|
, m_fcr0(0)
|
|
{
|
|
}
|
|
|
|
r2000_device::r2000_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock, size_t icache_size, size_t dcache_size)
|
|
: mips1_device_base(mconfig, R2000, tag, owner, clock, 0x0100, icache_size, dcache_size)
|
|
{
|
|
}
|
|
|
|
r2000a_device::r2000a_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock, size_t icache_size, size_t dcache_size)
|
|
: mips1_device_base(mconfig, R2000A, tag, owner, clock, 0x0210, icache_size, dcache_size)
|
|
{
|
|
}
|
|
|
|
r3000_device::r3000_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock, size_t icache_size, size_t dcache_size)
|
|
: mips1_device_base(mconfig, R3000, tag, owner, clock, 0x0220, icache_size, dcache_size)
|
|
{
|
|
}
|
|
|
|
r3000a_device::r3000a_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock, size_t icache_size, size_t dcache_size)
|
|
: mips1_device_base(mconfig, R3000A, tag, owner, clock, 0x0230, icache_size, dcache_size)
|
|
{
|
|
}
|
|
|
|
r3041_device::r3041_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: mips1core_device_base(mconfig, R3041, tag, owner, clock, 0x0700, 2048, 512)
|
|
{
|
|
}
|
|
|
|
r3051_device::r3051_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: mips1core_device_base(mconfig, R3051, tag, owner, clock, 0x0200, 4096, 2048)
|
|
{
|
|
}
|
|
|
|
r3052_device::r3052_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: mips1core_device_base(mconfig, R3052, tag, owner, clock, 0x0200, 8192, 2048)
|
|
{
|
|
}
|
|
|
|
r3052e_device::r3052e_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: mips1_device_base(mconfig, R3052E, tag, owner, clock, 0x0200, 8192, 2048)
|
|
{
|
|
}
|
|
|
|
r3071_device::r3071_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock, size_t icache_size, size_t dcache_size)
|
|
: mips1_device_base(mconfig, R3071, tag, owner, clock, 0x0200, icache_size, dcache_size)
|
|
{
|
|
}
|
|
|
|
r3081_device::r3081_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock, size_t icache_size, size_t dcache_size)
|
|
: mips1_device_base(mconfig, R3081, tag, owner, clock, 0x0200, icache_size, dcache_size)
|
|
{
|
|
set_fpu(0x0300);
|
|
}
|
|
|
|
iop_device::iop_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: mips1core_device_base(mconfig, SONYPS2_IOP, tag, owner, clock, 0x001f, 4096, 1024)
|
|
{
|
|
m_endianness = ENDIANNESS_LITTLE;
|
|
}
|
|
|
|
/*
|
|
* Two additional address spaces are defined to represent the instruction and
|
|
* data caches. These are only used to simulate cache isolation functionality
|
|
* at this point, but could simulate other behaviour as needed in future.
|
|
*/
|
|
void mips1core_device_base::device_add_mconfig(machine_config &config)
|
|
{
|
|
set_addrmap(1, &mips1core_device_base::icache_map);
|
|
set_addrmap(2, &mips1core_device_base::dcache_map);
|
|
}
|
|
|
|
void mips1core_device_base::device_start()
|
|
{
|
|
// set our instruction counter
|
|
set_icountptr(m_icount);
|
|
|
|
// resolve conditional branch input handlers
|
|
for (devcb_read_line &cb : m_in_brcond)
|
|
cb.resolve_safe(0);
|
|
|
|
// register our state for the debugger
|
|
state_add(STATE_GENPC, "GENPC", m_pc).noshow();
|
|
state_add(STATE_GENPCBASE, "CURPC", m_pc).noshow();
|
|
|
|
state_add(MIPS1_PC, "PC", m_pc);
|
|
state_add(MIPS1_COP0 + COP0_Status, "SR", m_cop0[COP0_Status]);
|
|
|
|
for (unsigned i = 0; i < ARRAY_LENGTH(m_r); i++)
|
|
state_add(MIPS1_R0 + i, util::string_format("R%d", i).c_str(), m_r[i]);
|
|
|
|
state_add(MIPS1_HI, "HI", m_hi);
|
|
state_add(MIPS1_LO, "LO", m_lo);
|
|
|
|
// cop0 exception registers
|
|
state_add(MIPS1_COP0 + COP0_BadVAddr, "BadVAddr", m_cop0[COP0_BadVAddr]);
|
|
state_add(MIPS1_COP0 + COP0_Cause, "Cause", m_cop0[COP0_Cause]);
|
|
state_add(MIPS1_COP0 + COP0_EPC, "EPC", m_cop0[COP0_EPC]);
|
|
|
|
// register our state for saving
|
|
save_item(NAME(m_pc));
|
|
save_item(NAME(m_hi));
|
|
save_item(NAME(m_lo));
|
|
save_item(NAME(m_r));
|
|
save_item(NAME(m_cop0));
|
|
save_item(NAME(m_branch_state));
|
|
save_item(NAME(m_branch_target));
|
|
|
|
// initialise cpu id register
|
|
m_cop0[COP0_PRId] = m_cpurev;
|
|
}
|
|
|
|
void r3041_device::device_start()
|
|
{
|
|
mips1core_device_base::device_start();
|
|
|
|
// cop0 r3041 registers
|
|
state_add(MIPS1_COP0 + COP0_BusCtrl, "BusCtrl", m_cop0[COP0_BusCtrl]);
|
|
state_add(MIPS1_COP0 + COP0_Config, "Config", m_cop0[COP0_Config]);
|
|
state_add(MIPS1_COP0 + COP0_Count, "Count", m_cop0[COP0_Count]);
|
|
state_add(MIPS1_COP0 + COP0_PortSize, "PortSize", m_cop0[COP0_PortSize]);
|
|
state_add(MIPS1_COP0 + COP0_Compare, "Compare", m_cop0[COP0_Compare]);
|
|
}
|
|
|
|
void mips1core_device_base::device_reset()
|
|
{
|
|
// initialize the state
|
|
m_pc = 0xbfc00000;
|
|
m_branch_state = NONE;
|
|
|
|
// non-tlb devices have tlb shut down
|
|
m_cop0[COP0_Status] = SR_BEV | SR_TS;
|
|
|
|
m_data_spacenum = 0;
|
|
}
|
|
|
|
void mips1core_device_base::execute_run()
|
|
{
|
|
// check for interrupts
|
|
check_irqs();
|
|
|
|
// core execution loop
|
|
while (m_icount-- > 0)
|
|
{
|
|
// debugging
|
|
debugger_instruction_hook(m_pc);
|
|
|
|
if (VERBOSE & LOG_IOP)
|
|
{
|
|
if ((m_pc & 0x1fffffff) == 0x00012C48 || (m_pc & 0x1fffffff) == 0x0001420C || (m_pc & 0x1fffffff) == 0x0001430C)
|
|
{
|
|
u32 ptr = m_r[5];
|
|
u32 length = m_r[6];
|
|
if (length >= 4096)
|
|
length = 4095;
|
|
while (length)
|
|
{
|
|
load<u8>(ptr, [](char c) { printf("%c", c); });
|
|
ptr++;
|
|
length--;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
// fetch and execute instruction
|
|
fetch(m_pc, [this](u32 const op)
|
|
{
|
|
// parse the instruction
|
|
switch (op >> 26)
|
|
{
|
|
case 0x00: // SPECIAL
|
|
switch (op & 63)
|
|
{
|
|
case 0x00: // SLL
|
|
m_r[RDREG] = m_r[RTREG] << SHIFT;
|
|
break;
|
|
case 0x02: // SRL
|
|
m_r[RDREG] = m_r[RTREG] >> SHIFT;
|
|
break;
|
|
case 0x03: // SRA
|
|
m_r[RDREG] = s32(m_r[RTREG]) >> SHIFT;
|
|
break;
|
|
case 0x04: // SLLV
|
|
m_r[RDREG] = m_r[RTREG] << (m_r[RSREG] & 31);
|
|
break;
|
|
case 0x06: // SRLV
|
|
m_r[RDREG] = m_r[RTREG] >> (m_r[RSREG] & 31);
|
|
break;
|
|
case 0x07: // SRAV
|
|
m_r[RDREG] = s32(m_r[RTREG]) >> (m_r[RSREG] & 31);
|
|
break;
|
|
case 0x08: // JR
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_r[RSREG];
|
|
break;
|
|
case 0x09: // JALR
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_r[RSREG];
|
|
m_r[RDREG] = m_pc + 8;
|
|
break;
|
|
case 0x0c: // SYSCALL
|
|
generate_exception(EXCEPTION_SYSCALL);
|
|
break;
|
|
case 0x0d: // BREAK
|
|
generate_exception(EXCEPTION_BREAK);
|
|
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 0x18: // MULT
|
|
{
|
|
u64 product = mul_32x32(m_r[RSREG], m_r[RTREG]);
|
|
|
|
m_lo = product;
|
|
m_hi = product >> 32;
|
|
m_icount -= 11;
|
|
}
|
|
break;
|
|
case 0x19: // MULTU
|
|
{
|
|
u64 product = mulu_32x32(m_r[RSREG], m_r[RTREG]);
|
|
|
|
m_lo = product;
|
|
m_hi = product >> 32;
|
|
m_icount -= 11;
|
|
}
|
|
break;
|
|
case 0x1a: // DIV
|
|
if (m_r[RTREG])
|
|
{
|
|
m_lo = s32(m_r[RSREG]) / s32(m_r[RTREG]);
|
|
m_hi = s32(m_r[RSREG]) % s32(m_r[RTREG]);
|
|
}
|
|
m_icount -= 34;
|
|
break;
|
|
case 0x1b: // DIVU
|
|
if (m_r[RTREG])
|
|
{
|
|
m_lo = m_r[RSREG] / m_r[RTREG];
|
|
m_hi = m_r[RSREG] % m_r[RTREG];
|
|
}
|
|
m_icount -= 34;
|
|
break;
|
|
case 0x20: // ADD
|
|
{
|
|
u32 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], 31) && BIT(m_r[RSREG] ^ sum, 31))
|
|
generate_exception(EXCEPTION_OVERFLOW);
|
|
else
|
|
m_r[RDREG] = sum;
|
|
}
|
|
break;
|
|
case 0x21: // ADDU
|
|
m_r[RDREG] = m_r[RSREG] + m_r[RTREG];
|
|
break;
|
|
case 0x22: // SUB
|
|
{
|
|
u32 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], 31) && BIT(m_r[RSREG] ^ difference, 31))
|
|
generate_exception(EXCEPTION_OVERFLOW);
|
|
else
|
|
m_r[RDREG] = difference;
|
|
}
|
|
break;
|
|
case 0x23: // SUBU
|
|
m_r[RDREG] = m_r[RSREG] - 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 0x2a: // SLT
|
|
m_r[RDREG] = s32(m_r[RSREG]) < s32(m_r[RTREG]);
|
|
break;
|
|
case 0x2b: // SLTU
|
|
m_r[RDREG] = u32(m_r[RSREG]) < u32(m_r[RTREG]);
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x01: // REGIMM
|
|
switch (RTREG)
|
|
{
|
|
case 0x00: // BLTZ
|
|
if (s32(m_r[RSREG]) < 0)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x01: // BGEZ
|
|
if (s32(m_r[RSREG]) >= 0)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x10: // BLTZAL
|
|
if (s32(m_r[RSREG]) < 0)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
m_r[31] = m_pc + 8;
|
|
}
|
|
break;
|
|
case 0x11: // BGEZAL
|
|
if (s32(m_r[RSREG]) >= 0)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
m_r[31] = m_pc + 8;
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x02: // J
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = ((m_pc + 4) & 0xf0000000) | (LIMMVAL << 2);
|
|
break;
|
|
case 0x03: // JAL
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = ((m_pc + 4) & 0xf0000000) | (LIMMVAL << 2);
|
|
m_r[31] = m_pc + 8;
|
|
break;
|
|
case 0x04: // BEQ
|
|
if (m_r[RSREG] == m_r[RTREG])
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x05: // BNE
|
|
if (m_r[RSREG] != m_r[RTREG])
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x06: // BLEZ
|
|
if (s32(m_r[RSREG]) <= 0)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x07: // BGTZ
|
|
if (s32(m_r[RSREG]) > 0)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x08: // ADDI
|
|
{
|
|
u32 const sum = m_r[RSREG] + SIMMVAL;
|
|
|
|
// overflow: (sign(addend0) == sign(addend1)) && (sign(addend0) != sign(sum))
|
|
if (!BIT(m_r[RSREG] ^ s32(SIMMVAL), 31) && BIT(m_r[RSREG] ^ sum, 31))
|
|
generate_exception(EXCEPTION_OVERFLOW);
|
|
else
|
|
m_r[RTREG] = sum;
|
|
}
|
|
break;
|
|
case 0x09: // ADDIU
|
|
m_r[RTREG] = m_r[RSREG] + SIMMVAL;
|
|
break;
|
|
case 0x0a: // SLTI
|
|
m_r[RTREG] = s32(m_r[RSREG]) < s32(SIMMVAL);
|
|
break;
|
|
case 0x0b: // SLTIU
|
|
m_r[RTREG] = u32(m_r[RSREG]) < u32(SIMMVAL);
|
|
break;
|
|
case 0x0c: // ANDI
|
|
m_r[RTREG] = m_r[RSREG] & UIMMVAL;
|
|
break;
|
|
case 0x0d: // ORI
|
|
m_r[RTREG] = m_r[RSREG] | UIMMVAL;
|
|
break;
|
|
case 0x0e: // XORI
|
|
m_r[RTREG] = m_r[RSREG] ^ UIMMVAL;
|
|
break;
|
|
case 0x0f: // LUI
|
|
m_r[RTREG] = UIMMVAL << 16;
|
|
break;
|
|
case 0x10: // COP0
|
|
if (!(SR & SR_KUc) || (SR & SR_COP0))
|
|
handle_cop0(op);
|
|
else
|
|
generate_exception(EXCEPTION_BADCOP0);
|
|
break;
|
|
case 0x11: // COP1
|
|
handle_cop1(op);
|
|
break;
|
|
case 0x12: // COP2
|
|
handle_cop2(op);
|
|
break;
|
|
case 0x13: // COP3
|
|
handle_cop3(op);
|
|
break;
|
|
case 0x20: // LB
|
|
load<u8>(SIMMVAL + m_r[RSREG], [this, op](s8 temp) { m_r[RTREG] = temp; });
|
|
break;
|
|
case 0x21: // LH
|
|
load<u16>(SIMMVAL + m_r[RSREG], [this, op](s16 temp) { m_r[RTREG] = temp; });
|
|
break;
|
|
case 0x22: // LWL
|
|
lwl(op);
|
|
break;
|
|
case 0x23: // LW
|
|
load<u32>(SIMMVAL + m_r[RSREG], [this, op](u32 temp) { m_r[RTREG] = temp; });
|
|
break;
|
|
case 0x24: // LBU
|
|
load<u8>(SIMMVAL + m_r[RSREG], [this, op](u8 temp) { m_r[RTREG] = temp; });
|
|
break;
|
|
case 0x25: // LHU
|
|
load<u16>(SIMMVAL + m_r[RSREG], [this, op](u16 temp) { m_r[RTREG] = temp; });
|
|
break;
|
|
case 0x26: // LWR
|
|
lwr(op);
|
|
break;
|
|
case 0x28: // SB
|
|
store<u8>(SIMMVAL + m_r[RSREG], m_r[RTREG]);
|
|
break;
|
|
case 0x29: // SH
|
|
store<u16>(SIMMVAL + m_r[RSREG], m_r[RTREG]);
|
|
break;
|
|
case 0x2a: // SWL
|
|
swl(op);
|
|
break;
|
|
case 0x2b: // SW
|
|
store<u32>(SIMMVAL + m_r[RSREG], m_r[RTREG]);
|
|
break;
|
|
case 0x2e: // SWR
|
|
swr(op);
|
|
break;
|
|
case 0x31: // LWC1
|
|
handle_cop1(op);
|
|
break;
|
|
case 0x32: // LWC2
|
|
handle_cop2(op);
|
|
break;
|
|
case 0x33: // LWC3
|
|
handle_cop3(op);
|
|
break;
|
|
case 0x39: // SWC1
|
|
handle_cop1(op);
|
|
break;
|
|
case 0x3a: // SWC2
|
|
handle_cop2(op);
|
|
break;
|
|
case 0x3b: // SWC3
|
|
handle_cop3(op);
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// clear register 0
|
|
m_r[0] = 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
void mips1core_device_base::execute_set_input(int irqline, int state)
|
|
{
|
|
if (state != CLEAR_LINE)
|
|
{
|
|
CAUSE |= CAUSE_IPEX0 << irqline;
|
|
|
|
// enable debugger interrupt breakpoints
|
|
if ((SR & SR_IEc) && (SR & (SR_IMEX0 << irqline)))
|
|
standard_irq_callback(irqline);
|
|
}
|
|
else
|
|
CAUSE &= ~(CAUSE_IPEX0 << irqline);
|
|
|
|
check_irqs();
|
|
}
|
|
|
|
device_memory_interface::space_config_vector mips1core_device_base::memory_space_config() const
|
|
{
|
|
return space_config_vector {
|
|
std::make_pair(AS_PROGRAM, (m_endianness == ENDIANNESS_BIG) ? &m_program_config_be : &m_program_config_le),
|
|
std::make_pair(1, &m_icache_config),
|
|
std::make_pair(2, &m_dcache_config)
|
|
};
|
|
}
|
|
|
|
bool mips1core_device_base::memory_translate(int spacenum, int intention, offs_t &address)
|
|
{
|
|
// check for kernel memory address
|
|
if (BIT(address, 31))
|
|
{
|
|
// check debug or kernel mode
|
|
if ((intention & TRANSLATE_DEBUG_MASK) || !(SR & SR_KUc))
|
|
{
|
|
switch (address & 0xe0000000)
|
|
{
|
|
case 0x80000000: // kseg0: unmapped, cached, privileged
|
|
case 0xa0000000: // kseg1: unmapped, uncached, privileged
|
|
address &= ~0xe0000000;
|
|
break;
|
|
|
|
case 0xc0000000: // kseg2: mapped, cached, privileged
|
|
case 0xe0000000:
|
|
break;
|
|
}
|
|
}
|
|
else if (SR & SR_KUc)
|
|
{
|
|
if (!machine().side_effects_disabled())
|
|
{
|
|
// exception
|
|
m_cop0[COP0_BadVAddr] = address;
|
|
|
|
generate_exception((intention & TRANSLATE_WRITE) ? EXCEPTION_ADDRSTORE : EXCEPTION_ADDRLOAD);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
// kuseg physical addresses have a 1GB offset
|
|
address += 0x40000000;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<util::disasm_interface> mips1core_device_base::create_disassembler()
|
|
{
|
|
return std::make_unique<mips1_disassembler>();
|
|
}
|
|
|
|
void mips1core_device_base::icache_map(address_map &map)
|
|
{
|
|
if (m_icache_size)
|
|
map(0, m_icache_size - 1).ram().mirror(~(m_icache_size - 1));
|
|
}
|
|
|
|
void mips1core_device_base::dcache_map(address_map &map)
|
|
{
|
|
if (m_dcache_size)
|
|
map(0, m_dcache_size - 1).ram().mirror(~(m_dcache_size - 1));
|
|
}
|
|
|
|
void mips1core_device_base::generate_exception(u32 exception, bool refill)
|
|
{
|
|
if ((VERBOSE & LOG_RISCOS) && (exception == EXCEPTION_SYSCALL))
|
|
{
|
|
static char const *const sysv_syscalls[] =
|
|
{
|
|
"syscall", "exit", "fork", "read", "write", "open", "close", "wait", "creat", "link",
|
|
"unlink", "execv", "chdir", "time", "mknod", "chmod", "chown", "brk", "stat", "lseek",
|
|
"getpid", "mount", "umount", "setuid", "getuid", "stime", "ptrace", "alarm", "fstat", "pause",
|
|
"utime", "stty", "gtty", "access", "nice", "statfs", "sync", "kill", "fstatfs", "setpgrp",
|
|
nullptr, "dup", "pipe", "times", "profil", "plock", "setgid", "getgid", "signal", "msgsys",
|
|
"sysmips", "acct", "shmsys", "semsys", "ioctl", "uadmin", nullptr, "utssys", nullptr, "execve",
|
|
"umask", "chroot", "ofcntl", "ulimit", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
|
"advfs", "unadvfs", "rmount", "rumount", "rfstart", nullptr, "rdebug", "rfstop", "rfsys", "rmdir",
|
|
"mkdir", "getdents", nullptr, nullptr, "sysfs", "getmsg", "putmsg", "poll", "sigreturn", "accept",
|
|
"bind", "connect", "gethostid", "getpeername", "getsockname", "getsockopt", "listen", "recv", "recvfrom", "recvmsg",
|
|
"select", "send", "sendmsg", "sendto", "sethostid", "setsockopt", "shutdown", "socket", "gethostname", "sethostname",
|
|
"getdomainname","setdomainname","truncate", "ftruncate", "rename", "symlink", "readlink", "lstat", "nfsmount", "nfssvc",
|
|
"getfh", "async_daemon", "old_exportfs", "mmap", "munmap", "getitimer", "setitimer", nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
|
"cacheflush", "cachectl", "fchown", "fchmod", "wait3", "mmap", "munmap", "madvise", "getpagesize", "setreuid",
|
|
"setregid", "setpgid", "getgroups", "setgroups", "gettimeofday", "getrusage", "getrlimit", "setrlimit", "exportfs", "fcntl"
|
|
};
|
|
|
|
static char const *const bsd_syscalls[] =
|
|
{
|
|
"syscall", "exit", "fork", "read", "write", "open", "close", nullptr, "creat", "link",
|
|
"unlink", "execv", "chdir", nullptr, "mknod", "chmod", "chown", "brk", nullptr, "lseek",
|
|
"getpid", "omount", "oumount", nullptr, "getuid", nullptr, "ptrace", nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, "access", nullptr, nullptr, "sync", "kill", "stat", nullptr,
|
|
"lstat", "dup", "pipe", nullptr, "profil", nullptr, nullptr, "getgid", nullptr, nullptr,
|
|
nullptr, "acct", nullptr, nullptr, "ioctl", "reboot", nullptr, "symlink", "readlink", "execve",
|
|
"umask", "chroot", "fstat", nullptr, "getpagesize", "mremap", "vfork", nullptr, nullptr, "sbrk",
|
|
"sstk", "mmap", "vadvise", "munmap", "mprotec", "madvise", "vhangup", nullptr, "mincore", "getgroups",
|
|
"setgroups", "getpgrp", "setpgrp", "setitimer", "wait3", "swapon", "getitimer", "gethostname", "sethostname", "getdtablesize",
|
|
"dup2", "getdopt", "fcntl", "select", "setdopt", "fsync", "setpriority", "socket", "connect", "accept",
|
|
"getpriority", "send", "recv", "sigreturn", "bind", "setsockopt", "listen", nullptr, "sigvec", "sigblock",
|
|
"sigsetmask", "sigpause", "sigstack", "recvmsg", "sendmsg", nullptr, "gettimeofday", "getrusage", "getsockopt", nullptr,
|
|
"readv", "writev", "settimeofday", "fchown", "fchmod", "recvfrom", "setreuid", "setregid", "rename", "truncate",
|
|
"ftruncate", "flock", nullptr, "sendto", "shutdown", "socketpair", "mkdir", "rmdir", "utimes", "sigcleanup",
|
|
"adjtime", "getpeername", "gethostid", "sethostid", "getrlimit", "setrlimit", "killpg", nullptr, "setquota", "quota",
|
|
"getsockname", "sysmips", "cacheflush", "cachectl", "debug", nullptr, nullptr, nullptr, "nfssvc", "getdirentries",
|
|
"statfs", "fstatfs", "unmount", "async_daemon", "getfh", "getdomainname","setdomainname",nullptr, "quotactl", "old_exportfs",
|
|
"mount", "hdwconf", "exportfs", "nfsfh_open", "libattach", "libdetach"
|
|
};
|
|
|
|
static char const *const msg_syscalls[] = { "msgget", "msgctl", "msgrcv", "msgsnd" };
|
|
static char const *const shm_syscalls[] = { "shmat", "shmctl", "shmdt", "shmget" };
|
|
static char const *const sem_syscalls[] = { "semctl", "semget", "semop" };
|
|
static char const *const mips_syscalls[] = { "mipskopt", "mipshwconf", "mipsgetrusage", "mipswait", "mipscacheflush", "mipscachectl" };
|
|
|
|
unsigned const asid = (m_cop0[COP0_EntryHi] & EH_ASID) >> 6;
|
|
switch (m_r[2])
|
|
{
|
|
case 1000: // indirect
|
|
switch (m_r[4])
|
|
{
|
|
case 1049: // msgsys
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall msgsys:%s() (%s)\n",
|
|
asid, (m_r[5] < ARRAY_LENGTH(msg_syscalls)) ? msg_syscalls[m_r[5]] : "unknown", machine().describe_context());
|
|
break;
|
|
|
|
case 1052: // shmsys
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall shmsys:%s() (%s)\n",
|
|
asid, (m_r[5] < ARRAY_LENGTH(shm_syscalls)) ? shm_syscalls[m_r[5]] : "unknown", machine().describe_context());
|
|
break;
|
|
|
|
case 1053: // semsys
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall semsys:%s() (%s)\n",
|
|
asid, (m_r[5] < ARRAY_LENGTH(sem_syscalls)) ? sem_syscalls[m_r[5]] : "unknown", machine().describe_context());
|
|
break;
|
|
|
|
case 2151: // bsd_sysmips
|
|
switch (m_r[5])
|
|
{
|
|
case 0x100: // mipskopt
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall bsd_sysmips:mipskopt(\"%s\") (%s)\n",
|
|
asid, debug_string(m_r[6]), machine().describe_context());
|
|
break;
|
|
|
|
default:
|
|
if ((m_r[5] > 0x100) && (m_r[5] - 0x100) < ARRAY_LENGTH(mips_syscalls))
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall bsd_sysmips:%s() (%s)\n",
|
|
asid, mips_syscalls[m_r[5] - 0x100], machine().describe_context());
|
|
else
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall bsd_sysmips:unknown %d (%s)\n",
|
|
asid, m_r[5], machine().describe_context());
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if ((m_r[4] > 2000) && (m_r[4] - 2000 < ARRAY_LENGTH(bsd_syscalls)) && bsd_syscalls[m_r[4] - 2000])
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall bsd_%s() (%s)\n",
|
|
asid, bsd_syscalls[m_r[4] - 2000], machine().describe_context());
|
|
else
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall indirect:unknown %d (%s)\n",
|
|
asid, m_r[4], machine().describe_context());
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 1003: // read
|
|
case 1006: // close
|
|
case 1054: // ioctl
|
|
case 1169: // fcntl
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall %s(%d) (%s)\n",
|
|
asid, sysv_syscalls[m_r[2] - 1000], m_r[4], machine().describe_context());
|
|
break;
|
|
|
|
case 1004: // write
|
|
if (m_r[4] == 1 || m_r[4] == 2)
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall %s(%d, \"%s\") (%s)\n",
|
|
asid, sysv_syscalls[m_r[2] - 1000], m_r[4], debug_string(m_r[5], m_r[6]), machine().describe_context());
|
|
else
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall %s(%d) (%s)\n",
|
|
asid, sysv_syscalls[m_r[2] - 1000], m_r[4], machine().describe_context());
|
|
break;
|
|
|
|
case 1005: // open
|
|
case 1008: // creat
|
|
case 1009: // link
|
|
case 1010: // unlink
|
|
case 1012: // chdir
|
|
case 1018: // stat
|
|
case 1033: // access
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall %s(\"%s\") (%s)\n",
|
|
asid, sysv_syscalls[m_r[2] - 1000], debug_string(m_r[4]), machine().describe_context());
|
|
break;
|
|
|
|
case 1059: // execve
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall execve(\"%s\", [ %s ], [ %s ]) (%s)\n",
|
|
asid, debug_string(m_r[4]), debug_string_array(m_r[5]), debug_string_array(m_r[6]), machine().describe_context());
|
|
break;
|
|
|
|
case 1060: // umask
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall umask(%#o) (%s)\n",
|
|
asid, m_r[4] & 0777, machine().describe_context());
|
|
break;
|
|
|
|
default:
|
|
if ((m_r[2] > 1000) && (m_r[2] - 1000 < ARRAY_LENGTH(sysv_syscalls)) && sysv_syscalls[m_r[2] - 1000])
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall %s() (%s)\n", asid, sysv_syscalls[m_r[2] - 1000], machine().describe_context());
|
|
else
|
|
LOGMASKED(LOG_RISCOS, "asid %d syscall unknown %d (%s)\n", asid, m_r[2], machine().describe_context());
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set the exception PC
|
|
m_cop0[COP0_EPC] = m_pc;
|
|
|
|
// load the cause register
|
|
CAUSE = (CAUSE & CAUSE_IP) | exception;
|
|
|
|
// if in a branch delay slot, restart the branch
|
|
if (m_branch_state == DELAY)
|
|
{
|
|
m_cop0[COP0_EPC] -= 4;
|
|
CAUSE |= CAUSE_BD;
|
|
}
|
|
m_branch_state = EXCEPTION;
|
|
|
|
// shift the exception bits
|
|
SR = (SR & ~SR_KUIE) | ((SR << 2) & SR_KUIEop);
|
|
|
|
if (refill)
|
|
m_pc = (SR & SR_BEV) ? 0xbfc00100 : 0x80000000;
|
|
else
|
|
m_pc = (SR & SR_BEV) ? 0xbfc00180 : 0x80000080;
|
|
|
|
debugger_exception_hook(exception);
|
|
|
|
if (SR & SR_KUp)
|
|
debugger_privilege_hook();
|
|
}
|
|
|
|
void mips1core_device_base::check_irqs()
|
|
{
|
|
if ((CAUSE & SR & SR_IM) && (SR & SR_IEc))
|
|
generate_exception(EXCEPTION_INTERRUPT);
|
|
}
|
|
|
|
void mips1core_device_base::handle_cop0(u32 const op)
|
|
{
|
|
switch (RSREG)
|
|
{
|
|
case 0x00: // MFC0
|
|
m_r[RTREG] = get_cop0_reg(RDREG);
|
|
break;
|
|
case 0x04: // MTC0
|
|
set_cop0_reg(RDREG, m_r[RTREG]);
|
|
break;
|
|
case 0x08: // BC0
|
|
switch (RTREG)
|
|
{
|
|
case 0x00: // BC0F
|
|
if (!m_in_brcond[0]())
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x01: // BC0T
|
|
if (m_in_brcond[0]())
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x10: // COP0
|
|
switch (op & 31)
|
|
{
|
|
case 0x10: // RFE
|
|
SR = (SR & ~SR_KUIEpc) | ((SR >> 2) & SR_KUIEpc);
|
|
if (bool(SR & SR_KUc) ^ bool(SR & SR_KUp))
|
|
debugger_privilege_hook();
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
}
|
|
|
|
u32 mips1core_device_base::get_cop0_reg(unsigned const reg)
|
|
{
|
|
return m_cop0[reg];
|
|
}
|
|
|
|
void mips1core_device_base::set_cop0_reg(unsigned const reg, u32 const data)
|
|
{
|
|
switch (reg)
|
|
{
|
|
case COP0_Context:
|
|
m_cop0[COP0_Context] = (m_cop0[COP0_Context] & ~PTE_BASE) | (data & PTE_BASE);
|
|
break;
|
|
case COP0_Status:
|
|
{
|
|
u32 const delta = SR ^ data;
|
|
|
|
m_cop0[COP0_Status] = data;
|
|
|
|
// handle cache isolation and swap
|
|
m_data_spacenum = (data & SR_IsC) ? ((data & SR_SwC) ? 1 : 2) : 0;
|
|
|
|
// update interrupts
|
|
if (delta & (SR_IEc | SR_IM))
|
|
check_irqs();
|
|
|
|
if ((delta & SR_KUc) && (m_branch_state != EXCEPTION))
|
|
debugger_privilege_hook();
|
|
}
|
|
break;
|
|
case COP0_Cause:
|
|
CAUSE = (CAUSE & CAUSE_IPEX) | (data & ~CAUSE_IPEX);
|
|
|
|
// update interrupts -- software ints can occur this way
|
|
check_irqs();
|
|
break;
|
|
case COP0_PRId:
|
|
// read-only register
|
|
break;
|
|
|
|
default:
|
|
m_cop0[reg] = data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void mips1core_device_base::handle_cop1(u32 const op)
|
|
{
|
|
if (!(SR & SR_COP1))
|
|
generate_exception(EXCEPTION_BADCOP1);
|
|
}
|
|
|
|
void mips1core_device_base::handle_cop2(u32 const op)
|
|
{
|
|
if (SR & SR_COP2)
|
|
{
|
|
switch (RSREG)
|
|
{
|
|
case 0x08: // BC2
|
|
switch (RTREG)
|
|
{
|
|
case 0x00: // BC2F
|
|
if (!m_in_brcond[2]())
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x01: // BC2T
|
|
if (m_in_brcond[2]())
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
generate_exception(EXCEPTION_BADCOP2);
|
|
}
|
|
|
|
void mips1core_device_base::handle_cop3(u32 const op)
|
|
{
|
|
if (SR & SR_COP3)
|
|
{
|
|
switch (RSREG)
|
|
{
|
|
case 0x08: // BC3
|
|
switch (RTREG)
|
|
{
|
|
case 0x00: // BC3F
|
|
if (!m_in_brcond[3]())
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x01: // BC3T
|
|
if (m_in_brcond[3]())
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
generate_exception(EXCEPTION_INVALIDOP);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
generate_exception(EXCEPTION_BADCOP3);
|
|
}
|
|
|
|
void mips1core_device_base::lwl(u32 const op)
|
|
{
|
|
offs_t const offset = SIMMVAL + m_r[RSREG];
|
|
load<u32>(offset & ~3, [this, op, offset](u32 temp)
|
|
{
|
|
unsigned const shift = ((offset & 3) ^ ENDIAN_VALUE_LE_BE(m_endianness, 3, 0)) << 3;
|
|
|
|
m_r[RTREG] = (m_r[RTREG] & ~u32(0xffffffffU << shift)) | (temp << shift);
|
|
});
|
|
}
|
|
|
|
void mips1core_device_base::lwr(u32 const op)
|
|
{
|
|
offs_t const offset = SIMMVAL + m_r[RSREG];
|
|
load<u32>(offset & ~3, [this, op, offset](u32 temp)
|
|
{
|
|
unsigned const shift = ((offset & 0x3) ^ ENDIAN_VALUE_LE_BE(m_endianness, 0, 3)) << 3;
|
|
|
|
m_r[RTREG] = (m_r[RTREG] & ~u32(0xffffffffU >> shift)) | (temp >> shift);
|
|
});
|
|
}
|
|
|
|
void mips1core_device_base::swl(u32 const op)
|
|
{
|
|
offs_t const offset = SIMMVAL + m_r[RSREG];
|
|
unsigned const shift = ((offset & 3) ^ ENDIAN_VALUE_LE_BE(m_endianness, 3, 0)) << 3;
|
|
|
|
store<u32>(offset & ~3, m_r[RTREG] >> shift, 0xffffffffU >> shift);
|
|
}
|
|
|
|
void mips1core_device_base::swr(u32 const op)
|
|
{
|
|
offs_t const offset = SIMMVAL + m_r[RSREG];
|
|
unsigned const shift = ((offset & 3) ^ ENDIAN_VALUE_LE_BE(m_endianness, 0, 3)) << 3;
|
|
|
|
store<u32>(offset & ~3, m_r[RTREG] << shift, 0xffffffffU << shift);
|
|
}
|
|
|
|
template <typename T, typename U> std::enable_if_t<std::is_convertible<U, std::function<void(T)>>::value, void> mips1core_device_base::load(u32 address, U &&apply)
|
|
{
|
|
if (memory_translate(m_data_spacenum, TRANSLATE_READ, address))
|
|
{
|
|
switch (sizeof(T))
|
|
{
|
|
case 1: apply(T(space(m_data_spacenum).read_byte(address))); break;
|
|
case 2: apply(T(space(m_data_spacenum).read_word(address))); break;
|
|
case 4: apply(T(space(m_data_spacenum).read_dword(address))); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T, typename U> std::enable_if_t<std::is_convertible<U, T>::value, void> mips1core_device_base::store(u32 address, U data, T mem_mask)
|
|
{
|
|
if (memory_translate(m_data_spacenum, TRANSLATE_WRITE, address))
|
|
{
|
|
switch (sizeof(T))
|
|
{
|
|
case 1: space(m_data_spacenum).write_byte(address, T(data)); break;
|
|
case 2: space(m_data_spacenum).write_word(address, T(data), mem_mask); break;
|
|
case 4: space(m_data_spacenum).write_dword(address, T(data), mem_mask); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool mips1core_device_base::fetch(u32 address, std::function<void(u32)> &&apply)
|
|
{
|
|
if (memory_translate(0, TRANSLATE_FETCH, address))
|
|
{
|
|
apply(space(0).read_dword(address));
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
std::string mips1core_device_base::debug_string(u32 string_pointer, unsigned const 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 mips1core_device_base::debug_string_array(u32 array_pointer)
|
|
{
|
|
auto const suppressor(machine().disable_side_effects());
|
|
|
|
bool done = false;
|
|
std::string result("");
|
|
|
|
while (!done)
|
|
{
|
|
done = true;
|
|
load<u32>(array_pointer, [this, &done, &result](u32 string_pointer)
|
|
{
|
|
if (string_pointer != 0)
|
|
{
|
|
if (!result.empty())
|
|
result += ", ";
|
|
|
|
result += '\"' + debug_string(string_pointer) + '\"';
|
|
|
|
done = false;
|
|
}
|
|
});
|
|
|
|
array_pointer += 4;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void mips1_device_base::device_start()
|
|
{
|
|
mips1core_device_base::device_start();
|
|
|
|
// cop0 tlb registers
|
|
state_add(MIPS1_COP0 + COP0_Index, "Index", m_cop0[COP0_Index]);
|
|
state_add(MIPS1_COP0 + COP0_Random, "Random", m_cop0[COP0_Random]);
|
|
state_add(MIPS1_COP0 + COP0_EntryLo, "EntryLo", m_cop0[COP0_EntryLo]);
|
|
state_add(MIPS1_COP0 + COP0_EntryHi, "EntryHi", m_cop0[COP0_EntryHi]);
|
|
state_add(MIPS1_COP0 + COP0_Context, "Context", m_cop0[COP0_Context]);
|
|
|
|
// cop1 registers
|
|
if (m_fcr0)
|
|
{
|
|
state_add(MIPS1_FCR31, "FCSR", m_fcr31);
|
|
for (unsigned i = 0; i < ARRAY_LENGTH(m_f); i++)
|
|
state_add(MIPS1_F0 + i, util::string_format("F%d", i).c_str(), m_f[i]);
|
|
}
|
|
|
|
save_item(NAME(m_reset_time));
|
|
save_item(NAME(m_tlb));
|
|
|
|
save_item(NAME(m_fcr30));
|
|
save_item(NAME(m_fcr31));
|
|
save_item(NAME(m_f));
|
|
}
|
|
|
|
void mips1_device_base::device_reset()
|
|
{
|
|
mips1core_device_base::device_reset();
|
|
|
|
// tlb is not shut down
|
|
m_cop0[COP0_Status] &= ~SR_TS;
|
|
|
|
m_reset_time = total_cycles();
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
void mips1_device_base::handle_cop0(u32 const op)
|
|
{
|
|
switch (op)
|
|
{
|
|
case 0x42000001: // TLBR - read tlb
|
|
{
|
|
u8 const index = (m_cop0[COP0_Index] >> 8) & 0x3f;
|
|
|
|
m_cop0[COP0_EntryHi] = m_tlb[index][0];
|
|
m_cop0[COP0_EntryLo] = m_tlb[index][1];
|
|
}
|
|
break;
|
|
|
|
case 0x42000002: // TLBWI - write tlb (indexed)
|
|
{
|
|
u8 const index = (m_cop0[COP0_Index] >> 8) & 0x3f;
|
|
|
|
m_tlb[index][0] = m_cop0[COP0_EntryHi];
|
|
m_tlb[index][1] = m_cop0[COP0_EntryLo];
|
|
|
|
LOGMASKED(LOG_TLB, "tlb write index %d asid %d vpn 0x%08x pfn 0x%08x %c%c%c%c (%s)\n",
|
|
index, (m_cop0[COP0_EntryHi] & EH_ASID) >> 6, m_cop0[COP0_EntryHi] & EH_VPN, m_cop0[COP0_EntryLo] & EL_PFN,
|
|
m_cop0[COP0_EntryLo] & EL_N ? 'N' : '-',
|
|
m_cop0[COP0_EntryLo] & EL_D ? 'D' : '-',
|
|
m_cop0[COP0_EntryLo] & EL_V ? 'V' : '-',
|
|
m_cop0[COP0_EntryLo] & EL_G ? 'G' : '-',
|
|
machine().describe_context());
|
|
}
|
|
break;
|
|
|
|
case 0x42000006: // TLBWR - write tlb (random)
|
|
{
|
|
u8 const random = get_cop0_reg(COP0_Random) >> 8;
|
|
|
|
m_tlb[random][0] = m_cop0[COP0_EntryHi];
|
|
m_tlb[random][1] = m_cop0[COP0_EntryLo];
|
|
|
|
LOGMASKED(LOG_TLB, "tlb write random %d asid %d vpn 0x%08x pfn 0x%08x %c%c%c%c (%s)\n",
|
|
random, (m_cop0[COP0_EntryHi] & EH_ASID) >> 6, m_cop0[COP0_EntryHi] & EH_VPN, m_cop0[COP0_EntryLo] & EL_PFN,
|
|
m_cop0[COP0_EntryLo] & EL_N ? 'N' : '-',
|
|
m_cop0[COP0_EntryLo] & EL_D ? 'D' : '-',
|
|
m_cop0[COP0_EntryLo] & EL_V ? 'V' : '-',
|
|
m_cop0[COP0_EntryLo] & EL_G ? 'G' : '-',
|
|
machine().describe_context());
|
|
}
|
|
break;
|
|
|
|
case 0x42000008: // TLBP - probe tlb
|
|
m_cop0[COP0_Index] = 0x80000000;
|
|
for (u8 index = 0; index < 64; index++)
|
|
{
|
|
// test vpn and optionally asid
|
|
u32 const mask = (m_tlb[index][1] & EL_G) ? EH_VPN : EH_VPN | EH_ASID;
|
|
if ((m_tlb[index][0] & mask) == (m_cop0[COP0_EntryHi] & mask))
|
|
{
|
|
LOGMASKED(LOG_TLB, "tlb probe hit vpn 0x%08x index %d (%s)\n",
|
|
m_cop0[COP0_EntryHi] & mask, index, machine().describe_context());
|
|
|
|
m_cop0[COP0_Index] = index << 8;
|
|
break;
|
|
}
|
|
}
|
|
if ((VERBOSE & LOG_TLB) && BIT(m_cop0[COP0_Index], 31))
|
|
LOGMASKED(LOG_TLB, "tlb probe miss asid %d vpn 0x%08x(%s)\n",
|
|
(m_cop0[COP0_EntryHi] & EH_ASID) >> 6, m_cop0[COP0_EntryHi] & EH_VPN, machine().describe_context());
|
|
break;
|
|
|
|
default:
|
|
mips1core_device_base::handle_cop0(op);
|
|
}
|
|
}
|
|
|
|
u32 mips1_device_base::get_cop0_reg(unsigned const reg)
|
|
{
|
|
// assume 64-entry tlb with 8 wired entries
|
|
if (reg == COP0_Random)
|
|
m_cop0[reg] = (63 - ((total_cycles() - m_reset_time) % 56)) << 8;
|
|
|
|
return m_cop0[reg];
|
|
}
|
|
|
|
void mips1_device_base::handle_cop1(u32 const op)
|
|
{
|
|
if (!(SR & SR_COP1))
|
|
{
|
|
generate_exception(EXCEPTION_BADCOP1);
|
|
return;
|
|
}
|
|
|
|
if (!m_fcr0)
|
|
return;
|
|
|
|
switch (op >> 26)
|
|
{
|
|
case 0x11: // COP1
|
|
switch ((op >> 21) & 0x1f)
|
|
{
|
|
case 0x00: // MFC1
|
|
if (FSREG & 1)
|
|
// move the high half of the floating point register
|
|
m_r[RTREG] = m_f[FSREG >> 1] >> 32;
|
|
else
|
|
// move the low half of the floating point register
|
|
m_r[RTREG] = m_f[FSREG >> 1] >> 0;
|
|
break;
|
|
case 0x02: // CFC1
|
|
switch (FSREG)
|
|
{
|
|
case 0: m_r[RTREG] = m_fcr0; break;
|
|
case 30: m_r[RTREG] = m_fcr30; break;
|
|
case 31: m_r[RTREG] = m_fcr31; break;
|
|
break;
|
|
|
|
default:
|
|
logerror("cfc1 undefined fpu control register %d (%s)\n", FSREG, machine().describe_context());
|
|
break;
|
|
}
|
|
break;
|
|
case 0x04: // MTC1
|
|
if (FSREG & 1)
|
|
// load the high half of the floating point register
|
|
m_f[FSREG >> 1] = (u64(m_r[RTREG]) << 32) | u32(m_f[FSREG >> 1]);
|
|
else
|
|
// load the low half of the floating point register
|
|
m_f[FSREG >> 1] = (m_f[FSREG >> 1] & ~0xffffffffULL) | m_r[RTREG];
|
|
break;
|
|
case 0x06: // CTC1
|
|
switch (RDREG)
|
|
{
|
|
case 0: // register is read-only
|
|
break;
|
|
|
|
case 30:
|
|
m_fcr30 = m_r[RTREG];
|
|
break;
|
|
|
|
case 31:
|
|
m_fcr31 = 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))
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
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 = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
case 0x01: // BC1T
|
|
if (m_fcr31 & FCR31_C)
|
|
{
|
|
m_branch_state = BRANCH;
|
|
m_branch_target = m_pc + 4 + (s32(SIMMVAL) << 2);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// unimplemented operation
|
|
m_fcr31 |= FCR31_CE;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x10: // S
|
|
switch (op & 0x3f)
|
|
{
|
|
case 0x00: // ADD.S
|
|
set_cop1_reg(FDREG >> 1, f32_add(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }).v);
|
|
break;
|
|
case 0x01: // SUB.S
|
|
set_cop1_reg(FDREG >> 1, f32_sub(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }).v);
|
|
break;
|
|
case 0x02: // MUL.S
|
|
set_cop1_reg(FDREG >> 1, f32_mul(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }).v);
|
|
break;
|
|
case 0x03: // DIV.S
|
|
set_cop1_reg(FDREG >> 1, f32_div(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }).v);
|
|
break;
|
|
case 0x05: // ABS.S
|
|
if (f32_lt(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ 0 }))
|
|
set_cop1_reg(FDREG >> 1, f32_mul(float32_t{ u32(m_f[FSREG >> 1]) }, i32_to_f32(-1)).v);
|
|
else
|
|
set_cop1_reg(FDREG >> 1, u32(m_f[FSREG >> 1]));
|
|
break;
|
|
case 0x06: // MOV.S
|
|
if (FDREG & 1)
|
|
if (FSREG & 1)
|
|
// move high half to high half
|
|
m_f[FDREG >> 1] = (m_f[FSREG >> 1] & ~0xffffffffULL) | u32(m_f[FDREG >> 1]);
|
|
else
|
|
// move low half to high half
|
|
m_f[FDREG >> 1] = (m_f[FSREG >> 1] << 32) | u32(m_f[FDREG >> 1]);
|
|
else
|
|
if (FSREG & 1)
|
|
// move high half to low half
|
|
m_f[FDREG >> 1] = (m_f[FDREG >> 1] & ~0xffffffffULL) | (m_f[FSREG >> 1] >> 32);
|
|
else
|
|
// move low half to low half
|
|
m_f[FDREG >> 1] = (m_f[FDREG >> 1] & ~0xffffffffULL) | u32(m_f[FSREG >> 1]);
|
|
break;
|
|
case 0x07: // NEG.S
|
|
set_cop1_reg(FDREG >> 1, f32_mul(float32_t{ u32(m_f[FSREG >> 1]) }, i32_to_f32(-1)).v);
|
|
break;
|
|
|
|
case 0x21: // CVT.D.S
|
|
set_cop1_reg(FDREG >> 1, f32_to_f64(float32_t{ u32(m_f[FSREG >> 1]) }).v);
|
|
break;
|
|
case 0x24: // CVT.W.S
|
|
set_cop1_reg(FDREG >> 1, f32_to_i32(float32_t{ u32(m_f[FSREG >> 1]) }, softfloat_roundingMode, true));
|
|
break;
|
|
|
|
case 0x30: // C.F.S (false)
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x31: // C.UN.S (unordered)
|
|
f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) });
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x32: // C.EQ.S (equal)
|
|
if (f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x33: // C.UEQ.S (unordered equal)
|
|
if (f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x34: // C.OLT.S (less than)
|
|
if (f32_lt(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x35: // C.ULT.S (unordered less than)
|
|
if (f32_lt(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }) || (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 (f32_le(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x37: // C.ULE.S (unordered less than or equal)
|
|
if (f32_le(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
|
|
case 0x38: // C.SF.S (signalling false)
|
|
f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) });
|
|
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x39: // C.NGLE.S (not greater, less than or equal)
|
|
f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) });
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_C | FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x3a: // C.SEQ.S (signalling equal)
|
|
if (f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3b: // C.NGL.S (not greater or less than)
|
|
if (f32_eq(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3c: // C.LT.S (less than)
|
|
if (f32_lt(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3d: // C.NGE.S (not greater or equal)
|
|
if (f32_lt(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3e: // C.LE.S (less than or equal)
|
|
if (f32_le(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3f: // C.NGT.S (not greater than)
|
|
if (f32_le(float32_t{ u32(m_f[FSREG >> 1]) }, float32_t{ u32(m_f[FTREG >> 1]) }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
|
|
default: // unimplemented operation
|
|
m_fcr31 |= FCR31_CE;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x11: // D
|
|
switch (op & 0x3f)
|
|
{
|
|
case 0x00: // ADD.D
|
|
set_cop1_reg(FDREG >> 1, f64_add(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }).v);
|
|
break;
|
|
case 0x01: // SUB.D
|
|
set_cop1_reg(FDREG >> 1, f64_sub(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }).v);
|
|
break;
|
|
case 0x02: // MUL.D
|
|
set_cop1_reg(FDREG >> 1, f64_mul(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }).v);
|
|
break;
|
|
case 0x03: // DIV.D
|
|
set_cop1_reg(FDREG >> 1, f64_div(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }).v);
|
|
break;
|
|
|
|
case 0x05: // ABS.D
|
|
if (f64_lt(float64_t{ m_f[FSREG >> 1] }, float64_t{ 0 }))
|
|
set_cop1_reg(FDREG >> 1, f64_mul(float64_t{ m_f[FSREG >> 1] }, i32_to_f64(-1)).v);
|
|
else
|
|
set_cop1_reg(FDREG >> 1, m_f[FSREG >> 1]);
|
|
break;
|
|
case 0x06: // MOV.D
|
|
m_f[FDREG >> 1] = m_f[FSREG >> 1];
|
|
break;
|
|
case 0x07: // NEG.D
|
|
set_cop1_reg(FDREG >> 1, f64_mul(float64_t{ m_f[FSREG >> 1] }, i32_to_f64(-1)).v);
|
|
break;
|
|
|
|
case 0x20: // CVT.S.D
|
|
set_cop1_reg(FDREG >> 1, f64_to_f32(float64_t{ m_f[FSREG >> 1] }).v);
|
|
break;
|
|
case 0x24: // CVT.W.D
|
|
set_cop1_reg(FDREG >> 1, f64_to_i32(float64_t{ m_f[FSREG >> 1] }, softfloat_roundingMode, true));
|
|
break;
|
|
|
|
case 0x30: // C.F.D (false)
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x31: // C.UN.D (unordered)
|
|
f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] });
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x32: // C.EQ.D (equal)
|
|
if (f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x33: // C.UEQ.D (unordered equal)
|
|
if (f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x34: // C.OLT.D (less than)
|
|
if (f64_lt(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x35: // C.ULT.D (unordered less than)
|
|
if (f64_lt(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }) || (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 (f64_le(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x37: // C.ULE.D (unordered less than or equal)
|
|
if (f64_le(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
|
|
case 0x38: // C.SF.D (signalling false)
|
|
f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] });
|
|
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x39: // C.NGLE.D (not greater, less than or equal)
|
|
f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] });
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_C | FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
break;
|
|
case 0x3a: // C.SEQ.D (signalling equal)
|
|
if (f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3b: // C.NGL.D (not greater or less than)
|
|
if (f64_eq(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3c: // C.LT.D (less than)
|
|
if (f64_lt(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3d: // C.NGE.D (not greater or equal)
|
|
if (f64_lt(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3e: // C.LE.D (less than or equal)
|
|
if (f64_le(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
case 0x3f: // C.NGT.D (not greater than)
|
|
if (f64_le(float64_t{ m_f[FSREG >> 1] }, float64_t{ m_f[FTREG >> 1] }) || (softfloat_exceptionFlags & softfloat_flag_invalid))
|
|
m_fcr31 |= FCR31_C;
|
|
else
|
|
m_fcr31 &= ~FCR31_C;
|
|
|
|
if (softfloat_exceptionFlags & softfloat_flag_invalid)
|
|
{
|
|
m_fcr31 |= FCR31_CV;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
}
|
|
break;
|
|
|
|
default: // unimplemented operation
|
|
m_fcr31 |= FCR31_CE;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x14: // W
|
|
switch (op & 0x3f)
|
|
{
|
|
case 0x20: // CVT.S.W
|
|
set_cop1_reg(FDREG >> 1, i32_to_f32(s32(m_f[FSREG >> 1])).v);
|
|
break;
|
|
case 0x21: // CVT.D.W
|
|
set_cop1_reg(FDREG >> 1, i32_to_f64(s32(m_f[FSREG >> 1])).v);
|
|
break;
|
|
|
|
default: // unimplemented operation
|
|
m_fcr31 |= FCR31_CE;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default: // unimplemented operation
|
|
m_fcr31 |= FCR31_CE;
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x31: // LWC1
|
|
load<u32>(SIMMVAL + m_r[RSREG],
|
|
[this, op](u32 data)
|
|
{
|
|
if (FTREG & 1)
|
|
// load the high half of the floating point register
|
|
m_f[FTREG >> 1] = (u64(data) << 32) | u32(m_f[FTREG >> 1]);
|
|
else
|
|
// load the low half of the floating point register
|
|
m_f[FTREG >> 1] = (m_f[FTREG >> 1] & ~0xffffffffULL) | data;
|
|
});
|
|
break;
|
|
case 0x39: // SWC1
|
|
if (FTREG & 1)
|
|
// store the high half of the floating point register
|
|
store<u32>(SIMMVAL + m_r[RSREG], m_f[FTREG >> 1] >> 32);
|
|
else
|
|
// store the low half of the floating point register
|
|
store<u32>(SIMMVAL + m_r[RSREG], m_f[FTREG >> 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
template <typename T> void mips1_device_base::set_cop1_reg(unsigned const reg, T 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))
|
|
{
|
|
execute_set_input(m_fpu_irq, ASSERT_LINE);
|
|
return;
|
|
}
|
|
|
|
// set flags
|
|
m_fcr31 |= ((m_fcr31 & FCR31_CM) >> 10);
|
|
}
|
|
|
|
if (sizeof(T) == 4)
|
|
m_f[reg] = (m_f[reg] & ~0xffffffffULL) | data;
|
|
else
|
|
m_f[reg] = data;
|
|
}
|
|
|
|
bool mips1_device_base::memory_translate(int spacenum, int intention, offs_t &address)
|
|
{
|
|
// check for kernel memory address
|
|
if (BIT(address, 31))
|
|
{
|
|
// check debug or kernel mode
|
|
if ((intention & TRANSLATE_DEBUG_MASK) || !(SR & SR_KUc))
|
|
{
|
|
switch (address & 0xe0000000)
|
|
{
|
|
case 0x80000000: // kseg0: unmapped, cached, privileged
|
|
case 0xa0000000: // kseg1: unmapped, uncached, privileged
|
|
address &= ~0xe0000000;
|
|
return true;
|
|
|
|
case 0xc0000000: // kseg2: mapped, cached, privileged
|
|
case 0xe0000000:
|
|
break;
|
|
}
|
|
}
|
|
else if (SR & SR_KUc)
|
|
{
|
|
if (!machine().side_effects_disabled())
|
|
{
|
|
// exception
|
|
m_cop0[COP0_BadVAddr] = address;
|
|
|
|
generate_exception((intention & TRANSLATE_WRITE) ? EXCEPTION_ADDRSTORE : EXCEPTION_ADDRLOAD);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// key is a combination of VPN and ASID
|
|
u32 const key = (address & EH_VPN) | (m_cop0[COP0_EntryHi] & EH_ASID);
|
|
|
|
unsigned *mru = m_tlb_mru[intention & TRANSLATE_TYPE_MASK];
|
|
|
|
bool refill = !BIT(address, 31);
|
|
bool modify = false;
|
|
|
|
for (unsigned i = 0; i < ARRAY_LENGTH(m_tlb); i++)
|
|
{
|
|
unsigned const index = mru[i];
|
|
u32 const *const entry = m_tlb[index];
|
|
|
|
// test vpn and optionally asid
|
|
u32 const mask = (entry[1] & EL_G) ? EH_VPN : EH_VPN | EH_ASID;
|
|
if ((entry[0] & mask) != (key & mask))
|
|
continue;
|
|
|
|
// test valid
|
|
if (!(entry[1] & EL_V))
|
|
{
|
|
refill = false;
|
|
break;
|
|
}
|
|
|
|
// test dirty
|
|
if ((intention & TRANSLATE_WRITE) && !(entry[1] & EL_D))
|
|
{
|
|
refill = false;
|
|
modify = true;
|
|
break;
|
|
}
|
|
|
|
// translate the address
|
|
address &= ~EH_VPN;
|
|
address |= (entry[1] & EL_PFN);
|
|
|
|
// promote the entry in the mru index
|
|
if (i > 0)
|
|
std::swap(mru[i - 1], mru[i]);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!machine().side_effects_disabled() && !(intention & TRANSLATE_DEBUG_MASK))
|
|
{
|
|
if (VERBOSE & LOG_TLB)
|
|
{
|
|
if (modify)
|
|
LOGMASKED(LOG_TLB, "tlb modify asid %d address 0x%08x (%s)\n",
|
|
(m_cop0[COP0_EntryHi] & EH_ASID) >> 6, address, machine().describe_context());
|
|
else
|
|
LOGMASKED(LOG_TLB, "tlb miss %c asid %d address 0x%08x (%s)\n",
|
|
(intention & TRANSLATE_WRITE) ? 'w' : 'r', (m_cop0[COP0_EntryHi] & EH_ASID) >> 6, address, machine().describe_context());
|
|
}
|
|
|
|
// load tlb exception registers
|
|
m_cop0[COP0_BadVAddr] = address;
|
|
m_cop0[COP0_EntryHi] = key;
|
|
m_cop0[COP0_Context] = (m_cop0[COP0_Context] & PTE_BASE) | ((address >> 10) & BAD_VPN);
|
|
|
|
generate_exception(modify ? EXCEPTION_TLBMOD : (intention & TRANSLATE_WRITE) ? EXCEPTION_TLBSTORE : EXCEPTION_TLBLOAD, refill);
|
|
}
|
|
|
|
return false;
|
|
}
|