// license:BSD-3-Clause // copyright-holders:Joakim Larsson Edstrom /********************************************************************* Motorola MC14411 Bit Rate Generator This CMOS device utilizes a frequency divider network to provide sixteen different clock outputs (50% duty cycle). A 1.8432 MHz crystal oscillator normally provides the clock source for the network, though an external clock may also be applied to pin 21. A 2-bit address is used to select one of four rate multipliers for the F1-F14 outputs (X64, X16, X8, X1). The undivided input frequency is output on F16 (buffered). The base dividers for the F12 and F13 outputs do not go into 1.8432 * 10^6 evenly; the frequencies shown in the table below have accordingly been rounded off to five figures. The X64 frequencies for these two outputs are given on the Motorola datasheet as 8613.2 and 7035.5, probably incorrectly calculated. To lighten the burden on MAME's scheduler, the emulation of this device assumes that not all timer outputs will be needed at any given time, and only emulates the clocks actually in use. The special enable method intended to be used when several outputs are multiplexed through external switches. *********************************************************************/ #include "emu.h" #include "mc14411.h" /*************************************************************************** MACROS ***************************************************************************/ //#define LOG_GENERAL (1U << 0) // Already defined in logmacro.h #define LOG_SETUP (1U << 1) //#define VERBOSE (LOG_GENERAL|LOG_SETUP) //#define LOG_OUTPUT_STREAM std::cout #include "logmacro.h" //#define LOG(...) LOGMASKED(LOG_GENERAL, __VA_ARGS__) // Already defined in logmacro.h #define LOGSETUP(...) LOGMASKED(LOG_SETUP, __VA_ARGS__) #ifdef _MSC_VER #define FUNCNAME __func__ #else #define FUNCNAME __PRETTY_FUNCTION__ #endif /*************************************************************************** INTERNAL TABLES ***************************************************************************/ const int mc14411_device::s_counter_divider[16] = { ////////// X64 /////// X16 /////// X8 //////// X1 //////// 3, // F1: 614.4 kHz 153.6 kHz 76800 Hz 9600 Hz 4, // F2: 460.8 kHz 115.2 kHz 57600 Hz 7200 Hz 6, // F3: 307.2 kHz 76800 Hz 36400 Hz 4800 Hz 8, // F4: 230.4 kHz 57600 Hz 28800 Hz 3600 Hz 12, // F5: 153.6 kHz 38400 Hz 19200 Hz 2400 Hz 16, // F6: 115.2 kHz 28800 Hz 14400 Hz 1800 Hz 24, // F7: 76800 Hz 19200 Hz 9600 Hz 1200 Hz 48, // F8: 38400 Hz 9600 Hz 4800 Hz 600 Hz 96, // F9: 19200 Hz 4800 Hz 2400 Hz 300 Hz 144, // F10: 12800 Hz 3200 Hz 1600 Hz 200 Hz 192, // F11: 9600 Hz 2400 Hz 1200 Hz 150 Hz 214, // F12: 8613.1 Hz 2153.3 Hz 1076.6 Hz 134.58 Hz 262, // F13: 7035.1 Hz 1758.8 Hz 879.39 Hz 109.92 Hz 384, // F14: 4800.0 Hz 1200 Hz 600 Hz 75 Hz 2, // F15: 921.6 kHz 921.6 kHz 921.6 kHz 921.6 kHz 1 // F16: 1.8432 MHz 1.8432 MHz 1.8432 MHz 1.8432 MHz }; const int mc14411_device::s_divider_select[4] = { 64, // RSB=0, RSA=0 - X1 8, // RSB=0, RSA=1 - X8 4, // RSB=1, RSA=0 - X16 1 // RSB=1, RSA=1 - X64 }; //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(MC14411, mc14411_device, "mc14411", "MC14411 Bit Rate Generator") //************************************************************************** // LIVE DEVICE //************************************************************************** mc14411_device::mc14411_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : mc14411_device(mconfig, MC14411, tag, owner, clock) { } mc14411_device::mc14411_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock) , m_out_fx_cbs{*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this } , m_divider(0) , m_reset(CLEAR_LINE) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void mc14411_device::device_start() { LOGSETUP("%s\n", FUNCNAME); for (int i = TIMER_F1; i <= TIMER_F16; i++) { m_out_fx_cbs[i].resolve(); m_fx_timer[i] = timer_alloc(i); m_timer_enabled[i] = !m_out_fx_cbs[i].isnull(); } save_item(NAME(m_divider)); save_item(NAME(m_reset)); save_item(NAME(m_timer_enabled)); m_reset_timer = timer_alloc(TIMER_ID_RESET); } //------------------------------------------------- // device_clock_changed - notifier called upon // establishment or alteration of the device clock //------------------------------------------------- void mc14411_device::device_clock_changed() { for (int i = TIMER_F1; i <= TIMER_F16; i++) { if (m_timer_enabled[i]) arm_timer(i); } } //------------------------------------------------- // timer_enable - enable or disable one timer //------------------------------------------------- void mc14411_device::timer_enable(timer_id i, bool enable) { assert(i >= TIMER_F1 && i <= TIMER_F16); m_timer_enabled[i] = enable; if (!enable) m_fx_timer[i]->enable(false); else if (!m_fx_timer[i]->enabled()) arm_timer(i); } void mc14411_device::timer_disable_all() { for (int i = TIMER_F1; i <= TIMER_F16; i++) timer_enable((timer_id) i, false); } //------------------------------------------------- // arm_timer - arm the timer based on selected // divider and crystal value //------------------------------------------------- void mc14411_device::arm_timer(int i) { assert(!m_out_fx_cbs[i].isnull()); int divider = s_counter_divider[i]; if (i < TIMER_F15) divider *= s_divider_select[m_divider]; attotime half_cycle = clocks_to_attotime(divider) / 2; // 2 flanks per cycle m_fx_timer[i]->adjust(half_cycle, i, half_cycle); LOGSETUP(" - arming timer for F%d at %fHz (/%d)\n", i + 1, double(clock()) / divider, divider); } //------------------------------------------------------------------------ // device_reset - is called by the mame framework or by the owning device // driver or by ASSERTING the reset line through set_reset_line //------------------------------------------------------------------------ void mc14411_device::device_reset() { LOGSETUP("%s\n", FUNCNAME); for (int i = TIMER_F1; i <= TIMER_F16; i++) { if (!m_out_fx_cbs[i].isnull()) { // Reset line according to datasheet and remember it for transitions to come (m_out_fx_cbs[i])(m_fx_state[i] = (i < TIMER_F15 ? 0 : 1)); } } if (m_reset == ASSERT_LINE) { m_reset_timer->adjust(attotime::from_nsec((double)900), TIMER_ID_RESET, attotime::from_nsec((double)900)); } } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void mc14411_device::device_timer(emu_timer &timer, device_timer_id id, int32_t param, void *ptr) { if (id >= TIMER_F1 && id <= TIMER_F16) { (m_out_fx_cbs[id])(m_fx_state[id]++ & 1); } else if (id == TIMER_ID_RESET) { // NOTE: This check could be triggered by either faulty hardware design or non accurate emulation so is just informative if the reset line is handled // explicitelly instead of relying on calling device_reset if (!(m_reset == ASSERT_LINE)) { LOG("Reset pulse is too short, should be 900nS minimum"); logerror("Reset pulse is too short, should be 900nS minimum"); } } else { LOG("Unhandled Timer ID %d\n", id); } } //-------------------------------------------------------- // rate_select_w - implements the RSA and RSB input pins // TODO: Needs to check real device behaviour how changing // divider at run time affects wave forms //-------------------------------------------------------- WRITE8_MEMBER(mc14411_device::rate_select_w) { LOGSETUP("%s %02x\n", FUNCNAME, data); if (m_divider != (data & 3)) { m_divider = data & 3; notify_clock_changed(); } } //------------------------------------------------ // rsa_w - set RSA input line //------------------------------------------------ WRITE_LINE_MEMBER(mc14411_device::rsa_w) { LOGSETUP("%s %02x\n", FUNCNAME, state); if ((m_divider & RSA) != (state == ASSERT_LINE ? RSA : 0)) { m_divider = (m_divider & ~RSA) | (state == ASSERT_LINE ? RSA : 0); notify_clock_changed(); } } //------------------------------------------------ // rsb_w - set RSB input line //------------------------------------------------ WRITE_LINE_MEMBER(mc14411_device::rsb_w) { LOGSETUP("%s %02x\n", FUNCNAME, state); if ((m_divider & RSB) != (state == ASSERT_LINE ? RSB : 0)) { m_divider = (m_divider & ~RSB) | (state == ASSERT_LINE ? RSB : 0); notify_clock_changed(); } } //------------------------------------------------ // reset_w - software controlled reset //------------------------------------------------ WRITE_LINE_MEMBER(mc14411_device::reset_w) { LOGSETUP("%s %02x\n", FUNCNAME, state); m_reset = state; if (m_reset == ASSERT_LINE) { LOGSETUP(" - Asserting reset\n"); device_reset(); } else { LOGSETUP(" - Clearing reset\n"); } }