// license:BSD-3-Clause
// copyright-holders:Farfetch'd, R. Belmont
// V60.C
// Undiscover the beast!
// Main hacking and coding by Farfetch'd
// Portability fixes by R. Belmont
//
// Emulation for the NEC V60 (uPD70615) and V70 (uPD70632) CPUs
//
/*
Taken from the NEC Semiconductor Selection Guide Guide Book (Oct. 1995):
uPD70615 (V60)
Features:
- Virtual memory (paging method)
- Level protection architecture - 4-level hierarchical protection function
for system multi-programming.
- Abundant general registers - Thirty two 32-bit general registers for
optimizing compiler
- Refined instruction set - 2-address method: Arbitrary addressing mode
can be used independently for source operand and destination operand.
- Abundant address modes and data types - Auto increment/decrement mode
for string process, and memory indirect addressing for pointer operation
- High cost-to performance chip
- No multiprocessor system - no FRM function for increasing system
reliability using two or more processors.
- No V20/V30 simulation mode
Address bus: 24 bits
Data bus: 16 bits
Memory space: 4G bytes
Operating frequency: 16 MHz
Package: 120-pin QFP
uPD70616 (V60)
Features:
- Virtual memory (paging method)
- Level protection architecture - 4-level hierarchical protection function
for system multi-programming.
- Abundant general registers - Thirty two 32-bit general registers for
optimizing compiler
- Refined instruction set - 2-address method: Arbitrary addressing mode
can be used independently for source operand and destination operand.
- Abundant address modes and data types - Auto increment/decrement mode
for string process, and memory indirect addressing for pointer operation
- Multiprocessor system - FRM function for increasing system reliability
using two or more processors.
- V20/V30 simulation mode
Address bus: 24 bits
Data bus: 16 bits
Memory space: 4G bytes
Operating frequency: 16 MHz
Package: 68-pin PGA
uPD70632 (V70)
Features:
- Virtual memory (paging method)
- Level protection architecture - 4-level hierarchical protection function
for system multi-programming.
- Abundant general registers - Thirty two 32-bit general registers for
optimizing compiler
- Refined instruction set - 2-address method: Arbitrary addressing mode
can be used independently for source operand and destination operand.
- Abundant address modes and data types - Auto increment/decrement mode
for string process, and memory indirect addressing for pointer operation
- Multiprocessor system - FRM function for increasing system reliability
using two or more processors.
- V20/V30 simulation mode
Address bus: 32 bits
Data bus: 32 bits
Memory space: 4G bytes
Operating frequency: 20 MHz
Package: 132-pin PGA, 200-pin QFP
*/
#include "emu.h"
#include "v60.h"
#include "v60d.h"
#include "debugger.h"
DEFINE_DEVICE_TYPE(V60, v60_device, "v60", "NEC V60")
DEFINE_DEVICE_TYPE(V70, v70_device, "v70", "NEC V70")
// Set m_PIR (Processor ID) for NEC m_ LSB is reserved to NEC,
// so I don't know what it contains.
v60_device::v60_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: v60_device(mconfig, V60, tag, owner, clock, 16, 24, 0x00006000)
{
}
v60_device::v60_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, int databits, int addrbits, uint32_t pir)
: cpu_device(mconfig, type, tag, owner, clock)
, m_program_config("program", ENDIANNESS_LITTLE, databits, addrbits, 0)
, m_io_config("io", ENDIANNESS_LITTLE, 16, 24, 0)
, m_start_pc(0xfffffff0)
{
m_reg[45] = pir;
}
// Set m_PIR (Processor ID) for NEC v70. LSB is reserved to NEC,
// so I don't know what it contains.
v70_device::v70_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: v60_device(mconfig, V70, tag, owner, clock, 32, 32, 0x00007000)
{
}
device_memory_interface::space_config_vector v60_device::memory_space_config() const
{
return space_config_vector {
std::make_pair(AS_PROGRAM, &m_program_config),
std::make_pair(AS_IO, &m_io_config)
};
}
std::unique_ptr<util::disasm_interface> v60_device::create_disassembler()
{
return std::make_unique<v60_disassembler>();
}
// memory accessors
#define OpRead8(a) m_pr8(a)
#define OpRead16(a) m_pr16(a)
#define OpRead32(a) m_pr32(a)
// macros stolen from MAME for flags calc
// note that these types are in x86 naming:
// byte = 8 bit, word = 16 bit, long = 32 bit
// parameter x = result, y = source 1, z = source 2
#define SetOFL_Add(x, y,z) (_OV = (((x) ^ (y)) & ((x) ^ (z)) & 0x80000000) ? 1: 0)
#define SetOFW_Add(x, y,z) (_OV = (((x) ^ (y)) & ((x) ^ (z)) & 0x8000) ? 1 : 0)
#define SetOFB_Add(x, y,z) (_OV = (((x) ^ (y)) & ((x) ^ (z)) & 0x80) ? 1 : 0)
#define SetOFL_Sub(x, y,z) (_OV = (((z) ^ (y)) & ((z) ^ (x)) & 0x80000000) ? 1 : 0)
#define SetOFW_Sub(x, y,z) (_OV = (((z) ^ (y)) & ((z) ^ (x)) & 0x8000) ? 1 : 0)
#define SetOFB_Sub(x, y,z) (_OV = (((z) ^ (y)) & ((z) ^ (x)) & 0x80) ? 1 : 0)
#define SetCFB(x) {_CY = ((x) & 0x100) ? 1 : 0; }
#define SetCFW(x) {_CY = ((x) & 0x10000) ? 1 : 0; }
#define SetCFL(x) {_CY = ((x) & (((uint64_t)1) << 32)) ? 1 : 0; }
#define SetSF(x) (_S = (x))
#define SetZF(x) (_Z = (x))
#define SetSZPF_Byte(x) {_Z = ((uint8_t)(x) == 0); _S = ((x)&0x80) ? 1 : 0; }
#define SetSZPF_Word(x) {_Z = ((uint16_t)(x) == 0); _S = ((x)&0x8000) ? 1 : 0; }
#define SetSZPF_Long(x) {_Z = ((uint32_t)(x) == 0); _S = ((x)&0x80000000) ? 1 : 0; }
#define ORB(dst, src) { (dst) |= (src); _OV = 0; SetSZPF_Byte(dst); }
#define ORW(dst, src) { (dst) |= (src); _OV = 0; SetSZPF_Word(dst); }
#define ORL(dst, src) { (dst) |= (src); _OV = 0; SetSZPF_Long(dst); }
#define ANDB(dst, src) { (dst) &= (src); _OV = 0; SetSZPF_Byte(dst); }
#define ANDW(dst, src) { (dst) &= (src); _OV = 0; SetSZPF_Word(dst); }
#define ANDL(dst, src) { (dst) &= (src); _OV = 0; SetSZPF_Long(dst); }
#define XORB(dst, src) { (dst) ^= (src); _OV = 0; SetSZPF_Byte(dst); }
#define XORW(dst, src) { (dst) ^= (src); _OV = 0; SetSZPF_Word(dst); }
#define XORL(dst, src) { (dst) ^= (src); _OV = 0; SetSZPF_Long(dst); }
#define SUBB(dst, src) { unsigned res = (dst) - (src); SetCFB(res); SetOFB_Sub(res, src, dst); SetSZPF_Byte(res); dst = (uint8_t)res; }
#define SUBW(dst, src) { unsigned res = (dst) - (src); SetCFW(res); SetOFW_Sub(res, src, dst); SetSZPF_Word(res); dst = (uint16_t)res; }
#define SUBL(dst, src) { uint64_t res = (uint64_t)(dst) - (int64_t)(src); SetCFL(res); SetOFL_Sub(res, src, dst); SetSZPF_Long(res); dst = (uint32_t)res; }
#define ADDB(dst, src) { unsigned res = (dst) + (src); SetCFB(res); SetOFB_Add(res, src, dst); SetSZPF_Byte(res); dst = (uint8_t)res; }
#define ADDW(dst, src) { unsigned res = (dst) + (src); SetCFW(res); SetOFW_Add(res, src, dst); SetSZPF_Word(res); dst = (uint16_t)res; }
#define ADDL(dst, src) { uint64_t res = (uint64_t)(dst) + (uint64_t)(src); SetCFL(res); SetOFL_Add(res, src, dst); SetSZPF_Long(res); dst = (uint32_t)res; }
#define SETREG8(a, b) (a) = ((a) & ~0xff) | ((b) & 0xff)
#define SETREG16(a, b) (a) = ((a) & ~0xffff) | ((b) & 0xffff)
/*
* Prevent warnings on NetBSD. All identifiers beginning with an underscore
* followed by an uppercase letter are reserved by the C standard (ISO / IEC
* 9899:1999, 7.1.3) to be used by the implementation. It'd be best to rename
* all such instances, but this is less intrusive and error-prone.
*/
#undef _S
#define _CY m_flags.CY
#define _OV m_flags.OV
#define _S m_flags.S
#define _Z m_flags.Z
// Defines of all v60 register...
#define R0 m_reg[0]
#define R1 m_reg[1]
#define R2 m_reg[2]
#define R3 m_reg[3]
#define R4 m_reg[4]
#define R5 m_reg[5]
#define R6 m_reg[6]
#define R7 m_reg[7]
#define R8 m_reg[8]
#define R9 m_reg[9]
#define R10 m_reg[10]
#define R11 m_reg[11]
#define R12 m_reg[12]
#define R13 m_reg[13]
#define R14 m_reg[14]
#define R15 m_reg[15]
#define R16 m_reg[16]
#define R17 m_reg[17]
#define R18 m_reg[18]
#define R19 m_reg[19]
#define R20 m_reg[20]
#define R21 m_reg[21]
#define R22 m_reg[22]
#define R23 m_reg[23]
#define R24 m_reg[24]
#define R25 m_reg[25]
#define R26 m_reg[26]
#define R27 m_reg[27]
#define R28 m_reg[28]
#define AP m_reg[29]
#define FP m_reg[30]
#define SP m_reg[31]
#define PC m_reg[32]
#define PSW m_reg[33]
// Privileged registers
#define ISP m_reg[36]
#define L0SP m_reg[37]
#define L1SP m_reg[38]
#define L2SP m_reg[39]
#define L3SP m_reg[40]
#define SBR m_reg[41]
#define TR m_reg[42]
#define SYCW m_reg[43]
#define TKCW m_reg[44]
#define PIR m_reg[45]
//10-14 reserved
#define PSW2 m_reg[51]
#define ATBR0 m_reg[52]
#define ATLR0 m_reg[53]
#define ATBR1 m_reg[54]
#define ATLR1 m_reg[55]
#define ATBR2 m_reg[56]
#define ATLR2 m_reg[57]
#define ATBR3 m_reg[58]
#define ATLR3 m_reg[59]
#define TRMODE m_reg[60]
#define ADTR0 m_reg[61]
#define ADTR1 m_reg[62]
#define ADTMR0 m_reg[63]
#define ADTMR1 m_reg[64]
//29-31 reserved
// Defines...
#define NORMALIZEFLAGS() \
{ \
_S = _S ? 1 : 0; \
_OV = _OV ? 1 : 0; \
_Z = _Z ? 1 : 0; \
_CY = _CY ? 1 : 0; \
}
void v60_device::v60SaveStack()
{
if (PSW & 0x10000000)
ISP = SP;
else
m_reg[37 + ((PSW >> 24) & 3)] = SP;
}
void v60_device::v60ReloadStack()
{
if (PSW & 0x10000000)
SP = ISP;
else
SP = m_reg[37 + ((PSW >> 24) & 3)];
}
uint32_t v60_device::v60ReadPSW()
{
PSW &= 0xfffffff0;
PSW |= (_Z?1:0) | (_S?2:0) | (_OV?4:0) | (_CY?8:0);
return PSW;
}
void v60_device::v60WritePSW(uint32_t newval)
{
/* determine if we need to save / restore the stacks */
int updateStack = 0;
/* if the interrupt state is changing, we definitely need to update */
if ((newval ^ PSW) & 0x10000000)
updateStack = 1;
/* if we are not in interrupt mode and the level is changing, we also must update */
else if (!(PSW & 0x10000000) && ((newval ^ PSW) & 0x03000000))
updateStack = 1;
/* save the previous stack value */
if (updateStack)
v60SaveStack();
/* set the new value and update the flags */
PSW = newval;
_Z = (uint8_t)(PSW & 1);
_S = (uint8_t)(PSW & 2);
_OV = (uint8_t)(PSW & 4);
_CY = (uint8_t)(PSW & 8);
/* fetch the new stack value */
if (updateStack)
v60ReloadStack();
}
uint32_t v60_device::v60_update_psw_for_exception(int is_interrupt, int target_level)
{
uint32_t oldPSW = v60ReadPSW();
uint32_t newPSW = oldPSW;
// Change to interrupt context
newPSW &= ~(3 << 24); // PSW.EL = 0
newPSW |= target_level << 24; // set target level
newPSW &= ~(1 << 18); // PSW.IE = 0
newPSW &= ~(1 << 16); // PSW.TE = 0
newPSW &= ~(1 << 27); // PSW.TP = 0
newPSW &= ~(1 << 17); // PSW.AE = 0
newPSW &= ~(1 << 29); // PSW.EM = 0
if (is_interrupt)
newPSW |= (1 << 28);// PSW.IS = 1
newPSW |= (1 << 31); // PSW.ASA = 1
v60WritePSW(newPSW);
return oldPSW;
}
#define GETINTVECT(nint) m_program->read_dword((SBR & ~0xfff) + (nint) * 4)
#define EXCEPTION_CODE_AND_SIZE(code, size) (((code) << 16) | (size))
// Addressing mode decoding functions
#include "am.hxx"
// Opcode functions
#include "op12.hxx"
#include "op2.hxx"
#include "op3.hxx"
#include "op4.hxx"
#include "op5.hxx"
#include "op6.hxx"
#include "op7a.hxx"
uint32_t v60_device::opUNHANDLED()
{
fatalerror("Unhandled OpCode found : %02x at %08x\n", OpRead16(PC), PC);
}
// Opcode jump table
#include "optable.hxx"
void v60_device::device_start()
{
m_stall_io = 0;
m_irq_line = CLEAR_LINE;
m_nmi_line = CLEAR_LINE;
for ( int i = 0; i < 68; i++ )
{
// Don't set SP (31), PCi (32), PSW (33), SBR (41), SYCW (43), TKCW (44), PIR (45), PSW2 (51)
if ( i != 31 && i != 32 && i != 33 && i != 41 && i != 43 && i != 44 && i != 45 && i != 51 )
{
m_reg[i] = 0;
}
}
m_flags.CY = 0;
m_flags.OV = 0;
m_flags.S = 0;
m_flags.Z = 0;
m_op1 = 0;
m_op2 = 0;
m_flag1 = 0;
m_flag2 = 0;
m_instflags = 0;
m_lenop1 = 0;
m_lenop2 = 0;
m_subop = 0;
m_bamoffset1 = 0;
m_bamoffset2 = 0;
m_amflag = 0;
m_amout = 0;
m_bamoffset = 0;
m_amlength1 = 0;
m_amlength2 = 0;
m_modadd = 0;
m_modm = 0;
m_modval = 0;
m_modval2 = 0;
m_modwritevalb = 0;
m_modwritevalh = 0;
m_modwritevalw = 0;
m_moddim = 0;
m_program = &space(AS_PROGRAM);
if (m_program->data_width() == 16)
{
m_program->cache(m_cache16);
m_pr8 = [this](offs_t address) -> u8 { return m_cache16.read_byte(address); };
m_pr16 = [this](offs_t address) -> u16 { return m_cache16.read_word_unaligned(address); };
m_pr32 = [this](offs_t address) -> u32 { return m_cache16.read_dword_unaligned(address); };
}
else
{
m_program->cache(m_cache32);
m_pr8 = [this](offs_t address) -> u8 { return m_cache32.read_byte(address); };
m_pr16 = [this](offs_t address) -> u16 { return m_cache32.read_word_unaligned(address); };
m_pr32 = [this](offs_t address) -> u32 { return m_cache32.read_dword_unaligned(address); };
}
m_io = &space(AS_IO);
save_item(NAME(m_reg));
save_item(NAME(m_irq_line));
save_item(NAME(m_nmi_line));
save_item(NAME(m_PPC));
save_item(NAME(_CY));
save_item(NAME(_OV));
save_item(NAME(_S));
save_item(NAME(_Z));
state_add( V60_R0, "R0", R0).formatstr("%08X");
state_add( V60_R1, "R1", R1).formatstr("%08X");
state_add( V60_R2, "R2", R2).formatstr("%08X");
state_add( V60_R3, "R3", R3).formatstr("%08X");
state_add( V60_R4, "R4", R4).formatstr("%08X");
state_add( V60_R5, "R5", R5).formatstr("%08X");
state_add( V60_R6, "R6", R6).formatstr("%08X");
state_add( V60_R7, "R7", R7).formatstr("%08X");
state_add( V60_R8, "R8", R8).formatstr("%08X");
state_add( V60_R9, "R9", R9).formatstr("%08X");
state_add( V60_R10, "R10", R10).formatstr("%08X");
state_add( V60_R11, "R11", R11).formatstr("%08X");
state_add( V60_R12, "R12", R12).formatstr("%08X");
state_add( V60_R13, "R13", R13).formatstr("%08X");
state_add( V60_R14, "R14", R14).formatstr("%08X");
state_add( V60_R15, "R15", R15).formatstr("%08X");
state_add( V60_R16, "R16", R16).formatstr("%08X");
state_add( V60_R17, "R17", R17).formatstr("%08X");
state_add( V60_R18, "R18", R18).formatstr("%08X");
state_add( V60_R19, "R19", R19).formatstr("%08X");
state_add( V60_R20, "R20", R20).formatstr("%08X");
state_add( V60_R21, "R21", R21).formatstr("%08X");
state_add( V60_R22, "R22", R22).formatstr("%08X");
state_add( V60_R23, "R23", R23).formatstr("%08X");
state_add( V60_R24, "R24", R24).formatstr("%08X");
state_add( V60_R25, "R25", R25).formatstr("%08X");
state_add( V60_R26, "R26", R26).formatstr("%08X");
state_add( V60_R27, "R27", R27).formatstr("%08X");
state_add( V60_R28, "R28", R28).formatstr("%08X");
state_add( V60_AP, "AP", AP).formatstr("%08X");
state_add( V60_FP, "FP", FP).formatstr("%08X");
state_add( V60_SP, "SP", SP).formatstr("%08X");
state_add( V60_PC, "PC", PC).formatstr("%08X");
state_add( V60_PSW, "PSW", m_debugger_temp).callimport().callexport().formatstr("%08X");
state_add( V60_ISP, "ISP", ISP).formatstr("%08X");
state_add( V60_L0SP, "L0SP", L0SP).formatstr("%08X");
state_add( V60_L1SP, "L1SP", L1SP).formatstr("%08X");
state_add( V60_L2SP, "L2SP", L2SP).formatstr("%08X");
state_add( V60_L3SP, "L3SP", L3SP).formatstr("%08X");
state_add( V60_SBR, "SBR", SBR).formatstr("%08X");
state_add( V60_TR, "TR", TR).formatstr("%08X");
state_add( V60_SYCW, "SYCW", SYCW).formatstr("%08X");
state_add( V60_TKCW, "TKCW", TKCW).formatstr("%08X");
state_add( V60_PIR, "PIR", PIR).formatstr("%08X");
state_add( V60_PSW2, "PSW2", PSW2).formatstr("%08X");
state_add( V60_ATBR0, "ATBR0", ATBR0).formatstr("%08X");
state_add( V60_ATLR0, "ATLR0", ATLR0).formatstr("%08X");
state_add( V60_ATBR1, "ATBR1", ATBR1).formatstr("%08X");
state_add( V60_ATLR1, "ATLR1", ATLR1).formatstr("%08X");
state_add( V60_ATBR2, "ATBR2", ATBR2).formatstr("%08X");
state_add( V60_ATLR2, "ATLR2", ATLR2).formatstr("%08X");
state_add( V60_ATBR3, "ATBR3", ATBR3).formatstr("%08X");
state_add( V60_ATLR3, "ATLR3", ATLR3).formatstr("%08X");
state_add( V60_TRMODE, "TRMODE", TRMODE).formatstr("%08X");
state_add( V60_ADTR0, "ADTR0", ADTR0).formatstr("%08X");
state_add( V60_ADTR1, "ADTR1", ADTR1).formatstr("%08X");
state_add( V60_ADTMR0, "ADTMR0", ADTMR0).formatstr("%08X");
state_add( V60_ADTMR1, "ADTMR1", ADTMR1).formatstr("%08X");
state_add( STATE_GENPC, "GENPC", PC).noshow();
state_add( STATE_GENPCBASE, "CURPC", m_PPC ).noshow();
state_add( STATE_GENFLAGS, "GENFLAGS", m_debugger_temp).callimport().formatstr("%7s").noshow();
set_icountptr(m_icount);
}
void v60_device::state_export(const device_state_entry &entry)
{
switch (entry.index())
{
case V60_PSW:
m_debugger_temp = v60ReadPSW();
break;
}
}
void v60_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 & 1 ? 'Z' : '.',
PSW & 2 ? 'S' : '.',
PSW & 4 ? 'O' : '.',
PSW & 8 ? 'C' : '.');
break;
}
}
void v60_device::state_import(const device_state_entry &entry)
{
switch (entry.index())
{
case V60_PSW:
v60WritePSW( m_debugger_temp );
break;
}
}
void v60_device::device_reset()
{
PSW = 0x10000000;
PC = m_start_pc;
SBR = 0x00000000;
SYCW = 0x00000070;
TKCW = 0x0000e000;
PSW2 = 0x0000f002;
_CY = 0;
_OV = 0;
_S = 0;
_Z = 0;
}
void v60_device::stall()
{
m_stall_io = 1;
}
void v60_device::v60_do_irq(int vector)
{
uint32_t oldPSW = v60_update_psw_for_exception(1, 0);
// Push PC and PSW onto the stack
SP-=4;
m_program->write_dword_unaligned(SP, oldPSW);
SP-=4;
m_program->write_dword_unaligned(SP, PC);
// Jump to vector for user interrupt
PC = GETINTVECT(vector);
}
void v60_device::v60_try_irq()
{
if(m_irq_line == CLEAR_LINE)
return;
if((PSW & (1 << 18)) != 0) {
int vector;
if(m_irq_line != ASSERT_LINE)
m_irq_line = CLEAR_LINE;
vector = standard_irq_callback(0);
v60_do_irq(vector + 0x40);
}
}
void v60_device::execute_set_input(int irqline, int state)
{
if(irqline == INPUT_LINE_NMI) {
switch(state) {
case ASSERT_LINE:
if(m_nmi_line == CLEAR_LINE) {
m_nmi_line = ASSERT_LINE;
v60_do_irq(2);
}
break;
case CLEAR_LINE:
m_nmi_line = CLEAR_LINE;
break;
}
} else {
m_irq_line = state;
v60_try_irq();
}
}
// Actual cycles / instruction is unknown
void v60_device::execute_run()
{
if (m_irq_line != CLEAR_LINE)
v60_try_irq();
while (m_icount > 0)
{
uint32_t inc;
m_PPC = PC;
debugger_instruction_hook(PC);
m_icount -= 8; /* fix me -- this is just an average */
inc = (this->*s_OpCodeTable[OpRead8(PC)])();
PC += inc;
if (m_irq_line != CLEAR_LINE)
v60_try_irq();
}
}