// license:LGPL-2.1+ // copyright-holders:Angelo Salese, R. Belmont /************************************************************************************ Sega Saturn SMPC - System Manager and Peripheral Control MCU simulation The SMPC is actually a 4-bit Hitachi HD404920FS MCU, labeled with a Sega custom 315-5744 (that needs decapping) MCU simulation by Angelo Salese & R. Belmont TODO: - timings; - fix intback issue with inputs (according to the docs, it should fall in between VBLANK-IN and OUT, for obvious reasons); - clean-ups; - RTC subdevice (unknown type, handled here for convenience); - Does ST-V even has a battery backed NVRAM? Notes: SMPC NVRAM contents: [0] unknown (always 0) [1] unknown (always 0) [2] ---- -x-- Button Labels (0=enable) ---- --x- Audio Out (1=Mono 0=Stereo) ---- ---x BIOS audio SFXs enable (0=enable) [3] language select (0=English, 5=Japanese) *************************************************************************************/ /* SMPC Addresses 00 01 -w Input Register 0 (IREG) 02 03 -w Input Register 1 04 05 -w Input Register 2 06 07 -w Input Register 3 08 09 -w Input Register 4 0a 0b -w Input Register 5 0c 0d -w Input Register 6 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f -w Command Register (COMREG) 20 21 r- Output Register 0 (OREG) 22 23 r- Output Register 1 24 25 r- Output Register 2 26 27 r- Output Register 3 28 29 r- Output Register 4 2a 2b r- Output Register 5 2c 2d r- Output Register 6 2e 2f r- Output Register 7 30 31 r- Output Register 8 32 33 r- Output Register 9 34 35 r- Output Register 10 36 37 r- Output Register 11 38 39 r- Output Register 12 3a 3b r- Output Register 13 3c 3d r- Output Register 14 3e 3f r- Output Register 15 40 41 r- Output Register 16 42 43 r- Output Register 17 44 45 r- Output Register 18 46 47 r- Output Register 19 48 49 r- Output Register 20 4a 4b r- Output Register 21 4c 4d r- Output Register 22 4e 4f r- Output Register 23 50 51 r- Output Register 24 52 53 r- Output Register 25 54 55 r- Output Register 26 56 57 r- Output Register 27 58 59 r- Output Register 28 5a 5b r- Output Register 29 5c 5d r- Output Register 30 5e 5f r- Output Register 31 60 61 r- SR 62 63 rw SF 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 rw PDR1 76 77 rw PDR2 78 79 -w DDR1 7a 7b -w DDR2 7c 7d -w IOSEL2/1 7e 7f -w EXLE2/1 */ #include "emu.h" #include "machine/smpc.h" #include "screen.h" #include "coreutil.h" #define LOG_SMPC 0 #define LOG_PAD_CMD 0 //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(SMPC_HLE, smpc_hle_device, "smpc_hle", "Sega Saturn SMPC HLE (HD404920FS)") // TODO: use DEVICE_ADDRESS_MAP once this fatalerror is fixed: // "uplift_submaps unhandled case: range straddling slots." void smpc_hle_device::smpc_regs(address_map &map) { // ADDRESS_MAP_UNMAP_HIGH map(0x00, 0x0d).w(FUNC(smpc_hle_device::ireg_w)); map(0x1f, 0x1f).w(FUNC(smpc_hle_device::command_register_w)); map(0x20, 0x5f).r(FUNC(smpc_hle_device::oreg_r)); map(0x61, 0x61).r(FUNC(smpc_hle_device::status_register_r)); map(0x63, 0x63).rw(FUNC(smpc_hle_device::status_flag_r), FUNC(smpc_hle_device::status_flag_w)); map(0x75, 0x75).rw(FUNC(smpc_hle_device::pdr1_r), FUNC(smpc_hle_device::pdr1_w)); map(0x77, 0x77).rw(FUNC(smpc_hle_device::pdr2_r), FUNC(smpc_hle_device::pdr2_w)); map(0x79, 0x79).w(FUNC(smpc_hle_device::ddr1_w)); map(0x7b, 0x7b).w(FUNC(smpc_hle_device::ddr2_w)); map(0x7d, 0x7d).w(FUNC(smpc_hle_device::iosel_w)); map(0x7f, 0x7f).w(FUNC(smpc_hle_device::exle_w)); } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // smpc_hle_device - constructor //------------------------------------------------- smpc_hle_device::smpc_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, SMPC_HLE, tag, owner, clock), device_memory_interface(mconfig, *this), m_space_config("regs", ENDIANNESS_LITTLE, 8, 7, 0, address_map_constructor(), address_map_constructor(FUNC(smpc_hle_device::smpc_regs), this)), m_mini_nvram(*this, "smem"), m_mshres(*this), m_mshnmi(*this), m_sshres(*this), m_sndres(*this), m_sysres(*this), m_syshalt(*this), m_dotsel(*this), m_pdr1_read(*this), m_pdr2_read(*this), m_pdr1_write(*this), m_pdr2_write(*this), m_irq_line(*this), m_ctrl1(nullptr), m_ctrl2(nullptr), m_screen(*this, finder_base::DUMMY_TAG) { m_ctrl1 = nullptr; m_ctrl2 = nullptr; m_has_ctrl_ports = false; } //------------------------------------------------- // device_add_mconfig - device-specific machine // configuration addiitons //------------------------------------------------- MACHINE_CONFIG_START(smpc_hle_device::device_add_mconfig) MCFG_NVRAM_ADD_0FILL("smem") // TODO: custom RTC subdevice MACHINE_CONFIG_END //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void smpc_hle_device::device_start() { system_time systime; machine().base_datetime(systime); // check if SMEM has valid data via byte 4 in the array, if not then simulate a battery backup fail // (-> call the RTC / Language select menu for Saturn) m_mini_nvram->set_base(&m_smem, 5); m_mshres.resolve_safe(); m_mshnmi.resolve_safe(); m_sshres.resolve_safe(); m_sndres.resolve_safe(); m_sysres.resolve_safe(); m_syshalt.resolve_safe(); m_dotsel.resolve_safe(); m_irq_line.resolve_safe(); m_pdr1_read.resolve_safe(0xff); m_pdr2_read.resolve_safe(0xff); m_pdr1_write.resolve_safe(); m_pdr2_write.resolve_safe(); save_item(NAME(m_sf)); save_item(NAME(m_sr)); save_item(NAME(m_ddr1)); save_item(NAME(m_ddr2)); save_item(NAME(m_pdr1_readback)); save_item(NAME(m_pdr2_readback)); save_item(NAME(m_iosel1)); save_item(NAME(m_iosel2)); save_item(NAME(m_exle1)); save_item(NAME(m_exle2)); save_item(NAME(m_ireg)); save_item(NAME(m_oreg)); save_item(NAME(m_comreg)); save_item(NAME(m_command_in_progress)); save_item(NAME(m_intback_buf)); save_item(NAME(m_intback_stage)); save_item(NAME(m_pmode)); save_item(NAME(m_rtc_data)); save_item(NAME(m_smem)); m_cmd_timer = timer_alloc(COMMAND_ID); m_rtc_timer = timer_alloc(RTC_ID); m_intback_timer = timer_alloc(INTBACK_ID); m_sndres_timer = timer_alloc(SNDRES_ID); m_rtc_data[0] = DectoBCD(systime.local_time.year / 100); m_rtc_data[1] = DectoBCD(systime.local_time.year % 100); m_rtc_data[2] = (systime.local_time.weekday << 4) | (systime.local_time.month+1); m_rtc_data[3] = DectoBCD(systime.local_time.mday); m_rtc_data[4] = DectoBCD(systime.local_time.hour); m_rtc_data[5] = DectoBCD(systime.local_time.minute); m_rtc_data[6] = DectoBCD(systime.local_time.second); if (m_has_ctrl_ports) { m_ctrl1 = downcast(machine().device(m_ctrl1_tag)); m_ctrl2 = downcast(machine().device(m_ctrl2_tag)); } // m_has_ctrl_ports = (m_ctrl1 != nullptr && m_ctrl2 != nullptr); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void smpc_hle_device::device_reset() { m_sr = 0x40; // this bit is always on according to docs (?) m_sf = false; m_cd_sf = false; m_ddr1 = 0; m_ddr2 = 0; m_pdr1_readback = 0; m_pdr2_readback = 0; memset(m_ireg,0,7); memset(m_oreg,0,32); m_cmd_timer->reset(); m_intback_timer->reset(); m_sndres_timer->reset(); m_comreg = 0xff; m_command_in_progress = false; m_NMI_reset = false; m_cur_dotsel = false; m_rtc_timer->adjust(attotime::zero, 0, attotime::from_seconds(1)); } device_memory_interface::space_config_vector smpc_hle_device::memory_space_config() const { return space_config_vector { std::make_pair(0, &m_space_config) }; } //************************************************************************** // READ/WRITE HANDLERS //************************************************************************** WRITE8_MEMBER( smpc_hle_device::ireg_w ) { if (!(offset & 1)) // avoid writing to even bytes return; m_ireg[offset >> 1] = data; if(offset == 1) // check if we are under intback { if(m_intback_stage) { if(data & 0x40) { if(LOG_PAD_CMD) printf("SMPC: BREAK request\n"); sr_ack(); m_intback_stage = 0; } else if(data & 0x80) { if(LOG_PAD_CMD) printf("SMPC: CONTINUE request\n"); m_intback_timer->adjust(attotime::from_usec(700)); // TODO: is timing correct? // TODO: following looks wrong here m_oreg[31] = 0x10; sf_set(); } } } } READ8_MEMBER( smpc_hle_device::oreg_r ) { if (!(offset & 1)) // avoid reading to even bytes (TODO: is it 0s or 1s?) return 0x00; return m_oreg[offset >> 1]; } READ8_MEMBER( smpc_hle_device::status_register_r ) { return m_sr; } READ8_MEMBER( smpc_hle_device::status_flag_r ) { // bit 3: CD enable related? return (m_sf<<0) | (m_cd_sf<<3); } WRITE8_MEMBER( smpc_hle_device::status_flag_w ) { m_sf = BIT(data,0); m_cd_sf = false; } READ8_MEMBER( smpc_hle_device::pdr1_r ) { uint8_t res = (m_pdr1_read() & ~m_ddr1) | m_pdr1_readback; return res; } READ8_MEMBER( smpc_hle_device::pdr2_r ) { uint8_t res = (m_pdr2_read() & ~m_ddr2) | m_pdr2_readback; return res; } WRITE8_MEMBER( smpc_hle_device::pdr1_w ) { // pins defined as output returns in input m_pdr1_readback = (data & m_ddr1); m_pdr1_readback &= 0x7f; m_pdr1_write(m_pdr1_readback); // bit 7 can be read back apparently m_pdr1_readback |= data & 0x80; } WRITE8_MEMBER( smpc_hle_device::pdr2_w ) { // pins defined as output returns in input m_pdr2_readback = (data & m_ddr2); m_pdr2_readback &= 0x7f; m_pdr2_write(m_pdr2_readback); // bit 7 can be read back apparently m_pdr2_readback |= data & 0x80; } WRITE8_MEMBER( smpc_hle_device::ddr1_w ) { m_ddr1 = data & 0x7f; } WRITE8_MEMBER( smpc_hle_device::ddr2_w ) { m_ddr2 = data & 0x7f; } WRITE8_MEMBER( smpc_hle_device::iosel_w ) { m_iosel1 = BIT(data,0); m_iosel2 = BIT(data,1); } WRITE8_MEMBER( smpc_hle_device::exle_w ) { m_exle1 = BIT(data,0); m_exle2 = BIT(data,1); } inline void smpc_hle_device::sr_ack() { m_sr &= 0x0f; } inline void smpc_hle_device::sr_set(uint8_t data) { m_sr = data; } inline void smpc_hle_device::sf_ack(bool cd_enable) { m_sf = false; m_cd_sf = cd_enable; } inline void smpc_hle_device::sf_set() { m_sf = true; } // Saturn Direct Mode polling check for delegate bool smpc_hle_device::get_iosel(bool which) { return which == true ? m_iosel2 : m_iosel1; } uint8_t smpc_hle_device::get_ddr(bool which) { return which == true ? m_ddr2 : m_ddr1; } inline void smpc_hle_device::master_sh2_nmi() { m_mshnmi(1); m_mshnmi(0); } inline void smpc_hle_device::irq_request() { m_irq_line(1); m_irq_line(0); } // TODO: trampolines that needs to go away READ8_MEMBER( smpc_hle_device::read ) { return this->space().read_byte(offset); } WRITE8_MEMBER( smpc_hle_device::write ) { this->space().write_byte(offset,data); } //************************************************************************** // Command simulation //************************************************************************** WRITE8_MEMBER( smpc_hle_device::command_register_w ) { // don't send a command if previous one is still in progress // ST-V tries to send a sysres command if OREG31 doesn't return the ack command if(m_command_in_progress == true) return; m_comreg = data & 0x1f; if(data & 0xe0) logerror("%s COMREG = %02x!?\n",this->tag(),data); m_command_in_progress = true; if(m_comreg == 0x0e || m_comreg == 0x0f) { /* on ST-V timing of this is pretty fussy, you get 2 credits at start-up otherwise * My current theory is that the PLL device can halt the whole system until the frequency change occurs. * (cfr. diagram on page 3 of SMPC manual) * I really don't think that the system can do an usable mid-frame clock switching anyway. */ m_syshalt(1); m_cmd_timer->adjust(m_screen->time_until_pos(m_screen->visible_area().max_y,0)); } else if(m_comreg == 0x10) { // copy ireg to our intback buffer for(int i=0;i<3;i++) m_intback_buf[i] = m_ireg[i]; // calculate the timing for intback command int timing; timing = 8; if( m_ireg[0] != 0) // non-peripheral data timing += 8; // TODO: At vblank-out actually ... if( m_ireg[1] & 8) // peripheral data timing += 700; // TODO: check against ireg2, must be 0xf0 m_cmd_timer->adjust(attotime::from_usec(timing)); } else m_cmd_timer->adjust(attotime::from_usec(m_cmd_table_timing[m_comreg])); } void smpc_hle_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch(id) { case COMMAND_ID: { switch(m_comreg) { case 0x00: // MSHON // enable Master SH2 m_mshres(m_comreg & 1); break; case 0x02: // SSHON case 0x03: // SSHOFF // enable or disable Slave SH2 m_sshres(m_comreg & 1); break; case 0x06: // SNDON case 0x07: // SNDOFF // enable or disable 68k m_sndres(m_comreg & 1); break; case 0x08: // CDON case 0x09: // CDOFF // ... m_command_in_progress = false; m_oreg[31] = m_comreg; sf_ack(true); //clear hand-shake flag (TODO: diagnostic wants this to have bit 3 high) return; // case 0x0a: // NETLINKON // case 0x0b: // NETLINKOFF case 0x0d: // SYSRES // send a 1 -> 0 to device reset lines m_sysres(1); m_sysres(0); // send a 1 -> 0 transition to reset line (was PULSE_LINE) m_mshres(1); m_mshres(0); break; case 0x0e: // CKCHG352 case 0x0f: // CKCHG320 m_dotsel(m_comreg & 1); // send a NMI to Master SH2 if enabled if(m_NMI_reset == false) master_sh2_nmi(); // assert Slave SH2 line m_sshres(1); // clear PLL system halt m_syshalt(0); // setup the new dot select m_cur_dotsel = (m_comreg & 1) ^ 1; break; case 0x10: // INTBACK resolve_intback(); return; case 0x16: // SETTIME { for(int i=0;i<7;i++) m_rtc_data[i] = m_ireg[i]; break; } case 0x17: // SETSMEM { for(int i=0;i<4;i++) m_smem[i] = m_ireg[i]; // clear the SETIME variable, simulate a cr2032 battery alive in the system m_smem[4] = 0xff; break; } case 0x18: // NMIREQ // NMI is unconditionally requested master_sh2_nmi(); break; case 0x19: // RESENAB case 0x1a: // RESDISA m_NMI_reset = m_comreg & 1; break; default: logerror("%s unemulated %02x command\n",this->tag(),m_comreg); return; } m_command_in_progress = false; m_oreg[31] = m_comreg; sf_ack(false); break; } case INTBACK_ID: intback_continue_request(); break; case RTC_ID: handle_rtc_increment(); break; // from m68k reset opcode trigger case SNDRES_ID: m_sndres(1); m_sndres(0); break; default: printf("%d\n",id); break; } } void smpc_hle_device::resolve_intback() { int i; m_command_in_progress = false; if(m_intback_buf[0] != 0) { m_oreg[0] = ((m_smem[4] & 0x80) | ((m_NMI_reset & 1) << 6)); for(i=0;i<7;i++) m_oreg[1+i] = m_rtc_data[i]; m_oreg[8] = 0; // CTG0 / CTG1? m_oreg[9] = m_region_code; // TODO: system region on Saturn /* 0-11 -1-- unknown -x-- ---- VDP2 dot select ---- x--- MSHNMI ---- --x- SYSRES ---- ---x SOUNDRES */ m_oreg[10] = 0 << 7 | m_cur_dotsel << 6 | 1 << 5 | 1 << 4 | 0 << 3 | 1 << 2 | 0 << 1 | 0 << 0; m_oreg[11] = 0 << 6; // CDRES for(i=0;i<4;i++) m_oreg[12+i] = m_smem[i]; for(i=0;i<15;i++) m_oreg[16+i] = 0xff; // undefined m_intback_stage = (m_intback_buf[1] & 8) >> 3; // first peripheral sr_set(0x40 | (m_intback_stage << 5)); m_pmode = m_intback_buf[0]>>4; irq_request(); // put issued command in OREG31 m_oreg[31] = 0x10; // TODO: doc says 0? /* clear hand-shake flag */ sf_ack(false); } else if(m_intback_buf[1] & 8) { m_intback_stage = (m_intback_buf[1] & 8) >> 3; // first peripheral sr_set(0x40); m_oreg[31] = 0x10; intback_continue_request(); } else { /* Shienryu calls this, it would be plainly illegal on Saturn, I'll just return the command and clear the hs flag for now. */ m_oreg[31] = 0x10; sf_ack(false); } } void smpc_hle_device::intback_continue_request() { if( m_has_ctrl_ports == true ) read_saturn_ports(); if (m_intback_stage == 2) { sr_set(0x80 | m_pmode); // pad 2, no more data, echo back pad mode set by intback m_intback_stage = 0; } else { sr_set(0xc0 | m_pmode); // pad 1, more data, echo back pad mode set by intback m_intback_stage ++; } irq_request(); m_oreg[31] = 0x10; // callback for last command issued sf_ack(false); } int smpc_hle_device::DectoBCD(int num) { int i, cnt = 0, tmp, res = 0; while (num > 0) { tmp = num; while (tmp >= 10) tmp %= 10; for (i=0; i 10*/ if((m_rtc_data[6] & 0x0f) >= 0x0a) { m_rtc_data[6]+=0x10; m_rtc_data[6]&=0xf0; } /* seconds from 59 -> 0 */ if((m_rtc_data[6] & 0xf0) >= 0x60) { m_rtc_data[5]++; m_rtc_data[6] = 0; } /* minutes from 9 -> 10 */ if((m_rtc_data[5] & 0x0f) >= 0x0a) { m_rtc_data[5]+=0x10; m_rtc_data[5]&=0xf0; } /* minutes from 59 -> 0 */ if((m_rtc_data[5] & 0xf0) >= 0x60) { m_rtc_data[4]++; m_rtc_data[5] = 0; } /* hours from 9 -> 10 */ if((m_rtc_data[4] & 0x0f) >= 0x0a) { m_rtc_data[4]+=0x10; m_rtc_data[4]&=0xf0; } /* hours from 23 -> 0 */ if((m_rtc_data[4] & 0xff) >= 0x24) { m_rtc_data[3]++; m_rtc_data[2]+=0x10; m_rtc_data[4] = 0; } /* week day name sunday -> monday */ if((m_rtc_data[2] & 0xf0) >= 0x70) { m_rtc_data[2]&=0x0f; } /* day number 9 -> 10 */ if((m_rtc_data[3] & 0x0f) >= 0x0a) { m_rtc_data[3]+=0x10; m_rtc_data[3]&=0xf0; } // year BCD to dec conversion (for the leap year stuff) { year_num = (m_rtc_data[1] & 0xf); for(year_count = 0; year_count < (m_rtc_data[1] & 0xf0); year_count += 0x10) year_num += 0xa; year_num += (m_rtc_data[0] & 0xf)*0x64; for(year_count = 0; year_count < (m_rtc_data[0] & 0xf0); year_count += 0x10) year_num += 0x3e8; } /* month +1 check */ /* the RTC have a range of 1980 - 2100, so we don't actually need to support the leap year special conditions */ if(((year_num % 4) == 0) && (m_rtc_data[2] & 0xf) == 2) { if((m_rtc_data[3] & 0xff) >= dpm[(m_rtc_data[2] & 0xf)-1]+1+1) { m_rtc_data[2]++; m_rtc_data[3] = 0x01; } } else if((m_rtc_data[3] & 0xff) >= dpm[(m_rtc_data[2] & 0xf)-1]+1){ m_rtc_data[2]++; m_rtc_data[3] = 0x01; } /* year +1 check */ if((m_rtc_data[2] & 0x0f) > 12) { m_rtc_data[1]++; m_rtc_data[2] = (m_rtc_data[2] & 0xf0) | 0x01; } /* year from 9 -> 10 */ if((m_rtc_data[1] & 0x0f) >= 0x0a) { m_rtc_data[1]+=0x10; m_rtc_data[1]&=0xf0; } /* year from 99 -> 100 */ if((m_rtc_data[1] & 0xf0) >= 0xa0) { m_rtc_data[0]++; m_rtc_data[1] = 0; } // probably not SO precise, here just for reference ... /* year from 999 -> 1000 */ //if((m_rtc_data[0] & 0x0f) >= 0x0a) { m_rtc_data[0]+=0x10; m_rtc_data[0]&=0xf0; } /* year from 9999 -> 0 */ //if((m_rtc_data[0] & 0xf0) >= 0xa0) { m_rtc_data[0] = 0; } //roll over } /******************************************** * * Saturn handlers * *******************************************/ /* [0] port status: 0x04 Sega-tap 0x16 Multi-tap 0x2x clock serial peripheral 0xf0 peripheral isn't connected 0xf1 peripheral is connected [1] Peripheral ID (note: lowest four bits determines the size of the input packet) 0x02 digital pad 0x25 (tested by Game Basic?) 0x34 keyboard Lower 4 bits of the port status tell the number of controllers to check for the port Lower 4 bits of the peripheral ID tell the number of registers used by each controller For multitap / segatap, we have implemented the following logic: SMPC reads in sequence - status for port 1 - ID first controller, followed by the number of reads needed by the plugged controller - ID second controller, followed by the number of reads needed by the plugged controller - and so on... until the 4th (for SegaTap) or 6th (for Multitap) controller is read TODO: how does the multitap check if a controller is connected? does it ask for the controller status of each subport? how does this work exactly? currently, there is a small problem in some specific controller config which seems to lose track of one controller. E.g. if I put multitap in port2 with inserted joy1, joy2 and joy4 it does not see joy4 controller, but if I put joy1, joy2, joy4 and joy5 it sees all four of them. The same happens if I skip controllers with id = 0xff... how did a real unit behave in this case? */ void smpc_hle_device::read_saturn_ports() { uint8_t status1 = m_ctrl1 ? m_ctrl1->read_status() : 0xf0; uint8_t status2 = m_ctrl2 ? m_ctrl2->read_status() : 0xf0; uint8_t reg_offset = 0; uint8_t ctrl1_offset = 0; // this is used when there is segatap or multitap connected uint8_t ctrl2_offset = 0; // this is used when there is segatap or multitap connected m_oreg[reg_offset++] = status1; // read ctrl1 for (int i = 0; i < (status1 & 0xf); i++) { uint8_t id = m_ctrl1->read_id(i); m_oreg[reg_offset++] = id; for (int j = 0; j < (id & 0xf); j++) m_oreg[reg_offset++] = m_ctrl1->read_ctrl(j + ctrl1_offset); ctrl1_offset += (id & 0xf); } m_oreg[reg_offset++] = status2; // read ctrl2 for (int i = 0; i < (status2 & 0xf); i++) { uint8_t id = m_ctrl2->read_id(i); m_oreg[reg_offset++] = id; for (int j = 0; j < (id & 0xf); j++) m_oreg[reg_offset++] = m_ctrl2->read_ctrl(j + ctrl2_offset); ctrl2_offset += (id & 0xf); } } INPUT_CHANGED_MEMBER(smpc_hle_device::trigger_nmi_r ) { // punt if NMI trigger is disabled if(!m_NMI_reset) return; // TODO: generated during the 3VINT period according to manual if(newval) master_sh2_nmi(); } /* Official documentation says that the "RESET/TAS opcodes aren't supported", but Out Run definitely contradicts with it. Since that m68k can't reset itself via the RESET opcode I suppose that the SMPC actually do it by reading an i/o connected to this opcode. */ void smpc_hle_device::m68k_reset_trigger() { m_sndres_timer->adjust(attotime::from_usec(100)); }