// license:BSD-3-Clause // copyright-holders:R. Belmont,byuu /*************************************************************************** upd7725.c Core implementation for the portable NEC uPD7725/uPD96050 emulator Original by byuu in the public domain. MAME conversion by R. Belmont ****************************************************************************/ #include "emu.h" #include "debugger.h" #include "upd7725.h" #include "dasm7725.h" //************************************************************************** // DEVICE INTERFACE //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(UPD7725, upd7725_device, "upd7725", "uPD7725") DEFINE_DEVICE_TYPE(UPD96050, upd96050_device, "upd96050", "uPD96050") necdsp_device::necdsp_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t abits, uint32_t dbits) : cpu_device(mconfig, type, tag, owner, clock), m_program_config("program", ENDIANNESS_BIG, 32, abits, -2), // data bus width, address bus width, -2 means DWORD-addressable m_data_config("data", ENDIANNESS_BIG, 16, dbits, -1), m_icount(0), // -1 for WORD-addressable m_irq(0), m_irq_firing(0), m_program(nullptr), m_data(nullptr), m_direct(nullptr), m_in_int_cb(*this), //m_in_si_cb(*this), //m_in_sck_cb(*this), //m_in_sien_cb(*this), //m_in_soen_cb(*this), //m_in_dack_cb(*this), m_out_p0_cb(*this), m_out_p1_cb(*this) //m_out_so_cb(*this), //m_out_sorq_cb(*this), //m_out_drq_cb(*this) { } upd7725_device::upd7725_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : necdsp_device(mconfig, UPD7725, tag, owner, clock, 11, 11) { } upd96050_device::upd96050_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : necdsp_device(mconfig, UPD96050, tag, owner, clock, 14, 12) { } //------------------------------------------------- // device_start - start up the device //------------------------------------------------- void necdsp_device::device_start() { // get our address spaces m_program = &space(AS_PROGRAM); m_data = &space(AS_DATA); m_direct = m_program->direct<-2>(); // register our state for the debugger state_add(STATE_GENPC, "GENPC", regs.pc).noshow(); state_add(STATE_GENPCBASE, "CURPC", regs.pc).noshow(); state_add(UPD7725_PC, "PC", regs.pc); state_add(UPD7725_RP, "RP", regs.rp); state_add(UPD7725_DP, "DP", regs.dp); state_add(UPD7725_SP, "SP", regs.sp); state_add(UPD7725_K, "K", regs.k); state_add(UPD7725_L, "L", regs.l); state_add(UPD7725_M, "M", regs.m); state_add(UPD7725_N, "N", regs.n); state_add(UPD7725_A, "A", regs.a); state_add(UPD7725_B, "B", regs.b); state_add(UPD7725_TR, "TR", regs.tr); state_add(UPD7725_TRB, "TRB", regs.trb); state_add(UPD7725_DR, "DR", regs.dr); state_add(UPD7725_SI, "SI", regs.si); state_add(UPD7725_SO, "SO", regs.so); state_add(UPD7725_IDB, "IDB", regs.idb); // resolve callbacks m_in_int_cb.resolve_safe(0); //m_in_si_cb.resolve_safe(0); //m_in_sck_cb.resolve_safe(0); //m_in_sien_cb.resolve_safe(0); //m_in_soen_cb.resolve_safe(0); //m_in_dack_cb.resolve_safe(0); m_out_p0_cb.resolve_safe(); m_out_p1_cb.resolve_safe(); //m_out_so_cb.resolve_safe(); //m_out_sorq_cb.resolve_safe(); //m_out_drq_cb.resolve_safe(); // save state registrations save_item(NAME(regs.pc)); save_item(NAME(regs.rp)); save_item(NAME(regs.dp)); save_item(NAME(regs.sp)); save_item(NAME(regs.k)); save_item(NAME(regs.l)); save_item(NAME(regs.m)); save_item(NAME(regs.n)); save_item(NAME(regs.a)); save_item(NAME(regs.b)); save_item(NAME(regs.flaga.s1)); save_item(NAME(regs.flaga.s0)); save_item(NAME(regs.flaga.c)); save_item(NAME(regs.flaga.z)); save_item(NAME(regs.flaga.ov1)); save_item(NAME(regs.flaga.ov0)); save_item(NAME(regs.flagb.s1)); save_item(NAME(regs.flagb.s0)); save_item(NAME(regs.flagb.c)); save_item(NAME(regs.flagb.z)); save_item(NAME(regs.flagb.ov1)); save_item(NAME(regs.flagb.ov0)); save_item(NAME(regs.tr)); save_item(NAME(regs.trb)); save_item(NAME(regs.dr)); save_item(NAME(regs.si)); save_item(NAME(regs.so)); save_item(NAME(regs.idb)); save_item(NAME(regs.siack)); save_item(NAME(regs.soack)); save_item(NAME(regs.sr.rqm)); save_item(NAME(regs.sr.usf0)); save_item(NAME(regs.sr.usf1)); save_item(NAME(regs.sr.drs)); save_item(NAME(regs.sr.dma)); save_item(NAME(regs.sr.drc)); save_item(NAME(regs.sr.soc)); save_item(NAME(regs.sr.sic)); save_item(NAME(regs.sr.ei)); save_item(NAME(regs.sr.p0)); save_item(NAME(regs.sr.p1)); save_item(NAME(regs.stack)); save_item(NAME(dataRAM)); save_item(NAME(m_irq)); save_item(NAME(m_irq_firing)); m_icountptr = &m_icount; for (auto & elem : dataRAM) { elem = 0x0000; } // reset registers not reset by the /RESET line (according to section 3.6.1 on the upd7725 advanced production datasheet) m_irq = 0; // not a register, but the current irq pin state regs.rp = 0x0000; regs.dp = 0x0000; regs.sp = 0x0; regs.k = 0x0000; regs.l = 0x0000; regs.m = 0x0000; regs.n = 0x0000; regs.a = 0x0000; regs.b = 0x0000; regs.tr = 0x0000; regs.trb = 0x0000; regs.dr = 0x0000; regs.si = 0x0000; regs.so = 0x0000; regs.idb = 0x0000; } //------------------------------------------------- // device_reset - reset the device //------------------------------------------------- void necdsp_device::device_reset() { // according to 3.6.1 on the upd7725 advanced production datasheet, /RESET resets the following only: regs.pc = 0x0000; regs.sr = 0x0000; m_out_p0_cb(regs.sr.p0); m_out_p1_cb(regs.sr.p1); // TODO: drq callback, once added, should be forced to the inactive state here // TODO: the sorq pin state is also reset to 'low' state regs.flaga = 0x00; regs.flagb = 0x00; regs.siack = 0; regs.soack = 0; // the irq state (if mid-irq) is assumed to also be reset, since the pulse width of reset must be more than 4 opcode clocks m_irq_firing = 0; } //------------------------------------------------- // memory_space_config - return the configuration // of the specified address space, or nullptr if // the space doesn't exist //------------------------------------------------- device_memory_interface::space_config_vector necdsp_device::memory_space_config() const { return space_config_vector { std::make_pair(AS_PROGRAM, &m_program_config), std::make_pair(AS_DATA, &m_data_config) }; } //------------------------------------------------- // state_import - import state into the device, // after it has been set //------------------------------------------------- void necdsp_device::state_import(const device_state_entry &entry) { } //------------------------------------------------- // state_export - export state from the device, // to a known location where it can be read //------------------------------------------------- void necdsp_device::state_export(const device_state_entry &entry) { } //------------------------------------------------- // state_string_export - export state as a string // for the debugger //------------------------------------------------- void necdsp_device::state_string_export(const device_state_entry &entry, std::string &str) const { switch (entry.index()) { case UPD7725_FLAGA: str = string_format("%s %s %c%c %s %s %s %s", regs.flaga.s1 ? "S1" : "s1", regs.flaga.s0 ? "S0" : "s0", regs.flaga.c ? 'C' : 'c', regs.flaga.z ? 'Z' : 'z', regs.flaga.ov1 ? "OV1" : "ov1", regs.flaga.ov0 ? "OV0" : "ov0"); break; case UPD7725_FLAGB: str = string_format("%s %s %c%c %s %s %s %s", regs.flagb.s1 ? "S1" : "s1", regs.flagb.s0 ? "S0" : "s0", regs.flagb.c ? 'C' : 'c', regs.flagb.z ? 'Z' : 'z', regs.flagb.ov1 ? "OV1" : "ov1", regs.flagb.ov0 ? "OV0" : "ov0"); break; } } //------------------------------------------------- // execute_min_cycles - return minimum number of // cycles it takes for one instruction to execute //------------------------------------------------- uint32_t necdsp_device::execute_min_cycles() const { return 4; } //------------------------------------------------- // execute_max_cycles - return maximum number of // cycles it takes for one instruction to execute //------------------------------------------------- uint32_t necdsp_device::execute_max_cycles() const { return 4; } //------------------------------------------------- // execute_input_lines - return the number of // input/interrupt lines //------------------------------------------------- uint32_t necdsp_device::execute_input_lines() const { return 3; // TODO: there should be 11: INT, SCK, /SIEN, /SOEN, SI, and /DACK, plus SO, /SORQ and DRQ; for now, just INT, P0, and P1 are enough. } //------------------------------------------------- // execute_set_input - //------------------------------------------------- void necdsp_device::execute_set_input(int inputnum, int state) { switch (inputnum) { case NECDSP_INPUT_LINE_INT: if ((!m_irq && (CLEAR_LINE != state)) && regs.sr.ei) // detect rising edge AND if EI == 1; { m_irq_firing = 1; regs.sr.ei = 0; } m_irq = (ASSERT_LINE == state); // set old state to current state break; // add more when needed } } //------------------------------------------------- // disassemble - call the disassembly // helper function //------------------------------------------------- util::disasm_interface *necdsp_device::create_disassembler() { return new necdsp_disassembler; } void necdsp_device::execute_run() { uint32_t opcode; do { // call debugger hook if necessary if (device_t::machine().debug_flags & DEBUG_FLAG_ENABLED) { debugger_instruction_hook(this, regs.pc); } if (m_irq_firing == 0) // normal opcode { opcode = m_direct->read_dword(regs.pc) >> 8; regs.pc++; } else if (m_irq_firing == 1) // if we're in an interrupt cycle, execute a op 'nop' first... { // NOP: OP PSEL ALU ASL DPL DPHM RPDCR SRC DST // 00 00 0000 0 00 000(0) 0 0000 0000 opcode = 0x000000; m_irq_firing = 2; } else // m_irq_firing == 2 // ...then a call to 100 { // LCALL: JP BRCH NA BNK(all 0s on 7725) // 10 101000000 00100000000 00 opcode = 0xA80400; m_irq_firing = 0; } switch(opcode >> 22) { case 0: exec_op(opcode); break; case 1: exec_rt(opcode); break; case 2: exec_jp(opcode); break; case 3: exec_ld(opcode); break; } int32_t result = (int32_t)regs.k * regs.l; //sign + 30-bit result regs.m = result >> 15; //store sign + top 15-bits regs.n = result << 1; //store low 15-bits + zero m_icount--; } while (m_icount > 0); } void necdsp_device::exec_op(uint32_t opcode) { uint8_t pselect = (opcode >> 20)&0x3; //P select uint8_t alu = (opcode >> 16)&0xf; //ALU operation mode uint8_t asl = (opcode >> 15)&0x1; //accumulator select uint8_t dpl = (opcode >> 13)&0x3; //DP low modify uint8_t dphm = (opcode >> 9)&0xf; //DP high XOR modify uint8_t rpdcr = (opcode >> 8)&0x1; //RP decrement uint8_t src = (opcode >> 4)&0xf; //move source uint8_t dst = (opcode >> 0)&0xf; //move destination switch(src) { case 0: regs.idb = regs.trb; break; case 1: regs.idb = regs.a; break; case 2: regs.idb = regs.b; break; case 3: regs.idb = regs.tr; break; case 4: regs.idb = regs.dp; break; case 5: regs.idb = regs.rp; break; case 6: regs.idb = m_data->read_word(regs.rp); break; case 7: regs.idb = 0x8000 - regs.flaga.s1; break; //SGN case 8: regs.idb = regs.dr; regs.sr.rqm = 1; break; case 9: regs.idb = regs.dr; break; case 10: regs.idb = regs.sr; break; case 11: regs.idb = regs.si; break; //MSB = first bit in from serial, 'natural' SI register order case 12: regs.idb = bitswap<16>(regs.si, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); break; //LSB = first bit in from serial, 'reversed' SI register order case 13: regs.idb = regs.k; break; case 14: regs.idb = regs.l; break; case 15: regs.idb = dataRAM[regs.dp]; break; } if(alu) { uint16_t p=0, q=0, r=0; Flag flag; bool c=0; flag.c = 0; flag.s1 = 0; flag.ov0 = 0; flag.ov1 = 0; switch(pselect) { case 0: p = dataRAM[regs.dp]; break; case 1: p = regs.idb; break; case 2: p = regs.m; break; case 3: p = regs.n; break; } switch(asl) { case 0: q = regs.a; flag = regs.flaga; c = regs.flagb.c; break; case 1: q = regs.b; flag = regs.flagb; c = regs.flaga.c; break; } switch(alu) { case 1: r = q | p; break; //OR case 2: r = q & p; break; //AND case 3: r = q ^ p; break; //XOR case 4: r = q - p; break; //SUB case 5: r = q + p; break; //ADD case 6: r = q - p - c; break; //SBB case 7: r = q + p + c; break; //ADC case 8: r = q - 1; p = 1; break; //DEC case 9: r = q + 1; p = 1; break; //INC case 10: r = ~q; break; //CMP case 11: r = (q >> 1) | (q & 0x8000); break; //SHR1 (ASR) case 12: r = (q << 1) | (c ? 1 : 0); break; //SHL1 (ROL) case 13: r = (q << 2) | 3; break; //SHL2 case 14: r = (q << 4) | 15; break; //SHL4 case 15: r = (q << 8) | (q >> 8); break; //XCHG } flag.s0 = (r & 0x8000); flag.z = (r == 0); if (!flag.ov1) flag.s1 = flag.s0; switch(alu) { case 1: case 2: case 3: case 10: case 13: case 14: case 15: { flag.c = 0; flag.ov0 = flag.ov1 = 0; // OV0 and OV1 are cleared by any non-add/sub/nop operation break; } case 4: case 5: case 6: case 7: case 8: case 9: { if(alu & 1) { //addition flag.ov0 = (q ^ r) & ~(q ^ p) & 0x8000; flag.c = (r < q); } else { //subtraction flag.ov0 = (q ^ r) & (q ^ p) & 0x8000; flag.c = (r > q); } flag.ov1 = (flag.ov0 & flag.ov1) ? (flag.s1 == flag.s0) : (flag.ov0 | flag.ov1); break; } case 11: { flag.c = q & 1; flag.ov0 = flag.ov1 = 0; // OV0 and OV1 are cleared by any non-add/sub/nop operation break; } case 12: { flag.c = q >> 15; flag.ov0 = flag.ov1 = 0; // OV0 and OV1 are cleared by any non-add/sub/nop operation break; } } switch(asl) { case 0: regs.a = r; regs.flaga = flag; break; case 1: regs.b = r; regs.flagb = flag; break; } } exec_ld((regs.idb << 6) + dst); if (dst != 4) { switch(dpl) { case 1: regs.dp = (regs.dp & 0xf0) + ((regs.dp + 1) & 0x0f); break; //DPINC case 2: regs.dp = (regs.dp & 0xf0) + ((regs.dp - 1) & 0x0f); break; //DPDEC case 3: regs.dp = (regs.dp & 0xf0); break; //DPCLR } regs.dp ^= dphm << 4; } if(rpdcr && (dst != 5)) regs.rp--; } void necdsp_device::exec_rt(uint32_t opcode) { exec_op(opcode); regs.pc = regs.stack[--regs.sp]; regs.sp &= 0xf; } void necdsp_device::exec_jp(uint32_t opcode) { uint16_t brch = (opcode >> 13) & 0x1ff; //branch uint16_t na = (opcode >> 2) & 0x7ff; //next address uint16_t bank = (opcode >> 0) & 0x3; //bank address uint16_t jps = (regs.pc & 0x2000) | (bank << 11) | (na << 0); uint16_t jpl = (bank << 11) | (na << 0); switch(brch) { case 0x000: regs.pc = regs.so; return; //JMPSO case 0x080: if(regs.flaga.c == 0) regs.pc = jps; return; //JNCA case 0x082: if(regs.flaga.c == 1) regs.pc = jps; return; //JCA case 0x084: if(regs.flagb.c == 0) regs.pc = jps; return; //JNCB case 0x086: if(regs.flagb.c == 1) regs.pc = jps; return; //JCB case 0x088: if(regs.flaga.z == 0) regs.pc = jps; return; //JNZA case 0x08a: if(regs.flaga.z == 1) regs.pc = jps; return; //JZA case 0x08c: if(regs.flagb.z == 0) regs.pc = jps; return; //JNZB case 0x08e: if(regs.flagb.z == 1) regs.pc = jps; return; //JZB case 0x090: if(regs.flaga.ov0 == 0) regs.pc = jps; return; //JNOVA0 case 0x092: if(regs.flaga.ov0 == 1) regs.pc = jps; return; //JOVA0 case 0x094: if(regs.flagb.ov0 == 0) regs.pc = jps; return; //JNOVB0 case 0x096: if(regs.flagb.ov0 == 1) regs.pc = jps; return; //JOVB0 case 0x098: if(regs.flaga.ov1 == 0) regs.pc = jps; return; //JNOVA1 case 0x09a: if(regs.flaga.ov1 == 1) regs.pc = jps; return; //JOVA1 case 0x09c: if(regs.flagb.ov1 == 0) regs.pc = jps; return; //JNOVB1 case 0x09e: if(regs.flagb.ov1 == 1) regs.pc = jps; return; //JOVB1 case 0x0a0: if(regs.flaga.s0 == 0) regs.pc = jps; return; //JNSA0 case 0x0a2: if(regs.flaga.s0 == 1) regs.pc = jps; return; //JSA0 case 0x0a4: if(regs.flagb.s0 == 0) regs.pc = jps; return; //JNSB0 case 0x0a6: if(regs.flagb.s0 == 1) regs.pc = jps; return; //JSB0 case 0x0a8: if(regs.flaga.s1 == 0) regs.pc = jps; return; //JNSA1 case 0x0aa: if(regs.flaga.s1 == 1) regs.pc = jps; return; //JSA1 case 0x0ac: if(regs.flagb.s1 == 0) regs.pc = jps; return; //JNSB1 case 0x0ae: if(regs.flagb.s1 == 1) regs.pc = jps; return; //JSB1 case 0x0b0: if((regs.dp & 0x0f) == 0x00) regs.pc = jps; return; //JDPL0 case 0x0b1: if((regs.dp & 0x0f) != 0x00) regs.pc = jps; return; //JDPLN0 case 0x0b2: if((regs.dp & 0x0f) == 0x0f) regs.pc = jps; return; //JDPLF case 0x0b3: if((regs.dp & 0x0f) != 0x0f) regs.pc = jps; return; //JDPLNF case 0x0b4: if(regs.siack == 0) regs.pc = jps; return; //JNSIAK case 0x0b6: if(regs.siack == 1) regs.pc = jps; return; //JSIAK case 0x0b8: if(regs.soack == 0) regs.pc = jps; return; //JNSOAK case 0x0ba: if(regs.soack == 1) regs.pc = jps; return; //JSOAK case 0x0bc: if(regs.sr.rqm == 0) regs.pc = jps; return; //JNRQM case 0x0be: if(regs.sr.rqm == 1) regs.pc = jps; return; //JRQM case 0x100: regs.pc = 0x0000 | jpl; return; //LJMP case 0x101: regs.pc = 0x2000 | jpl; return; //HJMP case 0x140: regs.stack[regs.sp++] = regs.pc; regs.pc = 0x0000 | jpl; regs.sp &= 0xf; return; //LCALL case 0x141: regs.stack[regs.sp++] = regs.pc; regs.pc = 0x2000 | jpl; regs.sp &= 0xf; return; //HCALL } } void necdsp_device::exec_ld(uint32_t opcode) { uint16_t id = opcode >> 6; //immediate data uint8_t dst = (opcode >> 0) & 0xf; //destination regs.idb = id; switch(dst) { case 0: break; case 1: regs.a = id; break; case 2: regs.b = id; break; case 3: regs.tr = id; break; case 4: regs.dp = id; break; case 5: regs.rp = id; break; case 6: regs.dr = id; regs.sr.rqm = 1; break; case 7: regs.sr = (regs.sr & 0x907c) | (id & ~0x907c); m_out_p0_cb(regs.sr.p0); m_out_p1_cb(regs.sr.p1); break; case 8: regs.so = bitswap<16>(id, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); break; //LSB first output, output tapped at bit 15 shifting left case 9: regs.so = id; break; //MSB first output, output tapped at bit 15 shifting left case 10: regs.k = id; break; case 11: regs.k = id; regs.l = m_data->read_word(regs.rp); break; case 12: regs.l = id; regs.k = dataRAM[regs.dp | 0x40]; break; case 13: regs.l = id; break; case 14: regs.trb = id; break; case 15: dataRAM[regs.dp] = id; break; } } uint8_t necdsp_device::snesdsp_read(bool mode) { if (!mode) { return regs.sr >> 8; } if (regs.sr.drc == 0) { //16-bit if(regs.sr.drs == 0) { regs.sr.drs = 1; return regs.dr >> 0; } else { regs.sr.rqm = 0; regs.sr.drs = 0; return regs.dr >> 8; } } else { //8-bit regs.sr.rqm = 0; return regs.dr >> 0; } } void necdsp_device::snesdsp_write(bool mode, uint8_t data) { if (!mode) return; if (regs.sr.drc == 0) { //16-bit if (regs.sr.drs == 0) { regs.sr.drs = 1; regs.dr = (regs.dr & 0xff00) | (data << 0); } else { regs.sr.rqm = 0; regs.sr.drs = 0; regs.dr = (data << 8) | (regs.dr & 0x00ff); } } else { //8-bit regs.sr.rqm = 0; regs.dr = (regs.dr & 0xff00) | (data << 0); } }