diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 4aa576ad317..87958503bef 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -1854,6 +1854,18 @@ if (MACHINES["M3002"]~=null) then } end +--------------------------------------------------- +-- +--@src/devices/machine/m68sfdc.h,MACHINES["M68SFDC"] = true +--------------------------------------------------- + +if (MACHINES["M68SFDC"]~=null) then + files { + MAME_DIR .. "src/devices/machine/m68sfdc.cpp", + MAME_DIR .. "src/devices/machine/m68sfdc.h", + } +end + --------------------------------------------------- -- --@src/devices/machine/m6m80011ap.h,MACHINES["M6M80011AP"] = true diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index 07d475f840d..12f77257758 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -552,6 +552,7 @@ MACHINES["LSI53C810"] = true MACHINES["M3002"] = true MACHINES["M68307"] = true MACHINES["M68340"] = true +MACHINES["M68SFDC"] = true MACHINES["M6M80011AP"] = true MACHINES["MB14241"] = true MACHINES["MB3773"] = true @@ -2902,6 +2903,7 @@ files { createMESSProjects(_target, _subtarget, "motorola") files { + MAME_DIR .. "src/mame/drivers/exorciser.cpp", MAME_DIR .. "src/mame/drivers/m6805evs.cpp", MAME_DIR .. "src/mame/drivers/m68705prg.cpp", MAME_DIR .. "src/mame/drivers/mekd1.cpp", diff --git a/src/devices/machine/m68sfdc.cpp b/src/devices/machine/m68sfdc.cpp new file mode 100644 index 00000000000..471952cf1a6 --- /dev/null +++ b/src/devices/machine/m68sfdc.cpp @@ -0,0 +1,869 @@ +// license:BSD-3-Clause +// copyright-holders:68bit +// +// Motorola M68SFDC floppy disk controller +// +// References: +// +// "M68SFDC2(D) EXORdisk II Floppy disk controller module - Users's guide.", +// Motorola, June 1978. +// +// "AN-764: A floppy disk controller using the MC6852 SSDA and other M6800 +// microprocessor family parts", Motorola 1976. + +#include "emu.h" +#include "m68sfdc.h" + +m68sfdc_device::m68sfdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + device_t(mconfig, M68SFDC, tag, owner, clock), + m_pia(*this, "pia"), + m_ssda(*this, "ssda"), + m_timer_head_load(nullptr), + m_timer_timeout(nullptr), + m_irq_handler(*this), + m_nmi_handler(*this), + m_select2_mode(*this, "SELECT2_MODE"), + m_select3_mode(*this, "SELECT3_MODE"), + m_disk_sides(*this, "DISK_SIDES"), + m_write_protect_mode(*this, "WRITE_PROTECT_MODE"), + m_stepper_mode(*this, "STEPPER_MODE") +{ +} + +INPUT_PORTS_START(m68sfdc) + + PORT_START("SELECT2_MODE") + PORT_CONFNAME(0x01, 0x00, "Select 2 line mode") + PORT_CONFSETTING(0, "Not connected") + PORT_CONFSETTING(1, "Selects drives 2 and 3") + + PORT_START("SELECT3_MODE") + PORT_CONFNAME(0x01, 0x00, "Select 3 line mode") + PORT_CONFSETTING(0, "Not connected") + PORT_CONFSETTING(1, "Selects drive head") + + PORT_START("DISK_SIDES") + PORT_CONFNAME(0x20, 0x20, "Disk sides switch") + PORT_CONFSETTING(0x20, "Single sided") + PORT_CONFSETTING(0x00, "Double sided") + + PORT_START("WRITE_PROTECT_MODE") + PORT_CONFNAME(0x1, 0x1, "Write-enabled line mode") + PORT_CONFSETTING(0x0, "Active low write protect") + PORT_CONFSETTING(0x1, "Active low write enabled") + + PORT_START("STEPPER_MODE") + PORT_CONFNAME(0x1, 0x0, "Stepper control lines mode") + PORT_CONFSETTING(0x0, "Conventional") + PORT_CONFSETTING(0x1, "'Step' line steps in, 'direction' line steps out") + +INPUT_PORTS_END + +ioport_constructor m68sfdc_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(m68sfdc); +} + +void m68sfdc_device::device_resolve_objects() +{ +} + +void m68sfdc_device::device_start() +{ + m_irq_handler.resolve_safe(); + m_nmi_handler.resolve_safe(); + + m_timer_head_load = timer_alloc(TM_HEAD_LOAD); + m_timer_timeout = timer_alloc(TM_TIMEOUT); + save_item(NAME(m_select_0)); + save_item(NAME(m_select_1)); + save_item(NAME(m_select_2)); + save_item(NAME(m_select_3)); + save_item(NAME(m_step)); + save_item(NAME(m_direction)); + save_item(NAME(m_head_load1)); + save_item(NAME(m_head_load2)); + save_item(NAME(m_head_load)); + save_item(NAME(m_crc)); + save_item(NAME(m_last_crc)); + save_item(NAME(m_pia_ca1)); + save_item(NAME(m_pia_cb2)); + save_item(NAME(m_reset)); + save_item(NAME(m_enable_drive_write)); + save_item(NAME(m_enable_read)); + save_item(NAME(m_shift_crc)); + save_item(NAME(m_shift_crc_count)); + save_item(NAME(m_tuf_count)); + save_item(NAME(m_ssda_reg)); + + m_floppy = nullptr; + + t_gen = timer_alloc(TM_GEN); +} + +void m68sfdc_device::device_reset() +{ + m_select_0 = 0; + m_select_1 = 0; + m_select_2 = 0; + m_select_3 = 0; + m_step = 1; + m_direction = 0; + m_head_load1 = 0; + m_head_load2 = 0; + m_head_load = 0; + m_crc = 0; + m_last_crc = 0; + m_pia_ca1 = 0; + m_pia_cb2 = 0; + m_reset = 1; + m_enable_drive_write = 0; + m_enable_read = 0; + m_shift_crc = 0; + m_shift_crc_count = 0; + m_tuf_count = 0; + + m_irq_handler(false); + m_nmi_handler(false); +} + +void m68sfdc_device::set_floppies_4(floppy_connector *f0, floppy_connector *f1, floppy_connector *f2, floppy_connector *f3) +{ + m_floppy0 = f0; + m_floppy1 = f1; + m_floppy2 = f2; + m_floppy3 = f3; + + if (m_floppy0) + { + m_floppy = m_floppy0->get_device(); + } +} + +WRITE_LINE_MEMBER(m68sfdc_device::handle_irq) +{ + m_irq_handler(state); +} + +WRITE_LINE_MEMBER(m68sfdc_device::handle_nmi) +{ + m_nmi_handler(state); +} + +void m68sfdc_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + case TM_HEAD_LOAD: + { + live_sync(); + m_head_load2 = 0; + u8 head_load = m_head_load1 && m_head_load2; + if (head_load != m_head_load) + { + // TODO sound? + m_head_load = head_load; + } + break; + } + case TM_TIMEOUT: + { + live_sync(); + m_pia->ca1_w(0); + m_pia_ca1 = 0; + break; + } + case TM_GEN: + live_sync(); + live_run(); + break; + default: + throw emu_fatalerror("Unknown id in m68sfdc_device::device_timer"); + } +} + + + +uint8_t m68sfdc_device::flip_bits(uint8_t data) +{ + data = (data & 0b11110000) >> 4 | (data & 0b00001111) << 4; + data = (data & 0b11001100) >> 2 | (data & 0b00110011) << 2; + data = (data & 0b10101010) >> 1 | (data & 0b01010101) << 1; + return data; +} + + +u8 m68sfdc_device::read(offs_t offset) +{ + if (!machine().side_effects_disabled()) + { + live_sync(); + // Triggers the 0.8 second head-load timer. + m_timer_head_load->reset(attotime::from_msec(800)); + } + + if (offset > 3) + { + u8 data = m_ssda->read(offset - 4); + // The data bits are connected in reverse. + data = (data & 0b11110000) >> 4 | (data & 0b00001111) << 4; + data = (data & 0b11001100) >> 2 | (data & 0b00110011) << 2; + data = (data & 0b10101010) >> 1 | (data & 0b01010101) << 1; + return data; + + } + + // The 6821 address lines are swapped. + offset = ((offset & 1) << 1) | (offset >> 1); + return m_pia->read(offset); +} + +#define C1_RX_RS 0x01 +#define C1_AC_MASK 0xc0 +#define C1_AC_C2 0x00 +#define C2_PC_MASK 0x03 +#define C2_PC1 0x01 + + +void m68sfdc_device::write(offs_t offset, u8 data) +{ + live_sync(); + + // Triggers the 0.8 second head-load timer. + m_head_load2 = 1; + m_timer_head_load->reset(attotime::from_msec(800)); + + if (offset > 3) + { + // Address line A1 is not decoded for the SSDA + offset = (offset - 4) & 0x0001; + + // The data bits are connected in reverse. + data = (data & 0b11110000) >> 4 | (data & 0b00001111) << 4; + data = (data & 0b11001100) >> 2 | (data & 0b00110011) << 2; + data = (data & 0b10101010) >> 1 | (data & 0b01010101) << 1; + m_ssda->write(offset, data); + + // Maintain shadow copies of the 6852 register writes. + if (offset == 0) + m_ssda_reg[0] = data; + else + m_ssda_reg[(m_ssda_reg[0] >> 6) + 1] = data; + + if (offset == 1 && (m_ssda_reg[0] & C1_AC_MASK) == C1_AC_C2 && + (data & C2_PC_MASK) == C2_PC1 && m_enable_read) + { + // This a write to the 6852 CR2 register which enables + // the SM output (PC2 = 0, PC1 = 1), while the read + // logic is enabled. At this point all is setup to + // search for a sync code. + if (m_reset == 0 && m_enable_read) + { + live_start(SYNC1); + } + } + + if (offset == 0 && m_enable_read && (data & C1_RX_RS) != 0) + { + live_abort(); + } + + return; + } + + // The 6821 address lines are swapped. + offset = ((offset & 1) << 1) | (offset >> 1); + m_pia->write(offset, data); +} + + +uint8_t m68sfdc_device::pia_pa_r() +{ + int ready = 1; + int track0 = 1; + if (m_floppy) + { + ready = m_floppy->ready_r(); + track0 = m_floppy->trk00_r(); + } + + // While this is not connected in the schematic, the MDOS 3 format + // command probes this input to determine if a disk is to be formatted + // singled sided (1) or double sided (0), and it is assumed to be a + // later revision. + int sides = m_disk_sides->read(); + + return (track0 ? 0 : 0x80) | (ready << 6) | sides; +} + +void m68sfdc_device::update_floppy_selection() +{ + floppy_image_device *floppy = nullptr; + u8 select2_mode = m_select2_mode->read(); + + if (select2_mode == 0 || m_select_2 == 0) + { + if (!m_select_1 && m_select_0) + floppy = m_floppy0->get_device(); + else if (m_select_1 && !m_select_0) + floppy = m_floppy1->get_device(); + } + else + { + if (!m_select_1 && m_select_0) + floppy = m_floppy2->get_device(); + else if (m_select_1 && !m_select_0) + floppy = m_floppy3->get_device(); + } + + if (floppy != m_floppy) + { + if (m_floppy) + { + m_floppy->mon_w(1); // Active low + m_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb()); + } + m_floppy = floppy; + if (m_floppy) + { + // Assume the motors are always on? + m_floppy->mon_w(0); // Active low + if (m_stepper_mode->read()) + { + m_floppy->dir_w(0); + m_floppy->stp_w(0); + } + else + { + m_floppy->dir_w(m_direction); + m_floppy->stp_w(m_step); + } + m_floppy->ss_w(m_select3_mode->read() ? m_select_3 : 0); + m_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&m68sfdc_device::fdc_index_callback, this)); + } + } +} + +void m68sfdc_device::pia_pa_w(u8 data) +{ + // Select 0 and select 1 are used for drive selection. When 0x02 these + // select drive 0 or 2, and when 0x01 select drive 1 or 3. These are + // used in conjuction with select 2 to decode four drives. + m_select_0 = !BIT(data, 0); + m_select_1 = !BIT(data, 1); + // u8 m_gt_trk43 = !BIT(data, 2); + u8 direction = !BIT(data, 3); + m_head_load1 = !BIT(data, 4); + + if (m_floppy) + { + if (m_stepper_mode->read()) + { + if (m_direction == 0 && direction == 1) + { + m_floppy->dir_w(0); + m_floppy->stp_w(1); + m_floppy->stp_w(0); + } + } + else + { + m_floppy->dir_w(m_direction); + m_floppy->stp_w(m_step); + } + + m_floppy->ss_w(m_select3_mode->read() ? m_select_3 : 0); + } + m_direction = direction; + + update_floppy_selection(); + + u8 head_load = m_head_load1 && m_head_load2; + if (head_load != m_head_load) + { + // TODO sound? + m_head_load = head_load; + } +} + +int m68sfdc_device::pia_ca1_r() +{ + return m_pia_ca1; +} + +void m68sfdc_device::pia_ca2_w(int state) +{ + if (m_floppy) + { + if (m_stepper_mode->read()) + { + if (m_step == 1 && state == 0) + { + m_floppy->dir_w(1); + m_floppy->stp_w(1); + m_floppy->stp_w(0); + } + } + else + { + m_floppy->dir_w(m_direction); + m_floppy->stp_w(state); + } + } + m_step = state; +} + +uint8_t m68sfdc_device::pia_pb_r() +{ + int wpt = m_floppy ? m_floppy->wpt_r() : 1; + + if (m_write_protect_mode->read()) + wpt = !wpt; + + return (wpt << 4) | (m_crc << 7); +} + +void m68sfdc_device::pia_pb_w(u8 data) +{ + u8 reset = BIT(data, 0); + u8 enable_drive_write = !BIT(data, 1); + m_enable_read = BIT(data, 2); + u8 shift_crc = BIT(data, 3); + // Select 2 is used for drive selection in MDOS, expanding the + // capability from 2 to 4 drives. A port value of 1 selects drives 0 + // and 1, and a port value of 0 selects drives 2 and 3. + m_select_2 = !BIT(data, 5); + // Select 3 is used for head selection in MDOS 3. A port value of 1 + // selects head 0, and a port value of 0 selects head 1. + m_select_3 = !BIT(data, 6); + + int reset_edge = m_reset == 0 && reset == 1; + int disable_write_edge = m_enable_drive_write == 1 && enable_drive_write == 0; + int enable_write_edge = m_enable_drive_write == 0 && enable_drive_write == 1; + int shift_crc_edge = m_shift_crc == 0 && shift_crc == 1; + + m_reset = reset; + m_enable_drive_write = enable_drive_write; + m_shift_crc = shift_crc; + + if (m_floppy) + m_floppy->ss_w(m_select3_mode->read() ? m_select_3 : 0); + + update_floppy_selection(); + + if (shift_crc_edge) + m_shift_crc_count = 2; + + if (reset_edge) + m_shift_crc_count = 0; + + // When reset goes high the read circuit switches to using a 500kHz + // clock to search for the sync byte. It also resets the CRC + // calculation. A reset may occur during a write, in a format + // operation, so don't idle if still writing. + if ((reset_edge && !enable_drive_write) || disable_write_edge) + { + // End of read or write operations. + // typically m_enable_read will be low here too. + live_abort(); + } + + if (enable_write_edge && m_floppy && !(m_select_0 && m_select_1)) + { + // Start of write operations, even if the logic is in reset. + m_tuf_count = 0; + live_start(WRITE); + } +} + +int m68sfdc_device::pia_cb1_r() +{ + // Index pulse, active high at CB1. + if (m_floppy) + { + int index = m_floppy->idx_r() ? 0 : 1; + return index; + } + return 0; +} + +void m68sfdc_device::pia_cb2_w(int state) +{ + if (m_pia_cb2 == 1 && state == 0) + { + // Trigger the timeout timer on a high to low transition of CB2 + m_pia->ca1_w(1); + m_pia_ca1 = 1; + m_timer_timeout->reset(attotime::from_msec(800)); + } + m_pia_cb2 = state; +} + +void m68sfdc_device::fdc_index_callback(floppy_image_device *floppy, int state) +{ + live_sync(); + m_pia->cb1_w(state ? 0 : 1); + live_run(); +} + + +void m68sfdc_device::live_start(int state) +{ + cur_live.tm = machine().time(); + cur_live.state = state; + cur_live.next_state = -1; + cur_live.shift_reg = 0; + cur_live.crc = 0xffff; + cur_live.bit_counter = 0; + cur_live.data_separator_phase = false; + cur_live.data_reg = 0; + + pll_reset(cur_live.tm); + checkpoint_live = cur_live; + pll_save_checkpoint(); + + live_run(); +} + +void m68sfdc_device::checkpoint() +{ + pll_commit(m_floppy, cur_live.tm); + checkpoint_live = cur_live; + pll_save_checkpoint(); +} + +void m68sfdc_device::rollback() +{ + cur_live = checkpoint_live; + pll_retrieve_checkpoint(); +} + + +void m68sfdc_device::pll_reset(const attotime &when) +{ + cur_pll.reset(when); + // 500kHz + cur_pll.set_clock(attotime::from_nsec(2000)); +} + +void m68sfdc_device::live_delay(int state) +{ + cur_live.next_state = state; + t_gen->adjust(cur_live.tm - machine().time()); +} + +void m68sfdc_device::live_sync() +{ + if(!cur_live.tm.is_never()) { + if(cur_live.tm > machine().time()) { + rollback(); + live_run(machine().time()); + pll_commit(m_floppy, cur_live.tm); + } else { + pll_commit(m_floppy, cur_live.tm); + if(cur_live.next_state != -1) { + cur_live.state = cur_live.next_state; + cur_live.next_state = -1; + } + if(cur_live.state == IDLE) { + pll_stop_writing(m_floppy, cur_live.tm); + cur_live.tm = attotime::never; + } + } + cur_live.next_state = -1; + checkpoint(); + } +} + +void m68sfdc_device::live_abort() +{ + if(!cur_live.tm.is_never() && cur_live.tm > machine().time()) { + rollback(); + live_run(machine().time()); + } + + pll_stop_writing(m_floppy, cur_live.tm); + cur_live.tm = attotime::never; + cur_live.state = IDLE; + cur_live.next_state = -1; +} + +bool m68sfdc_device::read_one_bit(const attotime &limit) +{ + int bit = pll_get_next_bit(cur_live.tm, m_floppy, limit); + if(bit < 0) + return true; + cur_live.shift_reg = (cur_live.shift_reg << 1) | bit; + cur_live.bit_counter++; + if(cur_live.data_separator_phase) { + cur_live.data_reg = (cur_live.data_reg << 1) | bit; + if((cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000) + cur_live.crc = (cur_live.crc << 1) ^ 0x1021; + else + cur_live.crc = cur_live.crc << 1; + } + cur_live.data_separator_phase = !cur_live.data_separator_phase; + return false; +} + +bool m68sfdc_device::write_one_bit(const attotime &limit) +{ + bool bit = cur_live.shift_reg & 0x8000; + if(pll_write_next_bit(bit, cur_live.tm, m_floppy, limit)) + return true; + if(cur_live.bit_counter & 1) { + if((cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000) + cur_live.crc = (cur_live.crc << 1) ^ 0x1021; + else + cur_live.crc = cur_live.crc << 1; + } + cur_live.shift_reg = cur_live.shift_reg << 1; + cur_live.bit_counter--; + return false; +} + +void m68sfdc_device::live_write_fm(uint8_t fm) +{ + uint16_t raw = 0xaaaa; + for(int i=0; i<8; i++) + if(fm & (0x80 >> i)) + raw |= 0x4000 >> (2*i); + cur_live.data_reg = fm; + cur_live.shift_reg = raw; +} + +void m68sfdc_device::live_run(attotime limit) +{ + if(cur_live.state == IDLE || cur_live.next_state != -1) + return; + + if(limit == attotime::never) { + if(m_floppy) + limit = m_floppy->time_next_index(); + if(limit == attotime::never) { + // Happens when there's no disk or if the wd is not + // connected to a drive, hence no index pulse. Force a + // sync from time to time in that case, so that the main + // cpu timeout isn't too painful. Avoids looping into + // infinity looking for data too. + + limit = machine().time() + attotime::from_msec(1); + t_gen->adjust(attotime::from_msec(1)); + } + } + + for(;;) { + switch(cur_live.state) { + case SYNC1: { + if(read_one_bit(limit)) + return; + + // The SSDA performs the sync code search, and the code + // will have been loaded into the SSDA sync code + // register. This is emulated here, and the code loaded + // from a copy of SSDA register writes. + int sync = flip_bits(m_ssda_reg[3]); + + // The SSDA searches for only the 8-bit 0xf5 code, and + // the CPU loads and checks the subsequent code. The + // 0xaa prefix check is an emulator hack for now to + // improve detection reliability. + if ((cur_live.shift_reg & 0xff) == sync && + (cur_live.shift_reg >> 8) == 0xaa) + { + // Initialize the CRC. The hardware has an 8 + // bit shift register to delay the bit stream + // so that it can reset the CRC on this sync + // event and then feed it the delayed sync + // code. + cur_live.crc = 0xffff; + cur_live.data_separator_phase = false; + cur_live.bit_counter = 0; + for (int i = 6; i >= 0; i-=2) + { + int bit = BIT(cur_live.shift_reg, i); + if((cur_live.crc ^ (bit ? 0x8000 : 0x0000)) & 0x8000) + cur_live.crc = (cur_live.crc << 1) ^ 0x1021; + else + cur_live.crc = cur_live.crc << 1; + } + live_delay(SYNC_BYTE1); + return; + } + + break; + } + case SYNC_BYTE1: + m_ssda->receive_byte(flip_bits(cur_live.shift_reg & 0xff)); + cur_live.state = SYNC2; + checkpoint(); + break; + + case SYNC2: { + if(read_one_bit(limit)) + return; + + if(cur_live.bit_counter == 8) + { + live_delay(SYNC_BYTE2); + return; + } + + break; + } + case SYNC_BYTE2: + m_ssda->receive_byte(flip_bits(cur_live.shift_reg & 0xff)); + cur_live.bit_counter = 0; + cur_live.state = READ; + checkpoint(); + break; + + case READ: { + if(read_one_bit(limit)) + return; + + if(cur_live.bit_counter & 15) + break; + + live_delay(READ_BYTE); + return; + } + + case READ_BYTE: + m_ssda->receive_byte(flip_bits(cur_live.data_reg)); + cur_live.state = READ; + + // The data to the CRC generator is delayed 8 bits behind + // the SSDA data input delaying the CRC line. + m_crc = m_last_crc; + m_last_crc = cur_live.crc != 0; + + // Unfortunately the emulated system can at times read + // the CRC line early, the timing needs work, so as a + // workaround for now the CRC line is asserted early at + // expected CRC end positions: address marks, and 128 + // and 256 byte data sectors. + if (cur_live.bit_counter == (4 + 2) * 16 || + cur_live.bit_counter == (128 + 2) * 16 || + cur_live.bit_counter == (256 + 2) * 16) + { + m_crc = m_last_crc; + } + + checkpoint(); + break; + + case WRITE: + { + int tuf; + u8 data = flip_bits(m_ssda->get_tx_byte(&tuf)); + + if (tuf) + { + m_tuf_count = 3; + } + else if (m_tuf_count > 0) + { + if (m_tuf_count == 2) + { + // Start of the sync code, + // initialize the CRC. + cur_live.crc = 0xffff; + } + + } + + if (m_tuf_count > 0) + { + // Data clocked at 500kHz + cur_live.shift_reg = data << 8; + cur_live.bit_counter = 8; + m_tuf_count--; + } + else + { + // Data clocked at 250kHz + + // If the 'shift crc' line has been asserted + // then write the CRC code rather than the SSDA + // data, and for two bytes. + if (m_shift_crc_count > 0) + { + // Two CRC bytes + data = cur_live.crc >> 8; + m_shift_crc_count--; + } + + live_write_fm(data); + cur_live.bit_counter = 16; + } + + cur_live.state = WRITE_BITS; + checkpoint(); + break; + } + + case WRITE_BITS: + if(write_one_bit(limit)) + return; + if(cur_live.bit_counter == 0) { + live_delay(WRITE); + return; + } + break; + + default: + logerror("%s: Unknown live state %d\n", cur_live.tm.to_string(), cur_live.state); + return; + } + } +} + +void m68sfdc_device::pll_commit(floppy_image_device *floppy, const attotime &tm) +{ + cur_pll.commit(floppy, tm); +} + +void m68sfdc_device::pll_stop_writing(floppy_image_device *floppy, const attotime &tm) +{ + cur_pll.stop_writing(floppy, tm); +} + +void m68sfdc_device::pll_save_checkpoint() +{ + checkpoint_pll = cur_pll; +} + +void m68sfdc_device::pll_retrieve_checkpoint() +{ + cur_pll = checkpoint_pll; +} + +int m68sfdc_device::pll_get_next_bit(attotime &tm, floppy_image_device *floppy, const attotime &limit) +{ + return cur_pll.get_next_bit(tm, m_floppy, limit); +} + +bool m68sfdc_device::pll_write_next_bit(bool bit, attotime &tm, floppy_image_device *floppy, const attotime &limit) +{ + return cur_pll.write_next_bit(bit, tm, m_floppy, limit); +} + +void m68sfdc_device::device_add_mconfig(machine_config &config) +{ + PIA6821(config, m_pia, 0); + m_pia->readpa_handler().set(FUNC(m68sfdc_device::pia_pa_r)); + m_pia->writepa_handler().set(FUNC(m68sfdc_device::pia_pa_w)); + m_pia->readca1_handler().set(FUNC(m68sfdc_device::pia_ca1_r)); + m_pia->ca2_handler().set(FUNC(m68sfdc_device::pia_ca2_w)); + m_pia->readpb_handler().set(FUNC(m68sfdc_device::pia_pb_r)); + m_pia->writepb_handler().set(FUNC(m68sfdc_device::pia_pb_w)); + m_pia->readcb1_handler().set(FUNC(m68sfdc_device::pia_cb1_r)); + m_pia->cb2_handler().set(FUNC(m68sfdc_device::pia_cb2_w)); + m_pia->irqa_handler().set(FUNC(m68sfdc_device::handle_nmi)); + m_pia->irqb_handler().set(FUNC(m68sfdc_device::handle_irq)); + + MC6852(config, m_ssda, 0); +} + +DEFINE_DEVICE_TYPE(M68SFDC, m68sfdc_device, "m68sfdc", "M68SFDC") diff --git a/src/devices/machine/m68sfdc.h b/src/devices/machine/m68sfdc.h new file mode 100644 index 00000000000..2818d406797 --- /dev/null +++ b/src/devices/machine/m68sfdc.h @@ -0,0 +1,149 @@ +// license:BSD-3-Clause +// copyright-holders:68bit +// +// Motorola M68SFDC floppy disk controller + +#ifndef MAME_MACHINE_M68SFDC_H +#define MAME_MACHINE_M68SFDC_H + +#pragma once + +#include "machine/6821pia.h" +#include "machine/clock.h" +#include "machine/fdc_pll.h" +#include "machine/mc6852.h" +#include "machine/timer.h" +#include "imagedev/floppy.h" + +INPUT_PORTS_EXTERN(m68sfdc); + +class m68sfdc_device : public device_t { +public: + m68sfdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + void write(offs_t reg, uint8_t val); + uint8_t read(offs_t reg); + + auto irq_handler() { return m_irq_handler.bind(); } + auto nmi_handler() { return m_nmi_handler.bind(); } + + void set_floppies_4(floppy_connector*, floppy_connector*, floppy_connector*, floppy_connector*); + +private: + required_device m_pia; + required_device m_ssda; + emu_timer *m_timer_head_load; + emu_timer *m_timer_timeout; + + devcb_write_line m_irq_handler; + devcb_write_line m_nmi_handler; + DECLARE_WRITE_LINE_MEMBER(handle_irq); + DECLARE_WRITE_LINE_MEMBER(handle_nmi); + + uint8_t flip_bits(uint8_t data); + uint8_t pia_pa_r(); + void pia_pa_w(u8 data); + int pia_ca1_r(); + void pia_ca2_w(int state); + uint8_t pia_pb_r(); + void pia_pb_w(u8 data); + int pia_cb1_r(); + void pia_cb2_w(int state); + u8 m_select_0; + u8 m_select_1; + u8 m_select_2; + u8 m_select_3; + u8 m_step; + u8 m_direction; + u8 m_head_load1; + u8 m_head_load2; + u8 m_head_load; + u8 m_crc; + u8 m_last_crc; + u8 m_pia_ca1; + u8 m_pia_cb2; + u8 m_reset; + u8 m_enable_drive_write; + u8 m_enable_read; + u8 m_shift_crc; + u8 m_shift_crc_count; + u8 m_tuf_count; + u8 m_ssda_reg[5]; // Copy of register writes + + required_ioport m_select2_mode; + required_ioport m_select3_mode; + required_ioport m_disk_sides; + required_ioport m_write_protect_mode; + required_ioport m_stepper_mode; + void update_floppy_selection(); + void fdc_index_callback(floppy_image_device *floppy, int state); + + floppy_connector *m_floppy0, *m_floppy1, *m_floppy2, *m_floppy3; + floppy_image_device *m_floppy; // Currently selected floppy. + + virtual void device_resolve_objects() override; + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + virtual void device_add_mconfig(machine_config &config) override; + virtual ioport_constructor device_input_ports() const override; + + enum { TM_HEAD_LOAD, TM_TIMEOUT, TM_GEN }; + + enum { + // General "doing nothing" state + IDLE, + + // + SYNC1, + SYNC_BYTE1, + SYNC2, + SYNC_BYTE2, + READ, + READ_BYTE, + WRITE, + WRITE_BITS, + }; + + struct live_info { + enum { PT_NONE, PT_CRC_1, PT_CRC_2 }; + + attotime tm; + int state, next_state; + uint16_t shift_reg; + uint16_t crc; + int bit_counter; + bool data_separator_phase; + uint8_t data_reg; + }; + + live_info cur_live, checkpoint_live; + fdc_pll_t cur_pll, checkpoint_pll; + + void pll_reset(const attotime &when); + //void pll_start_writing(const attotime &tm); + void pll_commit(floppy_image_device *floppy, const attotime &tm); + void pll_stop_writing(floppy_image_device *floppy, const attotime &tm); + int pll_get_next_bit(attotime &tm, floppy_image_device *floppy, const attotime &limit); + bool pll_write_next_bit(bool bit, attotime &tm, floppy_image_device *floppy, const attotime &limit); + void pll_save_checkpoint(); + void pll_retrieve_checkpoint(); + + void live_start(int live_state); + void live_abort(); + void checkpoint(); + void rollback(); + void live_delay(int state); + void live_sync(); + void live_run(attotime limit = attotime::never); + bool read_one_bit(const attotime &limit); + bool write_one_bit(const attotime &limit); + + void live_write_fm(uint8_t fm); + + emu_timer *t_gen; +}; + +DECLARE_DEVICE_TYPE(M68SFDC, m68sfdc_device) + +#endif // MAME_MACHINE_M68SFDC_H diff --git a/src/mame/drivers/exorciser.cpp b/src/mame/drivers/exorciser.cpp new file mode 100644 index 00000000000..82c46c500f7 --- /dev/null +++ b/src/mame/drivers/exorciser.cpp @@ -0,0 +1,827 @@ +// license:BSD-3-Clause +// copyright-holders:68bit +/*************************************************************************** + +Motorola M6800 EXORciser I (M68SDT) + +Note The EXORciser II (M68SDT II) was distinctly different, using dual 64k +address maps (a user map and a supervisor map) and other improvements, and +used EXBUG 2. + +To boot MDOS using the ROM disk boot press 'X' until the EXBUG prompt is seen, +and then enter "MAID" without a LF or CR, and then at the "*" prompt enter +E800;G without a LF or CR. + +EXBUG 1.1 and 1.2 commands: +X + General escape command, exits to the prompt. +MAID + Switches to the MAID (Motorola Active Interface Debug) mode. +LOAD + Load S19 format binary object tapes. At the "SGL/CONT" prompt press S to + load a single program, or press C to load multiple programs and the abort + button when finished, or press X to abort. +VERF + Verifies memory from tape data. At the "SGL/CONT" prompt press S to verify a + single program, or press C to verify multiple programs and the abort button + when finished, or press X to abort. +SRCH + Searches for a S0 header and prints it and prompts CONT/LOAD/VERF and then + press C to continue searching, or press L to switch to the LOAD function, or + press V to switch to the VERF function, or press X to abort. +PNCH + Prompts for a begin and an end address and then for a header of 6 characters + and then prints "EXEC" and waits for Y and then outputs the memory range in + S19 format, or press X to abort. +PRNT + Prints a memory dump. Prompts for a begin and an end address then prints + "EXEC" and waits for Y and then dumps the memory in a readable format, or + press X to abort. +TERM (EXBUG 1.2 extension) + Prompts for a 15 bit hex number which is used as a terminal output delay + between characters. +S10. +S30. +S120 +S240 (EXBUG 1.2 extension) + Select different communication protocols for the tape drive. + + +MAID (Motorola Active Interface Debug) mode has a '*' prompt and the +following commands: +X + General escape command, exits the command or MAID mode. +xxxx/ + Memory examine and change. + xx LF - enter new value, and advance to the next address + LF - advance to the next address + ^ - previous address + xxxx;O - calculate a branch offset from the current to the given address + printing INVL if out of range. +$R + Register display and change. LF advances to the next register. +xxxx;V + Insert a software breakpoint at the given address, up to 8 breakpoints. + These are stored starting at address $ff4f. +$V + Prints the address of the 8 breakpoints. +xxxx;U + Removes the software breakpoint at the given address. +;U + Removes all software breakpoints. +$M + Prints the current search mask and prompts for a new search mask and address + range for use by the ;W search command. +xx;W + Searches the memory range set by the $M command for a byte matching the + given xx masked with the mask set by the $M command, printing all + matches. The test is (byte^xx) & mask == 0. +$T + Prompts for an end address, to trace this address when code is next run. +;T + Deactivates the trace end address set by the $T command. +$S + Prompts for a stop address, which it sets and activates. This is a hardware + breakpoint that activates when the address is touched. +;S + Deactivates the stop address, as set by the $S command. +;G + Execute the user's program from the auto start memory location. +xxxx;G + Execute the user's program from the given address. +;P + Continue executing from the current program counter address. +nn;P + Continue executing from the current program counter address, skipping the + given number of software breakpoints. This does not appear to be reliable + at least not in the emulator? +N + Trace one instruction. +;N +xxxx;N + Trace the next instruction or the given number of instructions. +#nnnnn= + Converts the given decimal number to hex. +#@nnn= + Converts the given octal number to hex. +#$xxxx= + Converts the given hex number to decimal. + + +For all tape formats the PNCH command sends code 0x12, aka DC2, to start tape +recording and code 0x14, aka DC4, to stop tape recording. For tape format +S120, the codes 0x10, 0x30, 0x12 are sent to start tape recording, and the +codes 0x14, 0x10, 0x39 are sent to stop tape recording. + +For tape formats S10 and S30 the ACIA RTS line is used for tape motor control +when reading from the tape, but not when writing to the tape, and the code +0x11, aka DC1, is sent to start tape playback, and the code 0x13, aka DC3, is +sent to stop tape playback. + +For tape format S120, the ACIA RTS line is not used for tape motor control, +rather the codes 0x10, 0x37 are sent to start playback from the tape, and the +code 0x13 is sent to stop playback. + +References: + +"M6800 EXORciser User's Guide.", second edition, Motorola, 1975. + +****************************************************************************/ + +#include "emu.h" +#include "cpu/m6800/m6800.h" +#include "machine/bankdev.h" +#include "machine/input_merger.h" +#include "machine/6821pia.h" +#include "machine/6850acia.h" +#include "machine/mc14411.h" +#include "machine/m68sfdc.h" + +#include "bus/rs232/rs232.h" +#include "imagedev/printer.h" + +#include "formats/mdos_dsk.h" + +class exorciser_state : public driver_device +{ +public: + exorciser_state(const machine_config &mconfig, device_type type, const char *tag) + : driver_device(mconfig, type, tag) + , m_maincpu(*this, "maincpu") + , m_bankdev(*this, "bankdev") + , m_mainirq(*this, "mainirq") + , m_mainnmi(*this, "mainnmi") + , m_maincpu_clock(*this, "MAINCPU_CLOCK") + , m_abort_key(*this, "ABORT_KEY") + , m_pia_dbg(*this, "pia_dbg") + , m_acia(*this, "acia") + , m_brg(*this, "brg") + , m_rs232_baud(*this, "RS232_BAUD") + , m_rs232_config(*this, "RS232_CONFIG") + , m_pia_lpt(*this, "pia_lpt") + , m_printer(*this, "printer") + , m_acia_prn(*this, "acia_prn") + , m_fdc(*this, "fdc") + , m_floppy0(*this, "floppy0") + , m_floppy1(*this, "floppy1") + , m_floppy2(*this, "floppy2") + , m_floppy3(*this, "floppy3") + { } + + enum + { + TIMER_TRACE, + }; + + void exorciser(machine_config &config); + + DECLARE_INPUT_CHANGED_MEMBER(maincpu_clock_change); + DECLARE_WRITE_LINE_MEMBER(abort_key_w); + +private: + virtual void machine_reset() override; + virtual void machine_start() override; + + void dbg_map(address_map &map); + void mem_map(address_map &map); + + DECLARE_WRITE_LINE_MEMBER(irq_line_w); + u8 m_irq; + address_space *m_banked_space; + u8 main_r(offs_t offset); + void main_w(offs_t offset, u8 data); + u8 prom_r(offs_t offset); + + required_device m_maincpu; + required_device m_bankdev; + required_device m_mainirq; + required_device m_mainnmi; + required_ioport m_maincpu_clock; + required_ioport m_abort_key; + required_device m_pia_dbg; + required_device m_acia; + required_device m_brg; + required_ioport m_rs232_baud; + required_ioport m_rs232_config; + required_device m_pia_lpt; + required_device m_printer; + required_device m_acia_prn; + required_device m_fdc; + + // RS232 bit rate generator clocks + DECLARE_WRITE_LINE_MEMBER(write_f1_clock); + DECLARE_WRITE_LINE_MEMBER(write_f3_clock); + DECLARE_WRITE_LINE_MEMBER(write_f5_clock); + DECLARE_WRITE_LINE_MEMBER(write_f7_clock); + DECLARE_WRITE_LINE_MEMBER(write_f8_clock); + DECLARE_WRITE_LINE_MEMBER(write_f9_clock); + DECLARE_WRITE_LINE_MEMBER(write_f11_clock); + DECLARE_WRITE_LINE_MEMBER(write_f13_clock); + + u8 m_restart_count; + + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + + void pia_dbg_pa_w(u8 data); + DECLARE_READ_LINE_MEMBER(pia_dbg_ca1_r); + void pia_dbg_pb_w(u8 data); + DECLARE_WRITE_LINE_MEMBER(pia_dbg_ca2_w); + DECLARE_WRITE_LINE_MEMBER(pia_dbg_cb2_w); + u16 m_stop_address; + u8 m_stop_enabled; + + void pia_lpt_pa_w(u8 data); + int pia_lpt_ca1_r(); + void pia_lpt_ca2_w(int state); + uint8_t pia_lpt_pb_r(); + uint8_t m_printer_data; + uint8_t m_printer_data_ready; + + static void exorciser_rs232_devices(device_slot_interface &device); + + DECLARE_FLOPPY_FORMATS(floppy_formats); + required_device m_floppy0; + required_device m_floppy1; + required_device m_floppy2; + required_device m_floppy3; +}; + +void exorciser_state::dbg_map(address_map &map) +{ + map(0x0000, 0xffff).rw(FUNC(exorciser_state::main_r), FUNC(exorciser_state::main_w)); +} + +void exorciser_state::mem_map(address_map &map) +{ + // User RAM + map(0x0000, 0xe800).ram(); + + // Disk driver code. + map(0xe800, 0xebff).rom().region("68fdc2", 0); + + // Disk driver unit + map(0xec00, 0xec07).rw(m_fdc, FUNC(m68sfdc_device::read), FUNC(m68sfdc_device::write)); + + // Line printer + map(0xec10, 0xec13).rw(m_pia_lpt, FUNC(pia6821_device::read), FUNC(pia6821_device::write)); + + // Serial printer. + map(0xec26, 0xec27).rw(m_acia_prn, FUNC(acia6850_device::read), FUNC(acia6850_device::write)); + + // EXBUG + map(0xf000, 0xfbff).rom().region("exbug", 0); + + map(0xfcf4, 0xfcf5).mirror(0x0002).rw(m_acia, FUNC(acia6850_device::read), FUNC(acia6850_device::write)); + map(0xfcf8, 0xfcfb).rw(m_pia_dbg, FUNC(pia6821_device::read), FUNC(pia6821_device::write)); + + // Small PROM with the restart vector and ACIA settings. + map(0xfcfc, 0xfcff).r(FUNC(exorciser_state::prom_r)); + + // EXBUG RAM + map(0xff00, 0xffff).ram(); +} + +/* Input ports */ +static INPUT_PORTS_START( exorciser ) + + PORT_START("ABORT_KEY") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYPAD) PORT_NAME("Abort") PORT_WRITE_LINE_DEVICE_MEMBER(DEVICE_SELF, exorciser_state, abort_key_w) + + // The EXORciser I supported 1MHz, and the EXORciser II also supported + // 1.5 and 2.0MHz. + PORT_START("MAINCPU_CLOCK") + PORT_CONFNAME(0xffffff, 1000000, "CPU clock") PORT_CHANGED_MEMBER(DEVICE_SELF, exorciser_state, maincpu_clock_change, 0) + PORT_CONFSETTING(1000000, "1.0 MHz") + PORT_CONFSETTING(2000000, "1.5 MHz") + PORT_CONFSETTING(4000000, "2.0 MHz") + + PORT_START("RS232_BAUD") + PORT_CONFNAME(0xff, 1, "RS232 Baud Rate") + PORT_CONFSETTING(0x80, "110") + PORT_CONFSETTING(0x40, "150") + PORT_CONFSETTING(0x20, "300") + PORT_CONFSETTING(0x10, "600") + PORT_CONFSETTING(0x08, "1200") + PORT_CONFSETTING(0x04, "2400") + PORT_CONFSETTING(0x02, "4800") + PORT_CONFSETTING(0x01, "9600") + + PORT_START("RS232_CONFIG") + PORT_CONFNAME(0x7F, 0x75, "RS232 Config") + PORT_CONFSETTING(0x61, "7 data bits, 2 stop bits, even parity") + PORT_CONFSETTING(0x65, "7 data bits, 2 stop bits, odd parity") + PORT_CONFSETTING(0x69, "7 data bits, 1 stop bits, even parity") + PORT_CONFSETTING(0x6d, "7 data bits, 1 stop bits, odd parity") + PORT_CONFSETTING(0x71, "8 data bits, 2 stop bits, no parity") + PORT_CONFSETTING(0x75, "8 data bits, 1 stop bit, no parity") + PORT_CONFSETTING(0x79, "8 data bits, 1 stop bit, even parity") + PORT_CONFSETTING(0x7d, "8 data bits, 1 stop bit, odd parity") + +INPUT_PORTS_END + +INPUT_CHANGED_MEMBER(exorciser_state::maincpu_clock_change) +{ + m_maincpu->set_clock(newval); +} + + +WRITE_LINE_MEMBER(exorciser_state::write_f1_clock) +{ + if (BIT(m_rs232_baud->read(), 0)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } + + m_acia_prn->write_txc(state); + m_acia_prn->write_rxc(state); +} + +WRITE_LINE_MEMBER(exorciser_state::write_f3_clock) +{ + if (BIT(m_rs232_baud->read(), 1)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +WRITE_LINE_MEMBER(exorciser_state::write_f5_clock) +{ + if (BIT(m_rs232_baud->read(), 2)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +WRITE_LINE_MEMBER(exorciser_state::write_f7_clock) +{ + if (BIT(m_rs232_baud->read(), 3)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +WRITE_LINE_MEMBER(exorciser_state::write_f8_clock) +{ + if (BIT(m_rs232_baud->read(), 4)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +WRITE_LINE_MEMBER(exorciser_state::write_f9_clock) +{ + if (BIT(m_rs232_baud->read(), 5)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +WRITE_LINE_MEMBER(exorciser_state::write_f11_clock) +{ + if (BIT(m_rs232_baud->read(), 6)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +WRITE_LINE_MEMBER(exorciser_state::write_f13_clock) +{ + if (BIT(m_rs232_baud->read(), 7)) + { + m_acia->write_txc(state); + m_acia->write_rxc(state); + } +} + +u8 exorciser_state::main_r(offs_t offset) +{ + if (offset == m_stop_address && m_stop_enabled && + !machine().side_effects_disabled()) + { + m_pia_dbg->cb1_w(CLEAR_LINE); + m_pia_dbg->cb1_w(ASSERT_LINE); + m_pia_dbg->cb1_w(CLEAR_LINE); + // The PIA does not connect to the NMI, rather + // this triggers some logic that triggers the NMI. + // The stop-on-address works with just this. + // TODO This logic might have some delay etc? + m_mainnmi->in_w<0>(1); + m_mainnmi->in_w<0>(0); + } + if (offset < 0xfffc) + return m_banked_space->read_byte(offset); + else if (m_restart_count < 2) + { + // The MAME debugger appears to read here on reset to + // initialize the PC, so it is not possible to distinguish a + // normal and debug read, so disable this path after the first + // two reads irrespective of side effects being enabled. + m_restart_count++; + if (offset == 0xfffe) + return 0xf0; + if (offset == 0xffff) + return 0x00; + return 0; + } + return m_banked_space->read_byte(offset); +} + +void exorciser_state::main_w(offs_t offset, u8 data) +{ + m_banked_space->write_byte(offset, data); +} + + +// The PROM occupies four addresses 0xfcfc to 0xfcff, decoding A0 and A1. It is +// used to supply the restart address, the first two reads which will be from +// 0xfffe and 0xffff are redirected to this PROM. +// +// The EXBUG firmware reads 0xfcfd to obtain some ACIA configuation bits. EXBUG +// 1.1 masks out all bits except 0x75, and EXBUG 1.2 masks out all bits except +// 0x7f. +// +// The A2 input comes from a circuit that selects the number of stop bits, +// however this might have only been effective at 150 baud. These are config +// options here, as if that small PROM was configured to the desired serial +// protocol settings. Since EXBUG 1.1 masks more of these bits is has less +// effective options. +// +// Input A3 is connected to the /IRQ line and has the effect of setting high +// bits in the byte read from 0xfcfd to allow probing of the /IRQ line. This +// feature does not appear to be used by EXBUG. +// +// Input A4 is a static level selectable via jumpers and when clear all reads +// from 0xfcfc to 0xfcff appear to return zero in the default PROM. +u8 exorciser_state::prom_r(offs_t offset) +{ + switch (offset) + { + case 0: + return 0; + case 1: { + u8 byte = m_rs232_config->read();; + if (!m_irq) + byte |= 0xf0; + return byte; + } + case 2: + // Restart vector + return 0xf0; + case 3: + return 0x00; + } + + return 0; +} + + +void exorciser_state::pia_dbg_pa_w(u8 data) +{ + m_stop_address = (m_stop_address & 0xff00) | data; +} + +WRITE_LINE_MEMBER(exorciser_state::abort_key_w) +{ + m_pia_dbg->ca1_w(!state); + m_mainnmi->input_merger_device::in_w<2>(state); +} + +READ_LINE_MEMBER(exorciser_state::pia_dbg_ca1_r) +{ + return !m_abort_key->read(); +} + +void exorciser_state::pia_dbg_pb_w(u8 data) +{ + m_stop_address = (m_stop_address & 0x00ff) | (data << 8); +} + +WRITE_LINE_MEMBER(exorciser_state::pia_dbg_ca2_w) +{ + m_stop_enabled = !state; +} + + +void exorciser_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + case TIMER_TRACE: + m_mainnmi->input_merger_device::in_w<1>(ASSERT_LINE); + break; + default: + throw emu_fatalerror("Unknown id in exorciser_state::device_timer"); + } +} + +// Note the trace timer delay is actually 11 cycles, but is stretched to 16 +// cycles here to get it working. This is necessary because of inaccurate +// cycle timing in the 6800 emulation, so change the delay to 11 cycles when +// the cycle emulation is more accurate. +WRITE_LINE_MEMBER(exorciser_state::pia_dbg_cb2_w) +{ + if (state) + { + // The trace timer needs to scale with the CPU clock. + uint32_t maincpu_clock = m_maincpu_clock->read(); + if (!maincpu_clock) + maincpu_clock = 10000000; + + timer_set(attotime::from_ticks(16, maincpu_clock), TIMER_TRACE); + } + else + m_mainnmi->input_merger_device::in_w<1>(CLEAR_LINE); +} + + + +void exorciser_state::pia_lpt_pa_w(u8 data) +{ + // External parallel printer data output. + m_printer_data = data; +} + +int exorciser_state::pia_lpt_ca1_r() +{ + // External parallel printer busy input. + return 0; +} + +void exorciser_state::pia_lpt_ca2_w(int state) +{ + // External parallel printer data ready. + + // Trigger on the falling edge. + if (m_printer_data_ready == 1 && state == 0) + { + m_printer->output(m_printer_data); + // Toggle the printer busy line as the software waits for a + // low to high transition. + m_pia_lpt->ca1_w(CLEAR_LINE); + m_pia_lpt->ca1_w(ASSERT_LINE); + m_pia_lpt->ca1_w(CLEAR_LINE); + } + m_printer_data_ready = state; +} + + +uint8_t exorciser_state::pia_lpt_pb_r() +{ + // The printer driver expects the low two bits to be 01 for a printer + // attempt to succeed. + return 1; +} + + +FLOPPY_FORMATS_MEMBER( exorciser_state::floppy_formats ) + FLOPPY_MFI_FORMAT, + FLOPPY_MDOS_FORMAT +FLOPPY_FORMATS_END + + +static void mdos_floppies(device_slot_interface &device) +{ + device.option_add("8sssd", FLOPPY_8_SSSD); // 77 trks ss sd 8" + device.option_add("8dssd", FLOPPY_8_DSSD); // 77 trks ds sd 8" +} + + +WRITE_LINE_MEMBER(exorciser_state::irq_line_w) +{ + m_maincpu->set_input_line(M6800_IRQ_LINE, state); + m_irq = state; +} + +void exorciser_state::machine_reset() +{ + uint32_t maincpu_clock = m_maincpu_clock->read(); + if (maincpu_clock) + m_maincpu->set_clock(maincpu_clock); + + m_brg->rsa_w(0); + m_brg->rsb_w(1); + + m_restart_count = 0; + + m_fdc->set_floppies_4(m_floppy0, m_floppy1, m_floppy2, m_floppy3); + + m_irq = 1; + m_stop_address = 0x0000; + m_stop_enabled = 0; + + m_printer_data = 0; + m_printer_data_ready = 1; + m_pia_lpt->ca1_w(CLEAR_LINE); + +} + +void exorciser_state::machine_start() +{ + m_banked_space = &subdevice("bankdev")->space(AS_PROGRAM); + + save_item(NAME(m_restart_count)); + save_item(NAME(m_irq)); + save_item(NAME(m_stop_address)); + save_item(NAME(m_stop_enabled)); + save_item(NAME(m_printer_data)); + save_item(NAME(m_printer_data_ready)); +} + +static DEVICE_INPUT_DEFAULTS_START(exorterm) + DEVICE_INPUT_DEFAULTS("RS232_RXBAUD", 0xff, RS232_BAUD_9600) + DEVICE_INPUT_DEFAULTS("RS232_TXBAUD", 0xff, RS232_BAUD_9600) + DEVICE_INPUT_DEFAULTS("RS232_STARTBITS", 0xff, RS232_STARTBITS_1) + DEVICE_INPUT_DEFAULTS("RS232_DATABITS", 0xff, RS232_DATABITS_8) + DEVICE_INPUT_DEFAULTS("RS232_PARITY", 0xff, RS232_PARITY_NONE) + DEVICE_INPUT_DEFAULTS("RS232_STOPBITS", 0xff, RS232_STOPBITS_1) +DEVICE_INPUT_DEFAULTS_END + +static DEVICE_INPUT_DEFAULTS_START(printer) + DEVICE_INPUT_DEFAULTS("RS232_RXBAUD", 0xff, RS232_BAUD_9600) + DEVICE_INPUT_DEFAULTS("RS232_TXBAUD", 0xff, RS232_BAUD_9600) + DEVICE_INPUT_DEFAULTS("RS232_STARTBITS", 0xff, RS232_STARTBITS_1) + DEVICE_INPUT_DEFAULTS("RS232_DATABITS", 0xff, RS232_DATABITS_8) + DEVICE_INPUT_DEFAULTS("RS232_PARITY", 0xff, RS232_PARITY_NONE) + DEVICE_INPUT_DEFAULTS("RS232_STOPBITS", 0xff, RS232_STOPBITS_1) +DEVICE_INPUT_DEFAULTS_END + +#include "bus/rs232/exorterm.h" +#include "bus/rs232/null_modem.h" +#include "bus/rs232/pty.h" +#include "bus/rs232/terminal.h" + +void exorciser_state::exorciser_rs232_devices(device_slot_interface &device) +{ + device.option_add("exorterm155", SERIAL_TERMINAL_EXORTERM155); + device.option_add("null_modem", NULL_MODEM); + device.option_add("terminal", SERIAL_TERMINAL); + device.option_add("pty", PSEUDO_TERMINAL); +} + +void exorciser_state::exorciser(machine_config &config) +{ + /* basic machine hardware */ + M6800(config, m_maincpu, 10000000); + m_maincpu->set_addrmap(AS_PROGRAM, &exorciser_state::dbg_map); + + ADDRESS_MAP_BANK(config, m_bankdev, 0); + m_bankdev->set_endianness(ENDIANNESS_BIG); + m_bankdev->set_data_width(8); + m_bankdev->set_addr_width(16); + m_bankdev->set_addrmap(AS_PROGRAM, &exorciser_state::mem_map); + + INPUT_MERGER_ANY_HIGH(config, m_mainirq).output_handler().set(FUNC(exorciser_state::irq_line_w)); + INPUT_MERGER_ANY_HIGH(config, m_mainnmi).output_handler().set_inputline(m_maincpu, INPUT_LINE_NMI); + + MC14411(config, m_brg, XTAL(1'843'200)); + m_brg->out_f<1>().set(FUNC(exorciser_state::write_f1_clock)); + m_brg->out_f<3>().set(FUNC(exorciser_state::write_f3_clock)); + m_brg->out_f<7>().set(FUNC(exorciser_state::write_f7_clock)); + m_brg->out_f<8>().set(FUNC(exorciser_state::write_f8_clock)); + m_brg->out_f<9>().set(FUNC(exorciser_state::write_f9_clock)); + m_brg->out_f<13>().set(FUNC(exorciser_state::write_f13_clock)); + + ACIA6850(config, m_acia, 0); + m_acia->txd_handler().set("rs232", FUNC(rs232_port_device::write_txd)); + m_acia->rts_handler().set("rs232", FUNC(rs232_port_device::write_rts)); + + rs232_port_device &rs232(RS232_PORT(config, "rs232", exorciser_state::exorciser_rs232_devices, "exorterm155")); + rs232.rxd_handler().set(m_acia, FUNC(acia6850_device::write_rxd)); + rs232.set_option_device_input_defaults("exorterm155", DEVICE_INPUT_DEFAULTS_NAME(exorterm)); + + PIA6821(config, m_pia_dbg, 0); + m_pia_dbg->writepa_handler().set(FUNC(exorciser_state::pia_dbg_pa_w)); + m_pia_dbg->readca1_handler().set(FUNC(exorciser_state::pia_dbg_ca1_r)); + m_pia_dbg->writepb_handler().set(FUNC(exorciser_state::pia_dbg_pb_w)); + m_pia_dbg->ca2_handler().set(FUNC(exorciser_state::pia_dbg_ca2_w)); + m_pia_dbg->cb2_handler().set(FUNC(exorciser_state::pia_dbg_cb2_w)); + + // MEX68PI Parallel printer port + PIA6821(config, m_pia_lpt, 0); + m_pia_lpt->writepa_handler().set(FUNC(exorciser_state::pia_lpt_pa_w)); + m_pia_lpt->readca1_handler().set(FUNC(exorciser_state::pia_lpt_ca1_r)); + m_pia_lpt->ca2_handler().set(FUNC(exorciser_state::pia_lpt_ca2_w)); + m_pia_lpt->readpb_handler().set(FUNC(exorciser_state::pia_lpt_pb_r)); + + PRINTER(config, m_printer, 0); + + // MEX6850? Serial printer port + ACIA6850(config, m_acia_prn, 0); + m_acia_prn->txd_handler().set("rs232_prn", FUNC(rs232_port_device::write_txd)); + + rs232_port_device &rs232_prn(RS232_PORT(config, "rs232_prn", default_rs232_devices, "printer")); + rs232_prn.rxd_handler().set(m_acia_prn, FUNC(acia6850_device::write_rxd)); + rs232_prn.set_option_device_input_defaults("printer", DEVICE_INPUT_DEFAULTS_NAME(printer)); + + M68SFDC(config, m_fdc, 0); + m_fdc->irq_handler().set(m_mainirq, FUNC(input_merger_device::in_w<0>)); + m_fdc->nmi_handler().set(m_mainnmi, FUNC(input_merger_device::in_w<3>)); + + FLOPPY_CONNECTOR(config, m_floppy0, mdos_floppies, "8dssd", exorciser_state::floppy_formats).enable_sound(true); + FLOPPY_CONNECTOR(config, m_floppy1, mdos_floppies, "8dssd", exorciser_state::floppy_formats).enable_sound(true); + FLOPPY_CONNECTOR(config, m_floppy2, mdos_floppies, "8dssd", exorciser_state::floppy_formats).enable_sound(true); + FLOPPY_CONNECTOR(config, m_floppy3, mdos_floppies, "8dssd", exorciser_state::floppy_formats).enable_sound(true); + +} + +/* ROM definition */ +ROM_START( exorciser ) + ROM_REGION( 0x0400, "68fdc2", 0 ) + // Later MDOS versions support four double sided disk drives, but these + // ROMs only support two singled sided disk drives. These ROMs are hard + // coded for 26 sectors per track and up to 2002 sectors fill in the 77 + // tracks. Both these ROM versions appear to have largely compatible + // entry points. The MDOS equate file calls this the Disk EROM. + // + // Various MDOS commands, such a 'format' and 'dosgen' probe this ROM + // at 0xebfe and 0xebff and use the contents to select between + // operating modes and features. There are clearly some ROMs supported + // by MDOS that are not represented here. If 0xebff has 'E' then the + // MDOS FORMAT command errors out. If 0xebff has 'C' then the 'write + // enabled' input is interpreted as being an active low write protect + // input, otherwise it is interpreted as being an active low write + // enabled input. If 0xebff has 'C' or 0xebfe has 0x11 or 0x12 then + // disks are formatted single sided otherwise PA5 is consulted. + // + // The MDOS code, such as DOSGEN, expects the byte at $000d to have bit + // 7 set for single sided and clear for double sided disks, yet these + // ROMs clear this bit. For DOSGEN this can be worked around with these + // ROMs by locking out the sectors that would have been allocated + // beyond the single sided disk extent, so lock out 0x7d0 to 0xfa0. The + // FORMAT command does not look at this bit so is not affected by this + // issue. Clearly neither of these ROMs was intended to work cleanly + // with MDOS 3 even using single sided disks. + // + // So it would be great if a ROM can be found that supports double + // sided disks, but if not then there appears to be enough known to + // write a ROM to work with MDOS. The ROM is tightly written already, + // and it is not clear how well double sided support was implemented, + // but the ROM accepts logical sector numbers and to work with both + // single and double sided disks it may need to firstly try a double + // sided operation and if that fails then to fall back to try a single + // sided operation and to set the 0x000d flag appropriately. Note that + // the FORMAT command does not bother to set the 'side' in the address + // marks, so the ROM should ignore that or expect it to be + // zero. Support also needs to be added for four drives which is just a + // matter of setting the 'select 2' output based on bit 1 of 0x0000 and + // updating the bounds check. + // + // The code compression techniques used in this disk driver code are + // edifying, generally reusing code well. For example, flags are used + // to make subtle changes in code paths to reuse many paths. Test or + // comparison instructions are used to probe I/O addresses without + // destroying an accumulator to reduce register pressure. + // + // Good use is made of instructions with overlapping + // interpretations. For example, placing small instructions in the + // operands of other instructions where they become effectively a NOP + // and this saves on branching. Two byte instructions such as loading + // an accumulator are placed in the 16 bit immediate argument of a + // three byte instruction, or one byte instructions such as a shift or + // rotate are placed in the immediate argument of a two byte + // instruction. + // + // This version supports two single sided disk drives. The use of the + // 'step' and 'direction' lines is conventional. It interprets a high + // on the 'write enabled' input as an active low write protected + // input. It drives a serial printer using a 6850 ACIA at $ec26-ec27, + // which is configured for 8 bits, no parity, and 1 stop bit, and + // clocked at 9600 baud here, and it supports XON-XOFF software flow + // control. The last byte of this ROM has been patched from 0x00 to + // 0x43 to work with the MDOS feature detection code. + // + ROM_LOAD("diskeromv2.bin", 0x0000, 0x0400, CRC(09b1e724) SHA1(59b54ded1f9a7266c1f12da37f700b4b478b84bc)) + + // This version supports two single sided disk drives. It toggles the + // 'step' line low to step towards track 0 and toggles the 'direction' + // line low to step away from track 0. It interprets a high on the + // 'write enabled' input as an active low write enabled input rather + // than an active low write protected input. It drives a parallel + // printer using a 6821 PIA at $ec10-ex13. The last two bytes of this + // PROM are 0x07 and 0x50 ('P') - these codes are probed by some MDOS + // commands to select different operation and features. + //ROM_LOAD("diskeromv1.bin", 0x0000, 0x0400, CRC(87bf9b0d) SHA1(dbcce885d21b418a08812234d1581ac101f32536)) + + ROM_REGION( 0x0c00, "exbug", 0 ) + // EXBUG 1.2 adds support for the 'S240' command another punch tape + // protocol, and a 'TERM' command which accepts a 15 bit hex number as + // a terminal output delay between characters. + ROM_DEFAULT_BIOS("exbug12") + ROM_SYSTEM_BIOS(0, "exbug12", "EXBUG version 1.2") + ROMX_LOAD("exbug12.bin", 0x0000, 0x0c00, CRC(c002759b) SHA1(0f63d75148e82fb0b9ce7d64b91464523937e0b7), ROM_BIOS(0)) + ROM_SYSTEM_BIOS(1, "exbug11", "EXBUG version 1.1") + ROMX_LOAD("exbug11.bin", 0x0000, 0x0c00, CRC(5a5db110) SHA1(14f3e14ed809f9ec30b8189e5506ed911127de34), ROM_BIOS(1)) + +ROM_END + +/* Driver */ + +// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS +COMP( 1975, exorciser, 0, 0, exorciser, exorciser, exorciser_state, empty_init, "Motorola", "M6800 EXORciser (M68SDT)", MACHINE_NO_SOUND_HW ) diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 97f32be299a..fc77bb405cd 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -13317,6 +13317,9 @@ deathrac // (c) 1976 Exidy destdrby // (c) 1976 Exidy rhunting // bootleg of deathrac +@source:exorciser.cpp +exorciser // 1975 Motorola + @source:exorterm.cpp exorterm155 // (c) 1979 Motorola diff --git a/src/mame/mess.flt b/src/mame/mess.flt index 8997cfc60ba..d0edb9fd453 100644 --- a/src/mame/mess.flt +++ b/src/mame/mess.flt @@ -277,6 +277,7 @@ eva.cpp evmbug.cpp excali64.cpp exelv.cpp +exorciser.cpp exorterm.cpp exp85.cpp facit4440.cpp