diff options
Diffstat (limited to 'src/devices/cpu/m6502/m50734.cpp')
-rw-r--r-- | src/devices/cpu/m6502/m50734.cpp | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/src/devices/cpu/m6502/m50734.cpp b/src/devices/cpu/m6502/m50734.cpp new file mode 100644 index 00000000000..af58690aa29 --- /dev/null +++ b/src/devices/cpu/m6502/m50734.cpp @@ -0,0 +1,473 @@ +// license:BSD-3-Clause +// copyright-holders:AJR +/********************************************************************** + + Mitsubishi M50734 emulation (preliminary) + + This microcontroller contains no internal memory, RAM or ROM. The + external bus allows P05 to be programmed as a strobe (DME) for a + separable "data" memory space. On-chip peripherals include an ADC, + UART, various general and special-purpose timers and a stepper + motor controller. + +**********************************************************************/ + +#include "emu.h" +#include "m50734.h" + +#define LOG_INIT (1U << 1) +#define LOG_TIMER (1U << 2) +#define LOG_TIMER_X (1U << 3) +#define VERBOSE (0) +#include "logmacro.h" + +// device type definition +DEFINE_DEVICE_TYPE(M50734, m50734_device, "m50734", "Mitsubishi M50734") + +m50734_device::m50734_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : m740_device(mconfig, M50734, tag, owner, clock) + , m_data_config("data", ENDIANNESS_LITTLE, 8, 16, 0) + , m_port_in_cb(*this, 0) + , m_port_out_cb(*this) + , m_analog_in_cb(*this, 0) + , m_port_latch{0, 0, 0, 0} + , m_port_3state{0, 0, 0, 0} + , m_ad_control(0) + , m_ad_register(0) + , m_prescaler_reload{0xff, 0xff, 0xff} + , m_timer_reload{0xff, 0xff, 0xff} + , m_step_counter{0, 0} + , m_phase_counter(0) + , m_smcon{0, 0} + , m_tx_count(0) + , m_tx_reload(0xffff) +{ + program_config.m_internal_map = address_map_constructor(FUNC(m50734_device::internal_map), this); +} + +device_memory_interface::space_config_vector m50734_device::memory_space_config() const +{ + space_config_vector scv = m740_device::memory_space_config(); + if (has_configured_map(AS_DATA)) + scv.emplace_back(AS_DATA, &m_data_config); + return scv; +} + +void m50734_device::device_config_complete() +{ + m740_device::device_config_complete(); + + for (int n = 0; n < 4; n++) + { + if (m_port_in_cb[n].isunset()) + m_port_in_cb[n].bind().set_constant(m_port_3state[n]); + } +} + +void m50734_device::step_motor(int which) +{ + if (!BIT(m_smcon[which], 2)) + return; + + if (--m_step_counter[which] == 0 && !BIT(m_interrupt_control[0], which * 2 + 5)) + { + LOGMASKED(LOG_TIMER, "%s counter empty at %s\n", which ? "Vertical" : "Horizontal", machine().time().to_string()); + m_interrupt_control[0] |= 0x20 << (which * 2); + if (BIT(m_interrupt_control[0], which * 2 + 4)) + set_input_line(M740_INT3_LINE, ASSERT_LINE); + } + + // Increment or decrement HPHC/VPHC + m_phase_counter = (m_phase_counter + ((BIT(m_smcon[which], 1) ? 7 : 1) << (which * 4))) & 0x77; + + // TODO: single shot mode, phase decoder and P2 output +} + +template <int N> +TIMER_CALLBACK_MEMBER(m50734_device::timer_interrupt) +{ + if (!BIT(m_interrupt_control[1], N * 2 + 1)) + { + m_interrupt_control[1] |= 1 << (N * 2 + 1); + if (BIT(m_interrupt_control[1], N * 2)) + { + LOGMASKED(LOG_TIMER, "Timer %d interrupt asserted at %s\n", N + 1, machine().time().to_string()); + set_input_line(M740_INT2_LINE, ASSERT_LINE); + } + } + + if (N == 2) + step_motor(0); // Timer 3 steps HC + else if (N == 0) + step_motor(1); // Timer 0 steps VC + + // Reload timer and prescaler + m_timer[N]->adjust(clocks_to_attotime(16 * u32(m_prescaler_reload[N] + 1) * (m_timer_reload[N] + 1))); +} + +void m50734_device::device_start() +{ + m740_device::device_start(); + + space(has_space(AS_DATA) ? AS_DATA : AS_PROGRAM).specific(m_data); + + m_ad_timer = timer_alloc(FUNC(m50734_device::ad_complete), this); + m_timer[0] = timer_alloc(FUNC(m50734_device::timer_interrupt<0>), this); + m_timer[1] = timer_alloc(FUNC(m50734_device::timer_interrupt<1>), this); + m_timer[2] = timer_alloc(FUNC(m50734_device::timer_interrupt<2>), this); + m_timer_x = timer_alloc(FUNC(m50734_device::timer_x_interrupt), this); + + save_item(NAME(m_port_latch)); + save_item(NAME(m_port_direction)); + save_item(NAME(m_p0_function)); + save_item(NAME(m_p2_p3_function)); + save_item(NAME(m_ad_control)); + save_item(NAME(m_ad_register)); + save_item(NAME(m_prescaler_reload)); + save_item(NAME(m_timer_reload)); + save_item(NAME(m_step_counter)); + save_item(NAME(m_phase_counter)); + save_item(NAME(m_smcon)); + save_item(NAME(m_tx_count)); + save_item(NAME(m_tx_reload)); + save_item(NAME(m_interrupt_control)); +} + +void m50734_device::device_reset() +{ + m740_device::device_reset(); + SP = 0x01ff; + + // Reset port registers + std::fill(std::begin(m_port_direction), std::end(m_port_direction), 0x00); + for (int n = 0; n < 4; n++) + m_port_out_cb[n](m_port_3state[n]); + m_p0_function = 0x00; + m_p2_p3_function = 0x00; + + // Reset A-D + m_ad_control |= 0x04; + m_ad_timer->adjust(attotime::never); + + // Reset stepper motor control registers (datasheet has no implication of RESET affecting these, but mps1200 never fully initializes SMCONH) + m_smcon[0] = 0; + m_smcon[1] = 0; + + // Reset interrupts + std::fill(std::begin(m_interrupt_control), std::end(m_interrupt_control), 0x00); + set_input_line(M740_INT2_LINE, CLEAR_LINE); + set_input_line(M740_INT3_LINE, CLEAR_LINE); + set_input_line(M740_INT4_LINE, CLEAR_LINE); + + // Initialize Timer X + set_timer_x(0x0200); +} + +void m50734_device::read_dummy(u16 adr) +{ + // M50734 outputs RD and WR strobes rather than R/W, so dummy accesses should do nothing +} + +u8 m50734_device::read_data(u16 adr) +{ + if (BIT(m_p0_function, 5)) + return m_data.read_byte(adr); + else + return m740_device::read(adr); +} + +void m50734_device::write_data(u16 adr, u8 val) +{ + if (BIT(m_p0_function, 5)) + m_data.write_byte(adr, val); + else + m740_device::write(adr, val); +} + +u8 m50734_device::interrupt_control_r(offs_t offset) +{ + return m_interrupt_control[2 - offset]; +} + +// TODO: emulate modes other than timer mode +static const char *const s_timer_x_modes[4] = +{ + "timer", + "pulse output", + "event count", + "PWM" +}; + +void m50734_device::interrupt_control_w(offs_t offset, u8 data) +{ + if (offset == 1) + data &= 0x3f; + u8 old_control = std::exchange(m_interrupt_control[2 - offset], data); + if (offset == 2) + { + bool he_ve_interrupt = (data & (data >> 1) & 0x50) != 0; + bool old_interrupt = (old_control & (old_control >> 1) & 0x50) != 0; + if (he_ve_interrupt != old_interrupt) + { + LOGMASKED(LOG_TIMER, "%s: HE/VE interrupt %sactivated by write to interrupt control register 1 ($%02X -> $%02X)\n", machine().describe_context(), he_ve_interrupt ? "": "de", old_control, data); + set_input_line(M740_INT3_LINE, he_ve_interrupt ? ASSERT_LINE : CLEAR_LINE); + } + } + else if (offset == 1) + { + bool timer_interrupt = (data & (data >> 1) & 0x15) != 0; + bool old_interrupt = (old_control & (old_control >> 1) & 0x15) != 0; + if (timer_interrupt != old_interrupt) + { + LOGMASKED(LOG_TIMER, "%s: Timer interrupt %sactivated by write to interrupt control register 2 ($%02X -> $%02X)\n", machine().describe_context(), timer_interrupt ? "": "de", old_control, data); + set_input_line(M740_INT2_LINE, timer_interrupt ? ASSERT_LINE : CLEAR_LINE); + } + } + else + { + bool tx_run = BIT(data, 0, 2) != 2 && !BIT(data, 2); + if (!tx_run && m_timer_x->enabled()) + { + m_tx_count = get_timer_x(); + LOGMASKED(LOG_TIMER_X, "%s: Timer X count stopped at $%04X in %s mode by write to interrupt control register 3 ($%02X -> $%02X)\n", machine().describe_context(), + m_tx_count, s_timer_x_modes[BIT(data, 0, 2)], old_control, data); + m_timer_x->enable(false); + } + else if (tx_run && !m_timer_x->enabled() && (m_tx_count != 0 || m_tx_reload != 0)) + { + LOGMASKED(LOG_TIMER_X, "%s: Timer X count restarted from $%04X in %s mode by write to interrupt control register 3 ($%02X -> $%02X)\n", machine().describe_context(), + m_tx_count, s_timer_x_modes[BIT(data, 0, 2)], old_control, data); + m_timer_x->adjust(clocks_to_attotime(16 * (m_tx_count != 0 ? m_tx_count : m_tx_reload + 1))); + m_timer_x->enable(true); + } + bool tx_interrupt = (data & (data >> 1) & 0x15) != 0; + bool old_interrupt = (old_control & (old_control >> 1) & 0x15) != 0; + if (tx_interrupt != old_interrupt) + { + LOGMASKED(LOG_TIMER_X, "%s: Timer X interrupt %sactivated by write to interrupt control register 3 ($%02X -> $%02X)\n", machine().describe_context(), tx_interrupt ? "": "de", old_control, data); + set_input_line(M740_INT4_LINE, tx_interrupt ? ASSERT_LINE : CLEAR_LINE); + } + } +} + +template <int N> +u8 m50734_device::port_r(offs_t offset) +{ + if (BIT(offset, 0)) + return m_port_direction[N]; + else if (m_port_direction[0] == 0xff) + return m_port_latch[N]; + else + return (m_port_in_cb[N]() & ~m_port_direction[N]) | (m_port_latch[N] & m_port_direction[N]); +} + +template <int N> +void m50734_device::port_w(offs_t offset, u8 data) +{ + if (BIT(offset, 0)) + { + if (m_port_direction[N] != data) + { + LOGMASKED(LOG_INIT, "%s: Port P%d direction = $%02X\n", machine().describe_context(), N, data); + m_port_direction[N] = data; + m_port_out_cb[N]((data & m_port_latch[N]) | (m_port_3state[N] & ~m_port_direction[N])); + } + } + else if (((std::exchange(m_port_latch[N], data) ^ data) & m_port_direction[N]) != 0) + m_port_out_cb[N]((data & m_port_direction[N]) | (m_port_3state[N] & ~m_port_direction[N])); +} + +u8 m50734_device::p4_r() +{ + // P4 has only 4 pins and no output drivers + return m_port_in_cb[4]() & 0x0f; +} + +u8 m50734_device::p0_function_r() +{ + return m_p0_function; +} + +void m50734_device::p0_function_w(u8 data) +{ + LOGMASKED(LOG_INIT, "%s: Port P0 function = $%02X\n", machine().describe_context(), data); + m_p0_function = data; +} + +u8 m50734_device::p2_p3_function_r() +{ + return m_p2_p3_function; +} + +void m50734_device::p2_p3_function_w(u8 data) +{ + LOGMASKED(LOG_INIT, "%s: Port P2/P3 function = $%02X\n", machine().describe_context(), data); + m_p2_p3_function = data & 0xc7; +} + +u8 m50734_device::ad_control_r() +{ + return m_ad_control; +} + +void m50734_device::ad_control_w(u8 data) +{ + m_ad_control = data & 0x03; + m_ad_timer->adjust(cycles_to_attotime(72)); // 36 µsec at 8 MHz +} + +TIMER_CALLBACK_MEMBER(m50734_device::ad_complete) +{ + m_ad_register = m_analog_in_cb[m_ad_control & 0x03](); + m_ad_control |= 0x04; +} + +u8 m50734_device::ad_r() +{ + return m_ad_register; +} + +u8 m50734_device::timer_r(offs_t offset) +{ + if (!m_timer[offset >> 1]->enabled()) + return 0; + + u32 ticks = attotime_to_clocks(m_timer[offset >> 1]->remaining()) / 16; + u8 pre = m_prescaler_reload[offset >> 1]; + if (BIT(offset, 0)) + return std::min<u32>(ticks / (pre + 1), 0xff); + else + return ticks % (pre + 1); +} + +void m50734_device::timer_w(offs_t offset, u8 data) +{ + u32 ticks = m_timer[offset >> 1]->enabled() ? attotime_to_clocks(m_timer[offset >> 1]->remaining()) / 16 : 0x10000; + u8 pre = m_prescaler_reload[offset >> 1]; + if (BIT(offset, 0)) + { + attotime expire_time = clocks_to_attotime(16 * (data * pre + (ticks % (pre + 1)) + 1)); + LOGMASKED(LOG_INIT, "%s: Reload timer %d latch = %u (expires in %.1f usec)\n", machine().describe_context(), (offset >> 1) + 1, data, expire_time.as_double() * 1.0E6); + m_timer_reload[offset >> 1] = data; + m_timer[offset >> 1]->adjust(expire_time); + } + else + { + attotime expire_time = clocks_to_attotime(16 * (data + 1) * (std::min<u32>(ticks / (pre + 1), 0xff) + 1)); + LOGMASKED(LOG_INIT, "%s: Reload prescaler %d latch = %u (expires in %.1f usec)\n", machine().describe_context(), (offset >> 1) + 1, data, expire_time.as_double() * 1.0E6); + m_prescaler_reload[offset >> 1] = data; + m_timer[offset >> 1]->adjust(expire_time); + } +} + +u8 m50734_device::step_counter_r(offs_t offset) +{ + return m_step_counter[offset]; +} + +void m50734_device::step_counter_w(offs_t offset, u8 data) +{ + LOGMASKED(LOG_INIT, "%s: %s counter = %u steps\n", machine().describe_context(), BIT(offset, 0) ? "Vertical" : "Horizontal", data); + m_step_counter[offset] = data; +} + +u8 m50734_device::phase_counter_r() +{ + return m_phase_counter; +} + +void m50734_device::phase_counter_w(u8 data) +{ + // HPHC and VPHC are 3-bit counters (TODO: P2 output) + m_phase_counter = data & 0x77; +} + +u8 m50734_device::smcon_r(offs_t offset) +{ + return m_smcon[offset]; +} + +void m50734_device::smcon_w(offs_t offset, u8 data) +{ + if (m_smcon[offset] != (data & 0x0f)) + LOGMASKED(LOG_INIT, "%s: SMCON%c = $%02X\n", machine().describe_context(), "HV"[offset], data); + m_smcon[offset] = data & 0x0f; +} + +TIMER_CALLBACK_MEMBER(m50734_device::timer_x_interrupt) +{ + if (!BIT(m_interrupt_control[2], 5)) + { + m_interrupt_control[2] |= 0x20; + if (BIT(m_interrupt_control[2], 4)) + { + LOGMASKED(LOG_TIMER_X, "Timer X interrupt asserted at %s\n", machine().time().to_string()); + set_input_line(M740_INT4_LINE, ASSERT_LINE); + } + } + + if (m_tx_reload != 0) + m_timer_x->adjust(clocks_to_attotime(16 * (m_tx_reload + 1))); + else + { + m_tx_count = 0; + m_timer_x->enable(false); + } +} + +u16 m50734_device::get_timer_x() const +{ + if (m_timer_x->enabled()) + { + unsigned count = attotime_to_clocks(m_timer_x->remaining()) / 16; + return count > m_tx_reload ? 0 : count; + } + else + return m_tx_count; +} + +void m50734_device::set_timer_x(u16 count) +{ + LOGMASKED(LOG_TIMER_X, "%s: Timer X reload value = $%04X (%s mode)\n", machine().describe_context(), count, s_timer_x_modes[BIT(m_interrupt_control[2], 0, 2)]); + m_tx_reload = count; + if (BIT(m_interrupt_control[2], 0, 2) != 2 && !BIT(m_interrupt_control[2], 2)) + { + m_timer_x->adjust(clocks_to_attotime(16 * count)); + m_timer_x->enable(true); + } + else + m_tx_count = count; +} + +u8 m50734_device::timer_x_r(offs_t offset) +{ + return BIT(get_timer_x(), BIT(offset, 0) ? 8 : 0, 8); +} + +void m50734_device::timer_x_w(offs_t offset, u8 data) +{ + if (BIT(offset, 0)) + set_timer_x(data << 8 | (m_tx_reload & 0x00ff)); + else + set_timer_x(data | (m_tx_reload & 0xff00)); +} + +void m50734_device::internal_map(address_map &map) +{ + // TODO: other timers, UART, etc. + map(0x00da, 0x00db).rw(FUNC(m50734_device::timer_x_r), FUNC(m50734_device::timer_x_w)); + map(0x00dc, 0x00e1).rw(FUNC(m50734_device::timer_r), FUNC(m50734_device::timer_w)); + map(0x00e2, 0x00e3).rw(FUNC(m50734_device::step_counter_r), FUNC(m50734_device::step_counter_w)); + map(0x00e9, 0x00e9).rw(FUNC(m50734_device::ad_control_r), FUNC(m50734_device::ad_control_w)); + map(0x00ea, 0x00ea).r(FUNC(m50734_device::ad_r)); + map(0x00eb, 0x00eb).r(FUNC(m50734_device::p4_r)); + map(0x00ec, 0x00ec).rw(FUNC(m50734_device::phase_counter_r), FUNC(m50734_device::phase_counter_w)); + map(0x00ed, 0x00ed).rw(FUNC(m50734_device::p2_p3_function_r), FUNC(m50734_device::p2_p3_function_w)); + map(0x00ee, 0x00ef).rw(FUNC(m50734_device::port_r<3>), FUNC(m50734_device::port_w<3>)); + map(0x00f0, 0x00f1).rw(FUNC(m50734_device::port_r<2>), FUNC(m50734_device::port_w<2>)); + map(0x00f3, 0x00f4).rw(FUNC(m50734_device::port_r<1>), FUNC(m50734_device::port_w<1>)); + map(0x00f5, 0x00f5).rw(FUNC(m50734_device::p0_function_r), FUNC(m50734_device::p0_function_w)); + map(0x00f6, 0x00f7).rw(FUNC(m50734_device::port_r<0>), FUNC(m50734_device::port_w<0>)); + map(0x00f8, 0x00f9).rw(FUNC(m50734_device::smcon_r), FUNC(m50734_device::smcon_w)); + map(0x00fd, 0x00ff).rw(FUNC(m50734_device::interrupt_control_r), FUNC(m50734_device::interrupt_control_w)); +} |