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

Too many changes to describe in detail. A rewrite of the interrupt and dma code in the ioga, and fixes to the cpu and mmu were the most important things.
2035 lines
53 KiB
C++
2035 lines
53 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Patrick Mackinlay
|
|
|
|
/*
|
|
* An implementation of the Fairchild/Intergraph CLIPPER CPU family.
|
|
*
|
|
* Primary source: http://bitsavers.org/pdf/fairchild/clipper/Clipper_Instruction_Set_Oct85.pdf
|
|
*
|
|
* TODO:
|
|
* - unimplemented C400 instructions (cdb, cnvx[ds]w, loadts, waitd)
|
|
* - correct boot logic
|
|
* - instruction timing
|
|
* - big endian support (not present in the wild)
|
|
*/
|
|
|
|
#include "emu.h"
|
|
#include "debugger.h"
|
|
#include "clipper.h"
|
|
#include "clipperd.h"
|
|
|
|
#define LOG_GENERAL (1U << 0)
|
|
#define LOG_EXCEPTION (1U << 1)
|
|
#define LOG_SYSCALLS (1U << 2)
|
|
|
|
//#define VERBOSE (LOG_GENERAL | LOG_EXCEPTION)
|
|
#define VERBOSE (LOG_SYSCALLS)
|
|
|
|
#include "logmacro.h"
|
|
|
|
// convenience macros for frequently used instruction fields
|
|
#define R1 (m_info.r1)
|
|
#define R2 (m_info.r2)
|
|
|
|
// macros for setting psw condition codes
|
|
#define FLAGS(C,V,Z,N) \
|
|
m_psw = (m_psw & ~(PSW_C | PSW_V | PSW_Z | PSW_N)) | (((C) << 3) | ((V) << 2) | ((Z) << 1) | ((N) << 0));
|
|
#define FLAGS_CV(C,V) \
|
|
m_psw = (m_psw & ~(PSW_C | PSW_V)) | (((C) << 3) | ((V) << 2));
|
|
#define FLAGS_ZN(Z,N) \
|
|
m_psw = (m_psw & ~(PSW_Z | PSW_N)) | (((Z) << 1) | ((N) << 0));
|
|
|
|
// over/underflow for addition/subtraction from here: http://stackoverflow.com/questions/199333/how-to-detect-integer-overflow-in-c-c
|
|
#define OF_ADD(a, b) ((b > 0) && (a > INT32_MAX - b))
|
|
#define UF_ADD(a, b) ((b < 0) && (a < INT32_MIN - b))
|
|
#define OF_SUB(a, b) ((b < 0) && (a > INT32_MAX + b))
|
|
#define UF_SUB(a, b) ((b > 0) && (a < INT32_MIN + b))
|
|
|
|
// CLIPPER logic for carry and overflow flags
|
|
#define C_ADD(a, b) (u32(a) + u32(b) < u32(a))
|
|
#define V_ADD(a, b) (OF_ADD(s32(a), s32(b)) || UF_ADD(s32(a), s32(b)))
|
|
#define C_SUB(a, b) (u32(a) < u32(b))
|
|
#define V_SUB(a, b) (OF_SUB(s32(a), s32(b)) || UF_SUB(s32(a), s32(b)))
|
|
|
|
DEFINE_DEVICE_TYPE(CLIPPER_C100, clipper_c100_device, "clipper_c100", "C100 CLIPPER")
|
|
DEFINE_DEVICE_TYPE(CLIPPER_C300, clipper_c300_device, "clipper_c300", "C300 CLIPPER")
|
|
DEFINE_DEVICE_TYPE(CLIPPER_C400, clipper_c400_device, "clipper_c400", "C400 CLIPPER")
|
|
|
|
clipper_c100_device::clipper_c100_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: clipper_device(mconfig, CLIPPER_C100, tag, owner, clock, ENDIANNESS_LITTLE, SSW_ID_C1R1)
|
|
, m_icammu(*this, "^cammu_i")
|
|
, m_dcammu(*this, "^cammu_d")
|
|
{
|
|
}
|
|
|
|
clipper_c300_device::clipper_c300_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: clipper_device(mconfig, CLIPPER_C300, tag, owner, clock, ENDIANNESS_LITTLE, SSW_ID_C3R1)
|
|
, m_icammu(*this, "^cammu_i")
|
|
, m_dcammu(*this, "^cammu_d")
|
|
{
|
|
}
|
|
|
|
clipper_c400_device::clipper_c400_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: clipper_device(mconfig, CLIPPER_C400, tag, owner, clock, ENDIANNESS_LITTLE, SSW_ID_C4R4)
|
|
, m_cammu(*this, "^cammu")
|
|
{
|
|
}
|
|
|
|
clipper_device::clipper_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, const endianness_t endianness, const u32 cpuid)
|
|
: cpu_device(mconfig, type, tag, owner, clock)
|
|
, m_main_config("main", endianness, 32, 32, 0)
|
|
, m_io_config("io", endianness, 32, 32, 0)
|
|
, m_boot_config("boot", endianness, 32, 32, 0)
|
|
, m_icount(0)
|
|
, m_psw(endianness == ENDIANNESS_BIG ? PSW_BIG : 0)
|
|
, m_ssw(cpuid)
|
|
, m_r(m_rs)
|
|
{
|
|
}
|
|
|
|
// rotate helpers to replace MSVC intrinsics
|
|
inline u32 rotl32(u32 x, u8 shift)
|
|
{
|
|
shift &= 31;
|
|
return (x << shift) | (x >> ((32 - shift) & 31));
|
|
}
|
|
|
|
inline u32 rotr32(u32 x, u8 shift)
|
|
{
|
|
shift &= 31;
|
|
return (x >> shift) | (x << ((32 - shift) & 31));
|
|
}
|
|
|
|
inline u64 rotl64(u64 x, u8 shift)
|
|
{
|
|
shift &= 63;
|
|
return (x << shift) | (x >> ((64 - shift) & 63));
|
|
}
|
|
|
|
inline u64 rotr64(u64 x, u8 shift)
|
|
{
|
|
shift &= 63;
|
|
return (x >> shift) | (x << ((64 - shift) & 63));
|
|
}
|
|
|
|
void clipper_device::device_start()
|
|
{
|
|
// map spaces to system tags
|
|
std::vector<address_space *> spaces = { &space(0), &space(0), &space(0), &space(0), &space(1), &space(2), &space(0), &machine().dummy_space() };
|
|
|
|
// configure the cammu address spaces
|
|
get_icammu().set_spaces(spaces);
|
|
get_dcammu().set_spaces(spaces);
|
|
|
|
// set our instruction counter
|
|
set_icountptr(m_icount);
|
|
|
|
// program-visible cpu state
|
|
save_item(NAME(m_pc));
|
|
save_item(NAME(m_psw));
|
|
save_item(NAME(m_ssw));
|
|
save_item(NAME(m_ru));
|
|
save_item(NAME(m_rs));
|
|
save_item(NAME(m_f));
|
|
save_item(NAME(m_fp_pc));
|
|
save_item(NAME(m_fp_dst));
|
|
|
|
// non-visible cpu state
|
|
save_item(NAME(m_wait));
|
|
save_item(NAME(m_nmi));
|
|
save_item(NAME(m_irq));
|
|
save_item(NAME(m_ivec));
|
|
save_item(NAME(m_exception));
|
|
|
|
state_add(STATE_GENPC, "GENPC", m_pc).noshow();
|
|
state_add(STATE_GENPCBASE, "CURPC", m_pc).noshow();
|
|
state_add(STATE_GENFLAGS, "GENFLAGS", m_psw).mask(0xf).formatstr("%4s").noshow();
|
|
|
|
state_add(CLIPPER_PC, "pc", m_pc);
|
|
state_add(CLIPPER_PSW, "psw", m_psw);
|
|
state_add(CLIPPER_SSW, "ssw", m_ssw);
|
|
|
|
// integer regsters
|
|
for (int i = 0; i < get_ireg_count(); i++)
|
|
state_add(CLIPPER_UREG + i, util::string_format("ur%d", i).c_str(), m_ru[i]);
|
|
for (int i = 0; i < get_ireg_count(); i++)
|
|
state_add(CLIPPER_SREG + i, util::string_format("sr%d", i).c_str(), m_rs[i]);
|
|
|
|
// floating point registers
|
|
for (int i = 0; i < get_freg_count(); i++)
|
|
state_add(CLIPPER_FREG + i, util::string_format("f%d", i).c_str(), m_f[i]);
|
|
}
|
|
|
|
void clipper_c400_device::device_start()
|
|
{
|
|
clipper_device::device_start();
|
|
|
|
save_item(NAME(m_db_pc));
|
|
}
|
|
|
|
void clipper_device::device_reset()
|
|
{
|
|
/*
|
|
* From C300 documentation, on reset:
|
|
* psw: T cleared, BIG set from hardware, others undefined
|
|
* ssw: EI, TP, M, U, K, KU, UU, P cleared, ID set from hardware, others undefined
|
|
*/
|
|
|
|
// clear the psw and ssw
|
|
set_psw(0);
|
|
set_ssw(0);
|
|
|
|
// FIXME: figure out how to branch to the boot code properly
|
|
m_pc = 0x7f100000;
|
|
|
|
m_wait = false;
|
|
m_nmi = CLEAR_LINE;
|
|
m_irq = CLEAR_LINE;
|
|
m_ivec = 0;
|
|
m_exception = 0;
|
|
}
|
|
|
|
void clipper_device::state_string_export(const device_state_entry &entry, std::string &str) const
|
|
{
|
|
switch (entry.index())
|
|
{
|
|
case STATE_GENFLAGS:
|
|
str = string_format("%c%c%c%c",
|
|
PSW(C) ? 'C' : '.',
|
|
PSW(V) ? 'V' : '.',
|
|
PSW(Z) ? 'Z' : '.',
|
|
PSW(N) ? 'N' : '.');
|
|
break;
|
|
}
|
|
}
|
|
|
|
void clipper_device::execute_run()
|
|
{
|
|
// check for non-maskable and prioritised interrupts
|
|
if (m_nmi)
|
|
{
|
|
// acknowledge non-maskable interrupt
|
|
standard_irq_callback(INPUT_LINE_NMI);
|
|
|
|
LOGMASKED(LOG_EXCEPTION, "non-maskable interrupt\n");
|
|
m_pc = intrap(EXCEPTION_INTERRUPT_BASE, m_pc);
|
|
}
|
|
else if (SSW(EI) && m_irq)
|
|
{
|
|
LOGMASKED(LOG_EXCEPTION, "prioritised interrupt vector 0x%02x\n", m_ivec);
|
|
|
|
// allow equal/higher priority interrupts
|
|
if ((m_ivec & IVEC_LEVEL) <= SSW(IL))
|
|
{
|
|
// acknowledge interrupt
|
|
standard_irq_callback(INPUT_LINE_IRQ0);
|
|
|
|
m_pc = intrap(EXCEPTION_INTERRUPT_BASE + m_ivec * 8, m_pc);
|
|
|
|
LOGMASKED(LOG_EXCEPTION, "transferring control to vector 0x%02x address 0x%08x\n", m_ivec, m_pc);
|
|
}
|
|
}
|
|
|
|
while (m_icount > 0)
|
|
{
|
|
debugger_instruction_hook(m_pc);
|
|
|
|
if (m_wait)
|
|
{
|
|
m_icount = 0;
|
|
continue;
|
|
}
|
|
|
|
// fetch and decode an instruction
|
|
if (decode_instruction())
|
|
{
|
|
float_exception_flags = 0;
|
|
|
|
// execute instruction
|
|
execute_instruction();
|
|
|
|
// check floating point exceptions
|
|
if (float_exception_flags)
|
|
fp_exception();
|
|
}
|
|
|
|
if (m_exception)
|
|
{
|
|
debugger_exception_hook(m_exception);
|
|
|
|
/*
|
|
* For traced instructions which are interrupted or cause traps, the TP
|
|
* flag is set by hardware when the interrupt or trap occurs to ensure
|
|
* that the trace trap will occur immediately after the interrupt or other
|
|
* trap has been serviced.
|
|
*/
|
|
// FIXME: don't know why/when the trace pending flag is needed
|
|
if (PSW(T))
|
|
m_ssw |= SSW_TP;
|
|
|
|
switch (m_exception)
|
|
{
|
|
// data memory trap group
|
|
case EXCEPTION_D_CORRECTED_MEMORY_ERROR:
|
|
case EXCEPTION_D_UNCORRECTABLE_MEMORY_ERROR:
|
|
case EXCEPTION_D_ALIGNMENT_FAULT:
|
|
case EXCEPTION_D_PAGE_FAULT:
|
|
case EXCEPTION_D_READ_PROTECT_FAULT:
|
|
case EXCEPTION_D_WRITE_PROTECT_FAULT:
|
|
|
|
// instruction memory trap group
|
|
case EXCEPTION_I_CORRECTED_MEMORY_ERROR:
|
|
case EXCEPTION_I_UNCORRECTABLE_MEMORY_ERROR:
|
|
case EXCEPTION_I_ALIGNMENT_FAULT:
|
|
case EXCEPTION_I_PAGE_FAULT:
|
|
case EXCEPTION_I_EXECUTE_PROTECT_FAULT:
|
|
|
|
// illegal operation trap group
|
|
case EXCEPTION_ILLEGAL_OPERATION:
|
|
case EXCEPTION_PRIVILEGED_INSTRUCTION:
|
|
// return address is faulting instruction
|
|
m_pc = intrap(m_exception, m_info.pc);
|
|
break;
|
|
|
|
default:
|
|
// return address is following instruction
|
|
m_pc = intrap(m_exception, m_pc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// FIXME: trace trap logic not working properly yet
|
|
//else if (PSW(T))
|
|
// m_pc = intrap(EXCEPTION_TRACE, m_pc);
|
|
|
|
// FIXME: some instructions take longer (significantly) than one cycle
|
|
// and also the timings are often slower for the C100 and C300
|
|
m_icount -= 4;
|
|
}
|
|
}
|
|
|
|
void clipper_device::execute_set_input(int inputnum, int state)
|
|
{
|
|
if (state)
|
|
m_wait = false;
|
|
|
|
switch (inputnum)
|
|
{
|
|
case INPUT_LINE_IRQ0:
|
|
m_irq = state;
|
|
break;
|
|
|
|
case INPUT_LINE_NMI:
|
|
m_nmi = state;
|
|
break;
|
|
}
|
|
}
|
|
|
|
device_memory_interface::space_config_vector clipper_device::memory_space_config() const
|
|
{
|
|
return space_config_vector {
|
|
std::make_pair(0, &m_main_config),
|
|
std::make_pair(1, &m_io_config),
|
|
std::make_pair(2, &m_boot_config)
|
|
};
|
|
}
|
|
|
|
bool clipper_device::memory_translate(int spacenum, int intention, offs_t &address)
|
|
{
|
|
return ((intention & TRANSLATE_TYPE_MASK) == TRANSLATE_FETCH ? get_icammu() : get_dcammu()).memory_translate(m_ssw, spacenum, intention, address);
|
|
}
|
|
|
|
WRITE16_MEMBER(clipper_device::set_exception)
|
|
{
|
|
LOGMASKED(LOG_EXCEPTION, "external exception 0x%04x triggered\n", data);
|
|
|
|
// check if corrected memory errors are masked
|
|
if (!SSW(ECM) && (data == EXCEPTION_D_CORRECTED_MEMORY_ERROR || data == EXCEPTION_I_CORRECTED_MEMORY_ERROR))
|
|
return;
|
|
|
|
m_exception = data;
|
|
}
|
|
|
|
/*
|
|
* Fetch and decode an instruction and compute an effective address (for
|
|
* instructions with addressing modes). The results are contained in the m_info
|
|
* structure to simplify passing between here and execute_instruction().
|
|
*/
|
|
bool clipper_device::decode_instruction()
|
|
{
|
|
// record the current instruction address
|
|
m_info.pc = m_pc;
|
|
|
|
// fetch and decode the primary parcel
|
|
if (!get_icammu().fetch<u16>(m_ssw, m_pc + 0, [this](u16 insn) {
|
|
m_info.opcode = insn >> 8;
|
|
m_info.subopcode = insn & 0xff;
|
|
m_info.r1 = (insn & 0x00f0) >> 4;
|
|
m_info.r2 = insn & 0x000f;
|
|
}))
|
|
return false;
|
|
|
|
// initialise the other fields
|
|
m_info.imm = 0;
|
|
m_info.macro = 0;
|
|
m_info.address = 0;
|
|
|
|
// default instruction size is 2 bytes
|
|
int size = 2;
|
|
|
|
if ((m_info.opcode & 0xf8) == 0x38)
|
|
{
|
|
// fetch 16 bit immediate and sign extend
|
|
if (!get_icammu().fetch<s16>(m_ssw, m_pc + 2, [this](s32 v) { m_info.imm = v; }))
|
|
return false;
|
|
size = 4;
|
|
}
|
|
else if ((m_info.opcode & 0xd3) == 0x83)
|
|
{
|
|
// instruction has an immediate operand, either 16 or 32 bit
|
|
if (m_info.subopcode & 0x80)
|
|
{
|
|
// fetch 16 bit immediate and sign extend
|
|
if (!get_icammu().fetch<s16>(m_ssw, m_pc + 2, [this](s32 v) { m_info.imm = v; }))
|
|
return false;
|
|
size = 4;
|
|
}
|
|
else
|
|
{
|
|
// fetch 32 bit immediate
|
|
if (!get_icammu().fetch<u32>(m_ssw, m_pc + 2, [this](u32 v) { m_info.imm = v; }))
|
|
return false;
|
|
size = 6;
|
|
}
|
|
}
|
|
else if ((m_info.opcode & 0xc0) == 0x40)
|
|
{
|
|
// instructions with addresses
|
|
if (m_info.opcode & 0x01)
|
|
{
|
|
// instructions with complex modes
|
|
switch (m_info.subopcode & 0xf0)
|
|
{
|
|
case ADDR_MODE_PC32:
|
|
if (!get_icammu().fetch<u32>(m_ssw, m_pc + 2, [this](u32 v) { m_info.address = m_pc + v; }))
|
|
return false;
|
|
size = 6;
|
|
break;
|
|
|
|
case ADDR_MODE_ABS32:
|
|
if (!get_icammu().fetch<u32>(m_ssw, m_pc + 2, [this](u32 v) { m_info.address = v; }))
|
|
return false;
|
|
size = 6;
|
|
break;
|
|
|
|
case ADDR_MODE_REL32:
|
|
if (!get_icammu().fetch<u16>(m_ssw, m_pc + 2, [this](u16 v) { m_info.r2 = v & 0xf; }))
|
|
return false;
|
|
|
|
if (!get_icammu().fetch<u32>(m_ssw, m_pc + 4, [this](u32 v) { m_info.address = m_r[m_info.subopcode & 0xf] + v; }))
|
|
return false;
|
|
size = 8;
|
|
break;
|
|
|
|
case ADDR_MODE_PC16:
|
|
if (!get_icammu().fetch<s16>(m_ssw, m_pc + 2, [this](s16 v) { m_info.address = m_pc + v; }))
|
|
return false;
|
|
size = 4;
|
|
break;
|
|
|
|
case ADDR_MODE_REL12:
|
|
if (!get_icammu().fetch<s16>(m_ssw, m_pc + 2, [this](s16 v) {
|
|
m_info.r2 = v & 0xf;
|
|
m_info.address = m_r[m_info.subopcode & 0xf] + (v >> 4);
|
|
}))
|
|
return false;
|
|
size = 4;
|
|
break;
|
|
|
|
case ADDR_MODE_ABS16:
|
|
if (!get_icammu().fetch<s16>(m_ssw, m_pc + 2, [this](s32 v) { m_info.address = v; }))
|
|
return false;
|
|
size = 4;
|
|
break;
|
|
|
|
case ADDR_MODE_PCX:
|
|
if (!get_icammu().fetch<u16>(m_ssw, m_pc + 2, [this](u16 v) {
|
|
m_info.r2 = v & 0xf;
|
|
m_info.address = m_pc + m_r[(v >> 4) & 0xf];
|
|
}))
|
|
return false;
|
|
size = 4;
|
|
break;
|
|
|
|
case ADDR_MODE_RELX:
|
|
if (!get_icammu().fetch<u16>(m_ssw, m_pc + 2, [this](u16 v) {
|
|
m_info.r2 = v & 0xf;
|
|
m_info.address = m_r[m_info.subopcode & 0xf] + m_r[(v >> 4) & 0xf];
|
|
}))
|
|
return false;
|
|
size = 4;
|
|
break;
|
|
|
|
default:
|
|
m_exception = EXCEPTION_ILLEGAL_OPERATION;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
// relative addressing mode
|
|
m_info.address = m_r[m_info.r1];
|
|
}
|
|
else if ((m_info.opcode & 0xfd) == 0xb4)
|
|
{
|
|
// macro instructions
|
|
if (!get_icammu().fetch<u16>(m_ssw, m_pc + 2, [this](u16 v) { m_info.macro = v; }))
|
|
return false;
|
|
size = 4;
|
|
}
|
|
|
|
// instruction fetch and decode complete
|
|
m_pc = m_pc + size;
|
|
|
|
return true;
|
|
}
|
|
|
|
void clipper_device::execute_instruction()
|
|
{
|
|
switch (m_info.opcode)
|
|
{
|
|
case 0x00: // noop
|
|
break;
|
|
|
|
case 0x10:
|
|
// movwp: move word to processor register
|
|
// treated as a noop if target ssw in user mode
|
|
// R1 == 3 means "fast" mode - avoids pipeline flush
|
|
if (R1 == 0)
|
|
set_psw(m_r[R2]);
|
|
else if (!SSW(U) && (R1 == 1 || R1 == 3))
|
|
set_ssw(m_r[R2]);
|
|
// FLAGS: CVZN
|
|
break;
|
|
case 0x11:
|
|
// movpw: move processor register to word
|
|
switch (R1)
|
|
{
|
|
case 0: m_r[R2] = m_psw; break;
|
|
case 1: m_r[R2] = m_ssw; break;
|
|
}
|
|
break;
|
|
case 0x12:
|
|
// calls: call supervisor
|
|
m_exception = EXCEPTION_SUPERVISOR_CALL_BASE + (m_info.subopcode & 0x7f) * 8;
|
|
if (VERBOSE & LOG_SYSCALLS)
|
|
switch (m_info.subopcode & 0x7f)
|
|
{
|
|
case 0x3b: // execve
|
|
LOGMASKED(LOG_SYSCALLS, "execve(\"%s\", [ %s ], envp)\n",
|
|
debug_string(m_r[0]), debug_string_array(m_r[1]));
|
|
break;
|
|
}
|
|
break;
|
|
case 0x13:
|
|
// ret: return from subroutine
|
|
get_dcammu().load<u32>(m_ssw, m_r[R2], [this](u32 v) {
|
|
m_pc = v;
|
|
m_r[R2] += 4;
|
|
});
|
|
// TRAPS: C,U,A,P,R
|
|
break;
|
|
case 0x14:
|
|
// pushw: push word
|
|
get_dcammu().store<u32>(m_ssw, m_r[R1] - 4, m_r[R2]);
|
|
m_r[R1] -= 4;
|
|
// TRAPS: A,P,W
|
|
break;
|
|
|
|
case 0x16:
|
|
// popw: pop word
|
|
get_dcammu().load<u32>(m_ssw, m_r[R1], [this](u32 v) {
|
|
m_r[R1] += 4;
|
|
m_r[R2] = v;
|
|
});
|
|
// TRAPS: C,U,A,P,R
|
|
break;
|
|
|
|
case 0x20:
|
|
// adds: add single floating
|
|
set_fp(R2, float32_add(get_fp32(R2), get_fp32(R1)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x21:
|
|
// subs: subtract single floating
|
|
set_fp(R2, float32_sub(get_fp32(R2), get_fp32(R1)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x22:
|
|
// addd: add double floating
|
|
set_fp(R2, float64_add(get_fp64(R2), get_fp64(R1)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x23:
|
|
// subd: subtract double floating
|
|
set_fp(R2, float64_sub(get_fp64(R2), get_fp64(R1)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x24:
|
|
// movs: move single floating
|
|
set_fp(R2, get_fp32(R1), F_NONE);
|
|
break;
|
|
case 0x25:
|
|
// cmps: compare single floating
|
|
FLAGS(0, 0, float32_eq(get_fp32(R2), get_fp32(R1)), float32_lt(get_fp32(R2), get_fp32(R1)))
|
|
// flag unordered
|
|
if (float_exception_flags & float_flag_invalid)
|
|
m_psw |= PSW_Z | PSW_N;
|
|
float_exception_flags &= F_NONE;
|
|
break;
|
|
case 0x26:
|
|
// movd: move double floating
|
|
set_fp(R2, get_fp64(R1), F_NONE);
|
|
break;
|
|
case 0x27:
|
|
// cmpd: compare double floating
|
|
FLAGS(0, 0, float64_eq(get_fp64(R2), get_fp64(R1)), float64_lt(get_fp64(R2), get_fp64(R1)))
|
|
// flag unordered
|
|
if (float_exception_flags & float_flag_invalid)
|
|
m_psw |= PSW_Z | PSW_N;
|
|
float_exception_flags &= F_NONE;
|
|
break;
|
|
case 0x28:
|
|
// muls: multiply single floating
|
|
set_fp(R2, float32_mul(get_fp32(R2), get_fp32(R1)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x29:
|
|
// divs: divide single floating
|
|
set_fp(R2, float32_div(get_fp32(R2), get_fp32(R1)), F_IVDUX);
|
|
// TRAPS: F_IVDUX
|
|
break;
|
|
case 0x2a:
|
|
// muld: multiply double floating
|
|
set_fp(R2, float64_mul(get_fp64(R2), get_fp64(R1)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x2b:
|
|
// divd: divide double floating
|
|
set_fp(R2, float64_div(get_fp64(R2), get_fp64(R1)), F_IVDUX);
|
|
// TRAPS: F_IVDUX
|
|
break;
|
|
case 0x2c:
|
|
// movsw: move single floating to word
|
|
m_r[R2] = get_fp32(R1);
|
|
break;
|
|
case 0x2d:
|
|
// movws: move word to single floating
|
|
set_fp(R2, m_r[R1], F_NONE);
|
|
break;
|
|
case 0x2e:
|
|
// movdl: move double floating to longword
|
|
set_64(R2, get_fp64(R1));
|
|
break;
|
|
case 0x2f:
|
|
// movld: move longword to double floating
|
|
set_fp(R2, get_64(R1), F_NONE);
|
|
break;
|
|
case 0x30:
|
|
// shaw: shift arithmetic word
|
|
if (s32(m_r[R1]) > 0)
|
|
{
|
|
// save the bits that will be shifted out plus new sign bit
|
|
const s32 v = s32(m_r[R2]) >> (31 - m_r[R1]);
|
|
|
|
m_r[R2] <<= m_r[R1];
|
|
|
|
// overflow is set if sign changes during shift
|
|
FLAGS(0, v != 0 && v != -1, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
}
|
|
else
|
|
{
|
|
m_r[R2] = s32(m_r[R2]) >> -m_r[R1];
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
}
|
|
// FLAGS: 0VZN
|
|
break;
|
|
case 0x31:
|
|
// shal: shift arithmetic longword
|
|
if (s32(m_r[R1]) > 0)
|
|
{
|
|
// save the bits that will be shifted out plus new sign bit
|
|
const s64 v = s64(get_64(R2)) >> (63 - m_r[R1]);
|
|
|
|
set_64(R2, get_64(R2) << m_r[R1]);
|
|
|
|
// overflow is set if sign changes during shift
|
|
FLAGS(0, v != 0 && v != -1, get_64(R2) == 0, s64(get_64(R2)) < 0)
|
|
}
|
|
else
|
|
{
|
|
set_64(R2, s64(get_64(R2)) >> -m_r[R1]);
|
|
FLAGS(0, 0, get_64(R2) == 0, s64(get_64(R2)) < 0)
|
|
}
|
|
// FLAGS: 0VZN
|
|
break;
|
|
case 0x32:
|
|
// shlw: shift logical word
|
|
if (s32(m_r[R1]) > 0)
|
|
m_r[R2] <<= m_r[R1];
|
|
else
|
|
m_r[R2] >>= -m_r[R1];
|
|
// FLAGS: 00ZN
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0);
|
|
break;
|
|
case 0x33:
|
|
// shll: shift logical longword
|
|
if (s32(m_r[R1]) > 0)
|
|
set_64(R2, get_64(R2) << m_r[R1]);
|
|
else
|
|
set_64(R2, get_64(R2) >> -m_r[R1]);
|
|
// FLAGS: 00ZN
|
|
FLAGS(0, 0, get_64(R2) == 0, s64(get_64(R2)) < 0);
|
|
break;
|
|
case 0x34:
|
|
// rotw: rotate word
|
|
if (s32(m_r[R1]) > 0)
|
|
m_r[R2] = rotl32(m_r[R2], m_r[R1]);
|
|
else
|
|
m_r[R2] = rotr32(m_r[R2], -m_r[R1]);
|
|
// FLAGS: 00ZN
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0);
|
|
break;
|
|
case 0x35:
|
|
// rotl: rotate longword
|
|
if (s32(m_r[R1]) > 0)
|
|
set_64(R2, rotl64(get_64(R2), m_r[R1]));
|
|
else
|
|
set_64(R2, rotr64(get_64(R2), -m_r[R1]));
|
|
// FLAGS: 00ZN
|
|
FLAGS(0, 0, get_64(R2) == 0, s64(get_64(R2)) < 0);
|
|
break;
|
|
|
|
case 0x38:
|
|
// shai: shift arithmetic immediate
|
|
if (s32(m_info.imm) > 0)
|
|
{
|
|
// save the bits that will be shifted out plus new sign bit
|
|
const s32 v = s32(m_r[R2]) >> (31 - m_info.imm);
|
|
|
|
m_r[R2] <<= m_info.imm;
|
|
|
|
// overflow is set if sign changes during shift
|
|
FLAGS(0, v != 0 && v != -1, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
}
|
|
else
|
|
{
|
|
m_r[R2] = s32(m_r[R2]) >> -m_info.imm;
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
}
|
|
// FLAGS: 0VZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x39:
|
|
// shali: shift arithmetic longword immediate
|
|
if (s32(m_info.imm) > 0)
|
|
{
|
|
// save the bits that will be shifted out plus new sign bit
|
|
const s64 v = s64(get_64(R2)) >> (63 - m_info.imm);
|
|
|
|
set_64(R2, get_64(R2) << m_info.imm);
|
|
|
|
// overflow is set if sign changes during shift
|
|
FLAGS(0, v != 0 && v != -1, get_64(R2) == 0, s64(get_64(R2)) < 0)
|
|
}
|
|
else
|
|
{
|
|
set_64(R2, s64(get_64(R2)) >> -m_info.imm);
|
|
FLAGS(0, 0, get_64(R2) == 0, s64(get_64(R2)) < 0)
|
|
}
|
|
// FLAGS: 0VZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x3a:
|
|
// shli: shift logical immediate
|
|
if (s32(m_info.imm) > 0)
|
|
m_r[R2] <<= m_info.imm;
|
|
else
|
|
m_r[R2] >>= -m_info.imm;
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0);
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x3b:
|
|
// shlli: shift logical longword immediate
|
|
if (s32(m_info.imm) > 0)
|
|
set_64(R2, get_64(R2) << m_info.imm);
|
|
else
|
|
set_64(R2, get_64(R2) >> -m_info.imm);
|
|
FLAGS(0, 0, get_64(R2) == 0, s64(get_64(R2)) < 0);
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x3c:
|
|
// roti: rotate immediate
|
|
if (s32(m_info.imm) > 0)
|
|
m_r[R2] = rotl32(m_r[R2], m_info.imm);
|
|
else
|
|
m_r[R2] = rotr32(m_r[R2], -m_info.imm);
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0);
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x3d:
|
|
// rotli: rotate longword immediate
|
|
if (s32(m_info.imm) > 0)
|
|
set_64(R2, rotl64(get_64(R2), m_info.imm));
|
|
else
|
|
set_64(R2, rotr64(get_64(R2), -m_info.imm));
|
|
FLAGS(0, 0, get_64(R2) == 0, s64(get_64(R2)) < 0);
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
|
|
case 0x44:
|
|
case 0x45:
|
|
// call: call subroutine
|
|
if (get_dcammu().store<u32>(m_ssw, m_r[R2] - 4, m_pc))
|
|
{
|
|
m_pc = m_info.address;
|
|
m_r[R2] -= 4;
|
|
}
|
|
// TRAPS: A,P,W
|
|
break;
|
|
|
|
case 0x48:
|
|
case 0x49:
|
|
// b*: branch on condition
|
|
if (evaluate_branch())
|
|
m_pc = m_info.address;
|
|
// TRAPS: A,I
|
|
break;
|
|
|
|
case 0x4c:
|
|
case 0x4d:
|
|
// bf*: branch on floating exception
|
|
// FIXME: documentation is not clear, implementation is guesswork
|
|
switch (R2)
|
|
{
|
|
case BF_ANY:
|
|
// bfany: floating any exception
|
|
if (m_psw & (PSW_FI | PSW_FV | PSW_FD | PSW_FU | PSW_FX))
|
|
m_pc = m_info.address;
|
|
break;
|
|
case BF_BAD:
|
|
// bfbad: floating bad result
|
|
if (m_psw & (PSW_FI | PSW_FD))
|
|
m_pc = m_info.address;
|
|
break;
|
|
default:
|
|
// reserved
|
|
// FIXME: not sure if this should trigger an exception?
|
|
m_exception = EXCEPTION_ILLEGAL_OPERATION;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x60:
|
|
case 0x61:
|
|
// loadw: load word
|
|
get_dcammu().load<u32>(m_ssw, m_info.address, [this](u32 v) { m_r[R2] = v; });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x62:
|
|
case 0x63:
|
|
// loada: load address
|
|
m_r[R2] = m_info.address;
|
|
// TRAPS: I
|
|
break;
|
|
case 0x64:
|
|
case 0x65:
|
|
// loads: load single floating
|
|
get_dcammu().load<float32>(m_ssw, m_info.address, [this](float32 v) { set_fp(R2, v, F_NONE); });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x66:
|
|
case 0x67:
|
|
// loadd: load double floating
|
|
get_dcammu().load<float64>(m_ssw, m_info.address, [this](float64 v) { set_fp(R2, float64(v), F_NONE); });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x68:
|
|
case 0x69:
|
|
// loadb: load byte
|
|
get_dcammu().load<s8>(m_ssw, m_info.address, [this](s32 v) { m_r[R2] = v; });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x6a:
|
|
case 0x6b:
|
|
// loadbu: load byte unsigned
|
|
get_dcammu().load<u8>(m_ssw, m_info.address, [this](u32 v) { m_r[R2] = v; });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x6c:
|
|
case 0x6d:
|
|
// loadh: load halfword
|
|
get_dcammu().load<s16>(m_ssw, m_info.address, [this](s32 v) { m_r[R2] = v; });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x6e:
|
|
case 0x6f:
|
|
// loadhu: load halfword unsigned
|
|
get_dcammu().load<u16>(m_ssw, m_info.address, [this](u32 v) { m_r[R2] = v; });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
case 0x70:
|
|
case 0x71:
|
|
// storw: store word
|
|
get_dcammu().store<u32>(m_ssw, m_info.address, m_r[R2]);
|
|
// TRAPS: A,P,W,I
|
|
break;
|
|
case 0x72:
|
|
case 0x73:
|
|
// tsts: test and set
|
|
get_dcammu().modify<u32>(m_ssw, m_info.address, [this](u32 v) {
|
|
m_r[R2] = v;
|
|
return v | 0x80000000U;
|
|
});
|
|
// TRAPS: C,U,A,P,R,W,I
|
|
break;
|
|
case 0x74:
|
|
case 0x75:
|
|
// stors: store single floating
|
|
get_dcammu().store<float32>(m_ssw, m_info.address, get_fp32(R2));
|
|
// TRAPS: A,P,W,I
|
|
break;
|
|
case 0x76:
|
|
case 0x77:
|
|
// stord: store double floating
|
|
get_dcammu().store<float64>(m_ssw, m_info.address, get_fp64(R2));
|
|
// TRAPS: A,P,W,I
|
|
break;
|
|
case 0x78:
|
|
case 0x79:
|
|
// storb: store byte
|
|
get_dcammu().store<u8>(m_ssw, m_info.address, m_r[R2]);
|
|
// TRAPS: A,P,W,I
|
|
break;
|
|
|
|
case 0x7c:
|
|
case 0x7d:
|
|
// storh: store halfword
|
|
get_dcammu().store<u16>(m_ssw, m_info.address, m_r[R2]);
|
|
// TRAPS: A,P,W,I
|
|
break;
|
|
|
|
case 0x80:
|
|
// addw: add word
|
|
FLAGS_CV(C_ADD(m_r[R2], m_r[R1]), V_ADD(m_r[R2], m_r[R1]))
|
|
m_r[R2] += m_r[R1];
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
|
|
case 0x82:
|
|
// addq: add quick
|
|
FLAGS_CV(C_ADD(m_r[R2], R1), V_ADD(m_r[R2], R1))
|
|
m_r[R2] += R1;
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
case 0x83:
|
|
// addi: add immediate
|
|
FLAGS_CV(C_ADD(m_r[R2], m_info.imm), V_ADD(m_r[R2], m_info.imm))
|
|
m_r[R2] += m_info.imm;
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x84:
|
|
// movw: move word
|
|
m_r[R2] = m_r[R1];
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
break;
|
|
|
|
case 0x86:
|
|
// loadq: load quick
|
|
m_r[R2] = R1;
|
|
FLAGS(0, 0, m_r[R2] == 0, 0)
|
|
// FLAGS: 00Z0
|
|
break;
|
|
case 0x87:
|
|
// loadi: load immediate
|
|
m_r[R2] = m_info.imm;
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x88:
|
|
// andw: and word
|
|
m_r[R2] &= m_r[R1];
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
break;
|
|
|
|
case 0x8b:
|
|
// andi: and immediate
|
|
m_r[R2] &= m_info.imm;
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x8c:
|
|
// orw: or word
|
|
m_r[R2] |= m_r[R1];
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
break;
|
|
|
|
case 0x8f:
|
|
// ori: or immediate
|
|
m_r[R2] |= m_info.imm;
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0x90:
|
|
// addwc: add word with carry
|
|
FLAGS_CV(C_ADD(m_r[R2], (m_r[R1] + (PSW(C) ? 1 : 0))), V_ADD(m_r[R2], (m_r[R1] + (PSW(C) ? 1 : 0))))
|
|
m_r[R2] += m_r[R1] + (PSW(C) ? 1 : 0);
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
case 0x91:
|
|
// subwc: subtract word with carry
|
|
FLAGS_CV(C_SUB(m_r[R2], (m_r[R1] + (PSW(C) ? 1 : 0))), V_SUB(m_r[R2], (m_r[R1] + (PSW(C) ? 1 : 0))))
|
|
m_r[R2] -= m_r[R1] + (PSW(C) ? 1 : 0);
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
|
|
case 0x93:
|
|
// negw: negate word
|
|
FLAGS_CV(m_r[R1] != 0, s32(m_r[R1]) == INT32_MIN)
|
|
m_r[R2] = -m_r[R1];
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
|
|
case 0x98:
|
|
// mulw: multiply word
|
|
{
|
|
const s64 product = mul_32x32(m_r[R1], m_r[R2]);
|
|
m_r[R2] = s32(product);
|
|
FLAGS(0, (u64(product) >> 32) != (BIT(product, 31) ? ~u32(0) : 0), 0, 0)
|
|
// FLAGS: 0V00
|
|
}
|
|
break;
|
|
case 0x99:
|
|
// mulwx: multiply word extended
|
|
{
|
|
const s64 product = mul_32x32(m_r[R1], m_r[R2]);
|
|
set_64(R2, product);
|
|
FLAGS(0, (u64(product) >> 32) != (BIT(product, 31) ? ~u32(0) : 0), 0, 0)
|
|
// FLAGS: 0V00
|
|
}
|
|
break;
|
|
case 0x9a:
|
|
// mulwu: multiply word unsigned
|
|
{
|
|
const u64 product = mulu_32x32(m_r[R1], m_r[R2]);
|
|
m_r[R2] = u32(product);
|
|
FLAGS(0, (product >> 32) != 0, 0, 0)
|
|
// FLAGS: 0V00
|
|
}
|
|
break;
|
|
case 0x9b:
|
|
// mulwux: multiply word unsigned extended
|
|
{
|
|
const u64 product = mulu_32x32(m_r[R1], m_r[R2]);
|
|
set_64(R2, product);
|
|
FLAGS(0, (product >> 32) != 0, 0, 0)
|
|
// FLAGS: 0V00
|
|
}
|
|
break;
|
|
case 0x9c:
|
|
// divw: divide word
|
|
if (m_r[R1] != 0)
|
|
{
|
|
// FLAGS: 0V00
|
|
FLAGS(0, s32(m_r[R2]) == INT32_MIN && s32(m_r[R1]) == -1, 0, 0)
|
|
m_r[R2] = s32(m_r[R2]) / s32(m_r[R1]);
|
|
}
|
|
else
|
|
// TRAPS: D
|
|
m_exception = EXCEPTION_INTEGER_DIVIDE_BY_ZERO;
|
|
break;
|
|
case 0x9d:
|
|
// modw: modulus word
|
|
if (m_r[R1] != 0)
|
|
{
|
|
// FLAGS: 0V00
|
|
FLAGS(0, s32(m_r[R2]) == INT32_MIN && s32(m_r[R1]) == -1, 0, 0)
|
|
m_r[R2] = s32(m_r[R2]) % s32(m_r[R1]);
|
|
}
|
|
else
|
|
// TRAPS: D
|
|
m_exception = EXCEPTION_INTEGER_DIVIDE_BY_ZERO;
|
|
break;
|
|
case 0x9e:
|
|
// divwu: divide word unsigned
|
|
if (m_r[R1] != 0)
|
|
{
|
|
m_r[R2] = m_r[R2] / m_r[R1];
|
|
// FLAGS: 0000
|
|
FLAGS(0, 0, 0, 0)
|
|
}
|
|
else
|
|
// TRAPS: D
|
|
m_exception = EXCEPTION_INTEGER_DIVIDE_BY_ZERO;
|
|
break;
|
|
case 0x9f:
|
|
// modwu: modulus word unsigned
|
|
if (m_r[R1] != 0)
|
|
{
|
|
m_r[R2] = m_r[R2] % m_r[R1];
|
|
// FLAGS: 0000
|
|
FLAGS(0, 0, 0, 0)
|
|
}
|
|
else
|
|
// TRAPS: D
|
|
m_exception = EXCEPTION_INTEGER_DIVIDE_BY_ZERO;
|
|
break;
|
|
case 0xa0:
|
|
// subw: subtract word
|
|
FLAGS_CV(C_SUB(m_r[R2], m_r[R1]), V_SUB(m_r[R2], m_r[R1]))
|
|
m_r[R2] -= m_r[R1];
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
|
|
case 0xa2:
|
|
// subq: subtract quick
|
|
FLAGS_CV(C_SUB(m_r[R2], R1), V_SUB(m_r[R2], R1))
|
|
m_r[R2] -= R1;
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
break;
|
|
case 0xa3:
|
|
// subi: subtract immediate
|
|
FLAGS_CV(C_SUB(m_r[R2], m_info.imm), V_SUB(m_r[R2], m_info.imm))
|
|
m_r[R2] -= m_info.imm;
|
|
FLAGS_ZN(m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: CVZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0xa4:
|
|
// cmpw: compare word
|
|
FLAGS(C_SUB(m_r[R2], m_r[R1]), V_SUB(m_r[R2], m_r[R1]), m_r[R2] == m_r[R1], s32(m_r[R2]) < s32(m_r[R1]))
|
|
// FLAGS: CVZN
|
|
break;
|
|
|
|
case 0xa6:
|
|
// cmpq: compare quick
|
|
FLAGS(C_SUB(m_r[R2], R1), V_SUB(m_r[R2], R1), m_r[R2] == R1, s32(m_r[R2]) < s32(R1))
|
|
// FLAGS: CVZN
|
|
break;
|
|
case 0xa7:
|
|
// cmpi: compare immediate
|
|
FLAGS(C_SUB(m_r[R2], m_info.imm), V_SUB(m_r[R2], m_info.imm), m_r[R2] == m_info.imm, s32(m_r[R2]) < s32(m_info.imm))
|
|
// FLAGS: CVZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0xa8:
|
|
// xorw: exclusive or word
|
|
m_r[R2] ^= m_r[R1];
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
break;
|
|
|
|
case 0xab:
|
|
// xori: exclusive or immediate
|
|
m_r[R2] ^= m_info.imm;
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
// TRAPS: I
|
|
break;
|
|
case 0xac:
|
|
// notw: not word
|
|
m_r[R2] = ~m_r[R1];
|
|
FLAGS(0, 0, m_r[R2] == 0, s32(m_r[R2]) < 0)
|
|
// FLAGS: 00ZN
|
|
break;
|
|
|
|
case 0xae:
|
|
// notq: not quick
|
|
m_r[R2] = ~R1;
|
|
FLAGS(0, 0, 0, 1)
|
|
// FLAGS: 0001
|
|
break;
|
|
|
|
case 0xb4:
|
|
// unprivileged macro instructions
|
|
switch (m_info.subopcode)
|
|
{
|
|
case 0x00: case 0x01: case 0x02: case 0x03:
|
|
case 0x04: case 0x05: case 0x06: case 0x07:
|
|
case 0x08: case 0x09: case 0x0a: case 0x0b:
|
|
case 0x0c:
|
|
// savew0..savew12: push registers rN:r14
|
|
|
|
// store ri at sp - 4 * (15 - i)
|
|
for (int i = R2; i < 15 && !m_exception; i++)
|
|
get_dcammu().store<u32>(m_ssw, m_r[15] - 4 * (15 - i), m_r[i]);
|
|
|
|
// decrement sp after push to allow restart on exceptions
|
|
if (!m_exception)
|
|
m_r[15] -= 4 * (15 - R2);
|
|
// TRAPS: A,P,W
|
|
break;
|
|
// NOTE: the movc, initc and cmpc macro instructions are implemented in a very basic way because
|
|
// at some point they will need to be improved to deal with possible exceptions (e.g. page faults)
|
|
// that may occur during execution. The implementation here is intended to allow the instructions
|
|
// to be "continued" after such exceptions.
|
|
case 0x0d:
|
|
// movc: copy r0 bytes from r1 to r2
|
|
|
|
while (m_r[0])
|
|
{
|
|
get_dcammu().load<u8>(m_ssw, m_r[1], [this](u8 byte) { get_dcammu().store<u8>(m_ssw, m_r[2], byte); });
|
|
|
|
if (m_exception)
|
|
break;
|
|
|
|
m_r[0]--;
|
|
m_r[1]++;
|
|
m_r[2]++;
|
|
}
|
|
// TRAPS: C,U,P,R,W
|
|
break;
|
|
case 0x0e:
|
|
// initc: initialise r0 bytes at r1 with value in r2
|
|
while (m_r[0])
|
|
{
|
|
if (!get_dcammu().store<u8>(m_ssw, m_r[1], m_r[2]))
|
|
break;
|
|
|
|
m_r[0]--;
|
|
m_r[1]++;
|
|
m_r[2] = rotr32(m_r[2], 8);
|
|
}
|
|
// TRAPS: P,W
|
|
break;
|
|
case 0x0f:
|
|
// cmpc: compare r0 bytes at r1 with r2
|
|
|
|
// set condition codes assuming strings match
|
|
FLAGS(0, 0, 1, 0);
|
|
|
|
while (m_r[0])
|
|
{
|
|
// read and compare bytes (as signed 32 bit integers)
|
|
get_dcammu().load<s8>(m_ssw, m_r[1], [this](s32 byte1) {
|
|
get_dcammu().load<s8>(m_ssw, m_r[2], [this, byte1](s32 byte2) {
|
|
if (byte1 != byte2)
|
|
FLAGS(C_SUB(byte2, byte1), V_SUB(byte2, byte1), byte2 == byte1, byte2 < byte1); }); });
|
|
|
|
// abort on exception or mismatch
|
|
if (m_exception || !PSW(Z))
|
|
break;
|
|
|
|
m_r[0]--;
|
|
m_r[1]++;
|
|
m_r[2]++;
|
|
}
|
|
// TRAPS: C,U,P,R
|
|
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:
|
|
// restwN..restw12: pop registers rN:r14
|
|
|
|
// load ri from sp + 4 * (i - N)
|
|
for (int i = R2; i < 15 && !m_exception; i++)
|
|
get_dcammu().load<u32>(m_ssw, m_r[15] + 4 * (i - R2), [this, i](u32 v) { m_r[i] = v; });
|
|
|
|
// increment sp after pop to allow restart on exceptions
|
|
if (!m_exception)
|
|
m_r[15] += 4 * (15 - R2);
|
|
// TRAPS: C,U,A,P,R
|
|
break;
|
|
|
|
case 0x20: case 0x21: case 0x22: case 0x23:
|
|
case 0x24: case 0x25: case 0x26: case 0x27:
|
|
// saved0..saved7: push registers fN:f7
|
|
|
|
// store fi at sp - 8 * (8 - i)
|
|
for (int i = R2; i < 8 && !m_exception; i++)
|
|
get_dcammu().store<float64>(m_ssw, m_r[15] - 8 * (8 - i), get_fp64(i));
|
|
|
|
// decrement sp after push to allow restart on exceptions
|
|
if (!m_exception)
|
|
m_r[15] -= 8 * (8 - R2);
|
|
// TRAPS: A,P,W
|
|
break;
|
|
case 0x28: case 0x29: case 0x2a: case 0x2b:
|
|
case 0x2c: case 0x2d: case 0x2e: case 0x2f:
|
|
// restd0..restd7: pop registers fN:f7
|
|
|
|
// load fi from sp + 8 * (i - N)
|
|
for (int i = R2; i < 8 && !m_exception; i++)
|
|
get_dcammu().load<float64>(m_ssw, m_r[15] + 8 * (i - R2), [this, i](float64 v) { set_fp(i, v, F_NONE); });
|
|
|
|
// increment sp after pop to allow restart on exceptions
|
|
if (!m_exception)
|
|
m_r[15] += 8 * (8 - R2);
|
|
// TRAPS: C,U,A,P,R
|
|
break;
|
|
case 0x30:
|
|
// cnvsw: convert single floating to word
|
|
m_fp_pc = m_info.pc;
|
|
|
|
m_r[m_info.macro & 0xf] = float32_to_int32(get_fp32((m_info.macro >> 4) & 0xf));
|
|
// TRAPS: F_IX
|
|
float_exception_flags &= F_IX;
|
|
break;
|
|
case 0x31:
|
|
// cnvrsw: convert rounding single floating to word (non-IEEE +0.5/-0.5 rounding)
|
|
m_fp_pc = m_info.pc;
|
|
|
|
if (float32_lt(get_fp32((m_info.macro >> 4) & 0xf), 0))
|
|
m_r[m_info.macro & 0xf] = float32_to_int32_round_to_zero(float32_sub(get_fp32((m_info.macro >> 4) & 0xf),
|
|
float32_div(int32_to_float32(1), int32_to_float32(2))));
|
|
else
|
|
m_r[m_info.macro & 0xf] = float32_to_int32_round_to_zero(float32_add(get_fp32((m_info.macro >> 4) & 0xf),
|
|
float32_div(int32_to_float32(1), int32_to_float32(2))));
|
|
// TRAPS: F_IX
|
|
float_exception_flags &= F_IX;
|
|
break;
|
|
case 0x32:
|
|
// cnvtsw: convert truncating single floating to word
|
|
m_fp_pc = m_info.pc;
|
|
|
|
m_r[m_info.macro & 0xf] = float32_to_int32_round_to_zero(get_fp32((m_info.macro >> 4) & 0xf));
|
|
// TRAPS: F_IX
|
|
float_exception_flags &= F_IX;
|
|
break;
|
|
case 0x33:
|
|
// cnvws: convert word to single floating
|
|
set_fp(m_info.macro & 0xf, int32_to_float32(m_r[(m_info.macro >> 4) & 0xf]), F_X);
|
|
// TRAPS: F_X
|
|
break;
|
|
case 0x34:
|
|
// cnvdw: convert double floating to word
|
|
m_fp_pc = m_info.pc;
|
|
|
|
m_r[m_info.macro & 0xf] = float64_to_int32(get_fp64((m_info.macro >> 4) & 0xf));
|
|
// TRAPS: F_IX
|
|
float_exception_flags &= F_IX;
|
|
break;
|
|
case 0x35:
|
|
// cnvrdw: convert rounding double floating to word (non-IEEE +0.5/-0.5 rounding)
|
|
m_fp_pc = m_info.pc;
|
|
|
|
if (float64_lt(get_fp64((m_info.macro >> 4) & 0xf), 0))
|
|
m_r[m_info.macro & 0xf] = float64_to_int32_round_to_zero(float64_sub(get_fp64((m_info.macro >> 4) & 0xf),
|
|
float64_div(int32_to_float64(1), int32_to_float64(2))));
|
|
else
|
|
m_r[m_info.macro & 0xf] = float64_to_int32_round_to_zero(float64_add(get_fp64((m_info.macro >> 4) & 0xf),
|
|
float64_div(int32_to_float64(1), int32_to_float64(2))));
|
|
// TRAPS: F_IX
|
|
float_exception_flags &= F_IX;
|
|
break;
|
|
case 0x36:
|
|
// cnvtdw: convert truncating double floating to word
|
|
m_fp_pc = m_info.pc;
|
|
|
|
m_r[m_info.macro & 0xf] = float64_to_int32_round_to_zero(get_fp64((m_info.macro >> 4) & 0xf));
|
|
// TRAPS: F_IX
|
|
float_exception_flags &= F_IX;
|
|
break;
|
|
case 0x37:
|
|
// cnvwd: convert word to double floating
|
|
set_fp(m_info.macro & 0xf, int32_to_float64(m_r[(m_info.macro >> 4) & 0xf]), F_NONE);
|
|
break;
|
|
case 0x38:
|
|
// cnvsd: convert single to double floating
|
|
set_fp(m_info.macro & 0xf, float32_to_float64(get_fp32((m_info.macro >> 4) & 0xf)), F_I);
|
|
// TRAPS: F_I
|
|
break;
|
|
case 0x39:
|
|
// cnvds: convert double to single floating
|
|
set_fp(m_info.macro & 0xf, float64_to_float32(get_fp64((m_info.macro >> 4) & 0xf)), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x3a:
|
|
// negs: negate single floating
|
|
set_fp(m_info.macro & 0xf, float32_mul(get_fp32((m_info.macro >> 4) & 0xf), int32_to_float32(-1)), F_NONE);
|
|
break;
|
|
case 0x3b:
|
|
// negd: negate double floating
|
|
set_fp(m_info.macro & 0xf, float64_mul(get_fp64((m_info.macro >> 4) & 0xf), int32_to_float64(-1)), F_NONE);
|
|
break;
|
|
case 0x3c:
|
|
/*
|
|
* This implementation for scalbd and scalbs is a bit opaque, but
|
|
* essentially we check if the integer value is within range, and
|
|
* directly create a floating constant representing 2^n or NaN
|
|
* respectively, which is used as an input to a multiply, producing
|
|
* the desired result. While doing an actual multiply is both
|
|
* inefficient and unnecessary, it's a tidy way to ensure the
|
|
* correct exception flags are set.
|
|
*/
|
|
// scalbs: scale by, single floating
|
|
set_fp(m_info.macro & 0xf, float32_mul(get_fp32(m_info.macro & 0xf),
|
|
((s32(m_r[(m_info.macro >> 4) & 0xf]) > -127 && s32(m_r[(m_info.macro >> 4) & 0xf]) < 128)
|
|
? float32((s32(m_r[(m_info.macro >> 4) & 0xf]) + 127) << 23)
|
|
: float32(~u32(0)))), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x3d:
|
|
// scalbd: scale by, double floating
|
|
set_fp(m_info.macro & 0xf, float64_mul(get_fp64(m_info.macro & 0xf),
|
|
(s32(m_r[(m_info.macro >> 4) & 0xf]) > -1023 && s32(m_r[(m_info.macro >> 4) & 0xf]) < 1024)
|
|
? float64(u64(s32(m_r[(m_info.macro >> 4) & 0xf]) + 1023) << 52)
|
|
: float64(~u64(0))), F_IVUX);
|
|
// TRAPS: F_IVUX
|
|
break;
|
|
case 0x3e:
|
|
// trapfn: trap floating unordered
|
|
// TRAPS: I
|
|
if (PSW(Z) && PSW(N))
|
|
m_exception = EXCEPTION_ILLEGAL_OPERATION;
|
|
break;
|
|
case 0x3f:
|
|
// loadfs: load floating status
|
|
m_r[(m_info.macro >> 4) & 0xf] = m_fp_pc;
|
|
m_f[m_info.macro & 0xf] = m_fp_dst;
|
|
m_ssw |= SSW_FRD;
|
|
break;
|
|
|
|
default:
|
|
m_exception = EXCEPTION_ILLEGAL_OPERATION;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0xb6:
|
|
// privileged macro instructions
|
|
if (!SSW(U))
|
|
{
|
|
switch (m_info.subopcode)
|
|
{
|
|
case 0x00:
|
|
// movus: move user to supervisor
|
|
m_rs[m_info.macro & 0xf] = m_ru[(m_info.macro >> 4) & 0xf];
|
|
FLAGS(0, 0, m_rs[m_info.macro & 0xf] == 0, s32(m_rs[m_info.macro & 0xf]) < 0)
|
|
// FLAGS: 00ZN
|
|
// TRAPS: S
|
|
break;
|
|
case 0x01:
|
|
// movsu: move supervisor to user
|
|
m_ru[m_info.macro & 0xf] = m_rs[(m_info.macro >> 4) & 0xf];
|
|
FLAGS(0, 0, m_ru[m_info.macro & 0xf] == 0, s32(m_ru[m_info.macro & 0xf]) < 0)
|
|
// FLAGS: 00ZN
|
|
// TRAPS: S
|
|
break;
|
|
case 0x02:
|
|
// saveur: save user registers
|
|
for (int i = 0; i < 16 && !m_exception; i++)
|
|
get_dcammu().store<u32>(m_ssw, m_rs[(m_info.macro >> 4) & 0xf] - 4 * (i + 1), m_ru[15 - i]);
|
|
|
|
if (!m_exception)
|
|
m_rs[(m_info.macro >> 4) & 0xf] -= 64;
|
|
// TRAPS: A,P,W,S
|
|
break;
|
|
case 0x03:
|
|
// restur: restore user registers
|
|
for (int i = 0; i < 16 && !m_exception; i++)
|
|
get_dcammu().load<u32>(m_ssw, m_rs[(m_info.macro >> 4) & 0xf] + 4 * i, [this, i](u32 v) { m_ru[i] = v; });
|
|
|
|
if (!m_exception)
|
|
m_rs[(m_info.macro >> 4) & 0xf] += 64;
|
|
// TRAPS: C,U,A,P,R,S
|
|
break;
|
|
case 0x04:
|
|
// reti: restore psw, ssw and pc from supervisor stack
|
|
m_pc = reti();
|
|
// TRAPS: S
|
|
break;
|
|
case 0x05:
|
|
// wait: wait for interrupt
|
|
m_wait = true;
|
|
// TRAPS: S
|
|
break;
|
|
|
|
default:
|
|
m_exception = EXCEPTION_ILLEGAL_OPERATION;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
m_exception = EXCEPTION_PRIVILEGED_INSTRUCTION;
|
|
break;
|
|
|
|
default:
|
|
m_exception = EXCEPTION_ILLEGAL_OPERATION;
|
|
break;
|
|
}
|
|
}
|
|
|
|
u32 clipper_device::reti()
|
|
{
|
|
u32 new_psw = 0, new_ssw = 0, new_pc = 0;
|
|
|
|
// pop the psw, ssw and pc from the supervisor stack
|
|
if (!get_dcammu().load<u32>(m_ssw, m_rs[(m_info.macro >> 4) & 0xf] + 0, [&new_psw](u32 v) { new_psw = v; }))
|
|
fatalerror("reti unrecoverable fault 0x%04x pop psw address 0x%08x pc 0x%08x\n", m_exception, m_rs[(m_info.macro >> 4) & 0xf] + 0, m_info.pc);
|
|
|
|
if (!get_dcammu().load<u32>(m_ssw, m_rs[(m_info.macro >> 4) & 0xf] + 4, [&new_ssw](u32 v) { new_ssw = v; }))
|
|
fatalerror("reti unrecoverable fault 0x%04x pop ssw address 0x%08x pc 0x%08x\n", m_exception, m_rs[(m_info.macro >> 4) & 0xf] + 4, m_info.pc);
|
|
|
|
if (!get_dcammu().load<u32>(m_ssw, m_rs[(m_info.macro >> 4) & 0xf] + 8, [&new_pc](u32 v) { new_pc = v; }))
|
|
fatalerror("reti unrecoverable fault 0x%04x pop pc address 0x%08x pc 0x%08x\n", m_exception, m_rs[(m_info.macro >> 4) & 0xf] + 8, m_info.pc);
|
|
|
|
LOGMASKED(LOG_EXCEPTION, "reti r%d ssp 0x%08x pc 0x%08x ssw 0x%08x psw 0x%08x new_pc 0x%08x new_ssw 0x%08x new_psw 0x%08x\n",
|
|
(m_info.macro >> 4) & 0xf, m_rs[(m_info.macro >> 4) & 0xf], m_info.pc, m_ssw, m_psw, new_pc, new_ssw, new_psw);
|
|
|
|
// adjust the stack pointer
|
|
m_rs[(m_info.macro >> 4) & 0xf] += 12;
|
|
|
|
// restore the psw and ssw
|
|
set_psw(new_psw);
|
|
set_ssw(new_ssw);
|
|
|
|
// return the restored pc
|
|
return new_pc;
|
|
}
|
|
|
|
/*
|
|
* Common entry point for transferring control in the event of an interrupt or
|
|
* exception. Reading between the lines, it appears this logic was implemented
|
|
* using the macro instruction ROM and a special macro instruction (intrap).
|
|
*/
|
|
u32 clipper_device::intrap(const u16 vector, const u32 old_pc)
|
|
{
|
|
const u32 old_ssw = m_ssw;
|
|
const u32 old_psw = m_psw;
|
|
u32 new_pc = 0, new_ssw = 0;
|
|
|
|
// clear ssw bits to enable supervisor memory access
|
|
m_ssw &= ~(SSW_U | SSW_K | SSW_UU | SSW_KU);
|
|
|
|
// clear exception state
|
|
m_exception = 0;
|
|
|
|
// fetch next pc and ssw from interrupt vector
|
|
if (!get_dcammu().load<u32>(m_ssw, vector + 0, [&new_pc](u32 v) { new_pc = v; }))
|
|
fatalerror("intrap unrecoverable fault 0x%04x load pc address 0x%08x pc 0x%08x\n", m_exception, vector + 0, old_pc);
|
|
if (!get_dcammu().load<u32>(m_ssw, vector + 4, [&new_ssw](u32 v) { new_ssw = v; }))
|
|
fatalerror("intrap unrecoverable fault 0x%04x load ssw address 0x%08x pc 0x%08x\n", m_exception, vector + 4, old_pc);
|
|
|
|
LOGMASKED(LOG_EXCEPTION, "intrap vector 0x%04x pc 0x%08x ssp 0x%08x new_pc 0x%08x new_ssw 0x%08x\n", vector, old_pc, m_rs[15], new_pc, new_ssw);
|
|
|
|
// derive cts and mts from vector
|
|
u32 source = 0;
|
|
switch (vector)
|
|
{
|
|
// data memory trap group
|
|
case EXCEPTION_D_CORRECTED_MEMORY_ERROR:
|
|
case EXCEPTION_D_UNCORRECTABLE_MEMORY_ERROR:
|
|
case EXCEPTION_D_ALIGNMENT_FAULT:
|
|
case EXCEPTION_D_PAGE_FAULT:
|
|
case EXCEPTION_D_READ_PROTECT_FAULT:
|
|
case EXCEPTION_D_WRITE_PROTECT_FAULT:
|
|
|
|
// instruction memory trap group
|
|
case EXCEPTION_I_CORRECTED_MEMORY_ERROR:
|
|
case EXCEPTION_I_UNCORRECTABLE_MEMORY_ERROR:
|
|
case EXCEPTION_I_ALIGNMENT_FAULT:
|
|
case EXCEPTION_I_PAGE_FAULT:
|
|
case EXCEPTION_I_EXECUTE_PROTECT_FAULT:
|
|
source = (vector & MTS_VMASK) << (MTS_SHIFT - MTS_VSHIFT);
|
|
break;
|
|
|
|
// integer arithmetic trap group
|
|
case EXCEPTION_INTEGER_DIVIDE_BY_ZERO:
|
|
source = CTS_DIVIDE_BY_ZERO;
|
|
break;
|
|
|
|
// illegal operation trap group
|
|
case EXCEPTION_ILLEGAL_OPERATION:
|
|
source = CTS_ILLEGAL_OPERATION;
|
|
break;
|
|
case EXCEPTION_PRIVILEGED_INSTRUCTION:
|
|
source = CTS_PRIVILEGED_INSTRUCTION;
|
|
break;
|
|
|
|
// diagnostic trap group
|
|
case EXCEPTION_TRACE:
|
|
source = CTS_TRACE_TRAP;
|
|
break;
|
|
}
|
|
|
|
// push pc, ssw and psw onto supervisor stack
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0x4, old_pc))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push pc ssp 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0x4, old_pc);
|
|
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0x8, old_ssw))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push ssw ssp 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0x8, old_pc);
|
|
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0xc, (old_psw & ~(PSW_CTS | PSW_MTS)) | source))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push psw ssp 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0xc, old_pc);
|
|
|
|
// decrement supervisor stack pointer
|
|
m_rs[15] -= 12;
|
|
|
|
// set ssw from vector and previous mode
|
|
set_ssw((new_ssw & ~SSW_P) | (old_ssw & SSW_U) << 1);
|
|
|
|
// clear psw
|
|
set_psw(0);
|
|
|
|
// return new pc from trap vector
|
|
return new_pc;
|
|
}
|
|
|
|
u32 clipper_c400_device::intrap(const u16 vector, const u32 old_pc)
|
|
{
|
|
const u32 old_ssw = m_ssw;
|
|
const u32 old_psw = m_psw;
|
|
u32 new_pc = 0, new_ssw = 0;
|
|
|
|
// clear ssw bits to enable supervisor memory access
|
|
m_ssw &= ~(SSW_U | SSW_K | SSW_UU | SSW_KU);
|
|
|
|
// clear exception state
|
|
m_exception = 0;
|
|
|
|
// fetch ssw and pc from interrupt vector (C400 reversed wrt C100/C300)
|
|
if (!get_dcammu().load<u32>(m_ssw, vector + 0, [&new_ssw](u32 v) { new_ssw = v; }))
|
|
fatalerror("intrap unrecoverable fault 0x%04x load ssw address 0x%08x pc 0x%08x\n", m_exception, vector + 0, old_pc);
|
|
if (!get_dcammu().load<u32>(m_ssw, vector + 4, [&new_pc](u32 v) { new_pc = v; }))
|
|
fatalerror("intrap unrecoverable fault 0x%04x load pc address 0x%08x pc 0x%08x\n", m_exception, vector + 4, old_pc);
|
|
|
|
LOGMASKED(LOG_EXCEPTION, "intrap vector 0x%04x pc 0x%08x ssp 0x%08x new_pc 0x%08x new_ssw 0x%08x\n", vector, old_pc, m_rs[15], new_pc, new_ssw);
|
|
|
|
// derive cts and mts from vector
|
|
u32 source = 0;
|
|
switch (vector)
|
|
{
|
|
// data memory trap group
|
|
case EXCEPTION_D_CORRECTED_MEMORY_ERROR:
|
|
case EXCEPTION_D_UNCORRECTABLE_MEMORY_ERROR:
|
|
case EXCEPTION_D_ALIGNMENT_FAULT:
|
|
case EXCEPTION_D_PAGE_FAULT:
|
|
case EXCEPTION_D_READ_PROTECT_FAULT:
|
|
case EXCEPTION_D_WRITE_PROTECT_FAULT:
|
|
|
|
// instruction memory trap group
|
|
case EXCEPTION_I_CORRECTED_MEMORY_ERROR:
|
|
case EXCEPTION_I_UNCORRECTABLE_MEMORY_ERROR:
|
|
case EXCEPTION_I_ALIGNMENT_FAULT:
|
|
case EXCEPTION_I_PAGE_FAULT:
|
|
case EXCEPTION_I_EXECUTE_PROTECT_FAULT:
|
|
source = (vector & MTS_VMASK) << (MTS_SHIFT - MTS_VSHIFT);
|
|
break;
|
|
|
|
// integer arithmetic trap group
|
|
case EXCEPTION_INTEGER_DIVIDE_BY_ZERO:
|
|
source = CTS_DIVIDE_BY_ZERO;
|
|
break;
|
|
|
|
// illegal operation trap group
|
|
case EXCEPTION_ILLEGAL_OPERATION:
|
|
source = CTS_ILLEGAL_OPERATION;
|
|
break;
|
|
case EXCEPTION_PRIVILEGED_INSTRUCTION:
|
|
source = CTS_PRIVILEGED_INSTRUCTION;
|
|
break;
|
|
|
|
// diagnostic trap group
|
|
case EXCEPTION_TRACE:
|
|
source = CTS_TRACE_TRAP;
|
|
break;
|
|
}
|
|
|
|
// push pc, ssw and psw onto supervisor stack
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0x4, old_pc))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push pc ssp 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0x4, old_pc);
|
|
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0x8, old_ssw))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push ssw ssp 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0x8, old_pc);
|
|
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0xc, (old_psw & ~(PSW_CTS | PSW_MTS)) | source))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push psw ssp 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0xc, old_pc);
|
|
|
|
// TODO: push pc1
|
|
// TODO: push pc2
|
|
|
|
// push delayed branch pc onto supervisor stack
|
|
if (!get_dcammu().store<u32>(m_ssw, m_rs[15] - 0x18, m_db_pc))
|
|
fatalerror("intrap unrecoverable fault 0x%04x push db_pc address 0x%08x pc 0x%08x\n", m_exception, m_rs[15] - 0x18, old_pc);
|
|
|
|
// decrement supervisor stack pointer
|
|
m_rs[15] -= 24;
|
|
|
|
// set ssw from vector and previous mode
|
|
set_ssw((new_ssw & ~SSW_P) | (old_ssw & SSW_U) << 1);
|
|
|
|
// clear psw
|
|
set_psw(0);
|
|
|
|
// return new pc from trap vector
|
|
return new_pc;
|
|
}
|
|
|
|
bool clipper_device::evaluate_branch() const
|
|
{
|
|
switch (m_info.r2)
|
|
{
|
|
case BRANCH_T:
|
|
return true;
|
|
|
|
case BRANCH_LT:
|
|
return (!PSW(V) && !PSW(Z) && !PSW(N))
|
|
|| (PSW(V) && !PSW(Z) && PSW(N));
|
|
|
|
case BRANCH_LE:
|
|
return (!PSW(V) && !PSW(N))
|
|
|| (PSW(V) && !PSW(Z) && PSW(N));
|
|
|
|
case BRANCH_EQ:
|
|
return PSW(Z) && !PSW(N);
|
|
|
|
case BRANCH_GT:
|
|
return (!PSW(V) && !PSW(Z) && PSW(N))
|
|
|| (PSW(V) && !PSW(N));
|
|
|
|
case BRANCH_GE:
|
|
return (PSW(V) && !PSW(N))
|
|
|| (!PSW(V) && !PSW(Z) && PSW(N))
|
|
|| (PSW(Z) && !PSW(N));
|
|
|
|
case BRANCH_NE:
|
|
return (!PSW(Z))
|
|
|| (PSW(Z) && PSW(N));
|
|
|
|
case BRANCH_LTU:
|
|
return (!PSW(C) && !PSW(Z));
|
|
|
|
case BRANCH_LEU:
|
|
return !PSW(C);
|
|
|
|
case BRANCH_GTU:
|
|
return PSW(C);
|
|
|
|
case BRANCH_GEU:
|
|
return PSW(C) || PSW(Z);
|
|
|
|
case BRANCH_V:
|
|
return PSW(V);
|
|
case BRANCH_NV:
|
|
return !PSW(V);
|
|
|
|
case BRANCH_N:
|
|
return !PSW(Z) && PSW(N);
|
|
case BRANCH_NN:
|
|
return !PSW(N);
|
|
|
|
case BRANCH_FN:
|
|
return PSW(Z) && PSW(N);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void clipper_device::set_psw(const u32 psw)
|
|
{
|
|
// retain read-only endianness field
|
|
m_psw = (m_psw & PSW_BIG) | (psw & ~PSW_BIG);
|
|
|
|
// set the softfloat rounding mode based on the psw rounding mode
|
|
switch (PSW(FR))
|
|
{
|
|
case FR_0: float_rounding_mode = float_round_nearest_even; break;
|
|
case FR_1: float_rounding_mode = float_round_up; break;
|
|
case FR_2: float_rounding_mode = float_round_down; break;
|
|
case FR_3: float_rounding_mode = float_round_to_zero; break;
|
|
}
|
|
}
|
|
|
|
void clipper_device::set_ssw(const u32 ssw)
|
|
{
|
|
// retain read-only id field
|
|
m_ssw = (m_ssw & SSW_ID) | (ssw & ~SSW_ID);
|
|
|
|
// select the register file
|
|
m_r = SSW(U) ? m_ru : m_rs;
|
|
}
|
|
|
|
void clipper_device::fp_exception()
|
|
{
|
|
u16 exception = 0;
|
|
|
|
/*
|
|
* Set the psw floating exception flags, and identify any enabled
|
|
* exceptions. The order here is important, but since the documentation
|
|
* doesn't explicitly specify, this is a guess. Simply put, exceptions
|
|
* are considered in sequence with an increasing order of priority.
|
|
*/
|
|
if (float_exception_flags & float_flag_inexact)
|
|
{
|
|
m_psw |= PSW_FX;
|
|
if (PSW(EFX))
|
|
exception = EXCEPTION_FLOATING_INEXACT;
|
|
}
|
|
if (float_exception_flags & float_flag_underflow)
|
|
{
|
|
m_psw |= PSW_FU;
|
|
if (PSW(EFU))
|
|
exception = EXCEPTION_FLOATING_UNDERFLOW;
|
|
}
|
|
if (float_exception_flags & float_flag_overflow)
|
|
{
|
|
m_psw |= PSW_FV;
|
|
if (PSW(EFV))
|
|
exception = EXCEPTION_FLOATING_OVERFLOW;
|
|
}
|
|
if (float_exception_flags & float_flag_divbyzero)
|
|
{
|
|
m_psw |= PSW_FD;
|
|
if (PSW(EFD))
|
|
exception = EXCEPTION_FLOATING_DIVIDE_BY_ZERO;
|
|
}
|
|
if (float_exception_flags & float_flag_invalid)
|
|
{
|
|
m_psw |= PSW_FI;
|
|
if (PSW(EFI))
|
|
exception = EXCEPTION_FLOATING_INVALID_OPERATION;
|
|
}
|
|
|
|
// trigger a floating point exception
|
|
if (PSW(EFT) && exception)
|
|
m_exception = exception;
|
|
}
|
|
|
|
void clipper_c400_device::execute_instruction()
|
|
{
|
|
// update delay slot pointer
|
|
switch (PSW(DSP))
|
|
{
|
|
case DSP_S1:
|
|
// take delayed branch
|
|
m_psw &= ~PSW_DSP;
|
|
m_pc = m_db_pc;
|
|
return;
|
|
|
|
case DSP_SALL:
|
|
// one delay slot still active
|
|
m_psw &= ~PSW_DSP;
|
|
m_psw |= DSP_S1;
|
|
break;
|
|
|
|
case DSP_SETUP:
|
|
// two delay slots active
|
|
m_psw &= ~PSW_DSP;
|
|
m_psw |= DSP_SALL;
|
|
break;
|
|
}
|
|
|
|
// if executing a delay slot instruction, test for valid type
|
|
if (PSW(DSP))
|
|
{
|
|
switch (m_info.opcode)
|
|
{
|
|
case 0x13: // ret
|
|
case 0x44: // call
|
|
case 0x45:
|
|
case 0x48: // b*
|
|
case 0x49:
|
|
case 0x4a: // cdb
|
|
case 0x4b:
|
|
case 0x4c: // cdbeq
|
|
case 0x4d:
|
|
case 0x4e: // cdbne
|
|
case 0x4f:
|
|
case 0x50: // db*
|
|
case 0x51:
|
|
// TODO: this should throw some kind of illegal instruction trap, not abort
|
|
fatalerror("instruction type 0x%02x invalid in branch delay slot pc 0x%08x\n", m_info.opcode, m_info.pc);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (m_info.opcode)
|
|
{
|
|
case 0x46:
|
|
case 0x47:
|
|
// loadd2: load double floating double
|
|
// TODO: 128-bit load
|
|
get_dcammu().load<float64>(m_ssw, m_info.address + 0, [this](float64 v) { set_fp(R2 + 0, v, F_NONE); });
|
|
get_dcammu().load<float64>(m_ssw, m_info.address + 8, [this](float64 v) { set_fp(R2 + 1, v, F_NONE); });
|
|
// TRAPS: C,U,A,P,R,I
|
|
break;
|
|
|
|
case 0x4a:
|
|
case 0x4b:
|
|
// cdb: compare and delayed branch?
|
|
// emulate.h: "cdb is special because it does not support all addressing modes", 2-3 parcels
|
|
fatalerror("cdb pc 0x%08x\n", m_info.pc);
|
|
case 0x4c:
|
|
case 0x4d:
|
|
// cdbeq: compare and delayed branch if equal?
|
|
if (m_r[R2] == 0)
|
|
{
|
|
m_psw |= DSP_SETUP;
|
|
m_db_pc = m_info.address;
|
|
}
|
|
break;
|
|
case 0x4e:
|
|
case 0x4f:
|
|
// cdbne: compare and delayed branch if not equal?
|
|
if (m_r[R2] != 0)
|
|
{
|
|
m_psw |= DSP_SETUP;
|
|
m_db_pc = m_info.address;
|
|
}
|
|
break;
|
|
case 0x50:
|
|
case 0x51:
|
|
// db*: delayed branch on condition
|
|
if (evaluate_branch())
|
|
{
|
|
m_psw |= DSP_SETUP;
|
|
m_db_pc = m_info.address;
|
|
}
|
|
break;
|
|
|
|
case 0xb0:
|
|
// abss: absolute value single floating?
|
|
if (float32_lt(get_fp32(R1), 0))
|
|
set_fp(R2, float32_mul(get_fp32(R1), int32_to_float32(-1)), F_IVUX);
|
|
else
|
|
set_fp(R2, get_fp32(R1), F_IVUX);
|
|
break;
|
|
|
|
case 0xb2:
|
|
// absd: absolute value double floating?
|
|
if (float64_lt(get_fp64(R1), 0))
|
|
set_fp(R2, float64_mul(get_fp64(R1), int32_to_float64(-1)), F_IVUX);
|
|
else
|
|
set_fp(R2, get_fp64(R1), F_IVUX);
|
|
break;
|
|
|
|
case 0xb4:
|
|
// unprivileged macro instructions
|
|
switch (m_info.subopcode)
|
|
{
|
|
case 0x44:
|
|
// cnvxsw: ??
|
|
fatalerror("cnvxsw pc 0x%08x\n", m_info.pc);
|
|
case 0x46:
|
|
// cnvxdw: ??
|
|
fatalerror("cnvxdw pc 0x%08x\n", m_info.pc);
|
|
|
|
default:
|
|
clipper_device::execute_instruction();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0xb6:
|
|
// privileged macro instructions
|
|
if (!SSW(U))
|
|
{
|
|
switch (m_info.subopcode)
|
|
{
|
|
case 0x07:
|
|
// loadts: unknown?
|
|
fatalerror("loadts pc 0x%08x\n", m_info.pc);
|
|
|
|
default:
|
|
clipper_device::execute_instruction();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
m_exception = EXCEPTION_PRIVILEGED_INSTRUCTION;
|
|
break;
|
|
|
|
case 0xbc:
|
|
// waitd:
|
|
if (!SSW(U))
|
|
; // TODO: don't know what this instruction does
|
|
else
|
|
m_exception = EXCEPTION_PRIVILEGED_INSTRUCTION;
|
|
break;
|
|
|
|
case 0xc0:
|
|
// s*: set register on condition
|
|
m_r[R1] = evaluate_branch() ? 1 : 0;
|
|
break;
|
|
|
|
default:
|
|
clipper_device::execute_instruction();
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<util::disasm_interface> clipper_device::create_disassembler()
|
|
{
|
|
return std::make_unique<clipper_disassembler>();
|
|
}
|
|
|
|
std::string clipper_device::debug_string(u32 pointer)
|
|
{
|
|
auto const suppressor(machine().disable_side_effects());
|
|
|
|
std::string s("");
|
|
|
|
while (true)
|
|
{
|
|
char c;
|
|
|
|
if (!get_dcammu().load<u8>(m_ssw, pointer++, [&c](u8 v) { c = v; }))
|
|
break;
|
|
|
|
if (c == '\0')
|
|
break;
|
|
|
|
s += c;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
std::string clipper_device::debug_string_array(u32 array_pointer)
|
|
{
|
|
auto const suppressor(machine().disable_side_effects());
|
|
|
|
std::string s("");
|
|
|
|
while (true)
|
|
{
|
|
u32 string_pointer;
|
|
|
|
if (!get_dcammu().load<u32>(m_ssw, array_pointer, [&string_pointer](u32 v) { string_pointer = v; }))
|
|
break;
|
|
|
|
if (string_pointer == 0)
|
|
break;
|
|
|
|
if (!s.empty())
|
|
s += ", ";
|
|
|
|
s += '\"' + debug_string(string_pointer) + '\"';
|
|
array_pointer += 4;
|
|
}
|
|
|
|
return s;
|
|
}
|