summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author 68bit <53298758+68bit@users.noreply.github.com>2020-07-18 22:12:28 +1000
committer GitHub <noreply@github.com>2020-07-18 08:12:28 -0400
commitbd0b4d9dd0aecf2076838757399717d6465ed935 (patch)
treed3407d8a69298ec05b03bc8e76ad8debf5de52e3
parent73bbbef8a96c9c17975ea4a37bb51ab8af59a72c (diff)
New WORKING machines
New WORKING machines -------------------------- Motorola M6800 EXORciser (M68SDT) [68bit]
-rw-r--r--scripts/src/machine.lua12
-rw-r--r--scripts/target/mame/mess.lua2
-rw-r--r--src/devices/machine/m68sfdc.cpp869
-rw-r--r--src/devices/machine/m68sfdc.h149
-rw-r--r--src/mame/drivers/exorciser.cpp827
-rw-r--r--src/mame/mame.lst3
-rw-r--r--src/mame/mess.flt1
7 files changed, 1863 insertions, 0 deletions
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
@@ -1856,6 +1856,18 @@ 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<pia6821_device> m_pia;
+ required_device<mc6852_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<m6800_cpu_device> m_maincpu;
+ required_device<address_map_bank_device> m_bankdev;
+ required_device<input_merger_device> m_mainirq;
+ required_device<input_merger_device> m_mainnmi;
+ required_ioport m_maincpu_clock;
+ required_ioport m_abort_key;
+ required_device<pia6821_device> m_pia_dbg;
+ required_device<acia6850_device> m_acia;
+ required_device<mc14411_device> m_brg;
+ required_ioport m_rs232_baud;
+ required_ioport m_rs232_config;
+ required_device<pia6821_device> m_pia_lpt;
+ required_device<printer_image_device> m_printer;
+ required_device<acia6850_device> m_acia_prn;
+ required_device<m68sfdc_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<floppy_connector> m_floppy0;
+ required_device<floppy_connector> m_floppy1;
+ required_device<floppy_connector> m_floppy2;
+ required_device<floppy_connector> 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<address_map_bank_device>("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