mirror of
https://github.com/holub/mame
synced 2025-04-16 13:34:55 +03:00
New WORKING machines
New WORKING machines -------------------------- Motorola M6800 EXORciser (M68SDT) [68bit]
This commit is contained in:
parent
73bbbef8a9
commit
bd0b4d9dd0
@ -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
|
||||
|
@ -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",
|
||||
|
869
src/devices/machine/m68sfdc.cpp
Normal file
869
src/devices/machine/m68sfdc.cpp
Normal file
@ -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")
|
149
src/devices/machine/m68sfdc.h
Normal file
149
src/devices/machine/m68sfdc.h
Normal file
@ -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
|
827
src/mame/drivers/exorciser.cpp
Normal file
827
src/mame/drivers/exorciser.cpp
Normal file
@ -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 )
|
@ -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
|
||||
|
||||
|
@ -277,6 +277,7 @@ eva.cpp
|
||||
evmbug.cpp
|
||||
excali64.cpp
|
||||
exelv.cpp
|
||||
exorciser.cpp
|
||||
exorterm.cpp
|
||||
exp85.cpp
|
||||
facit4440.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user