// 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 v60_device::create_disassembler() { return std::make_unique(); } // 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); //return 0; /* never reached, fatalerror won't return */ } // 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(); } }