New WORKING machines

New WORKING machines
--------------------------
Motorola M6800 EXORciser (M68SDT) [68bit]
This commit is contained in:
68bit 2020-07-18 22:12:28 +10:00 committed by GitHub
parent 73bbbef8a9
commit bd0b4d9dd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1863 additions and 0 deletions

View File

@ -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

View File

@ -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",

View 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")

View 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

View 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 )

View File

@ -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

View File

@ -277,6 +277,7 @@ eva.cpp
evmbug.cpp
excali64.cpp
exelv.cpp
exorciser.cpp
exorterm.cpp
exp85.cpp
facit4440.cpp